From af43af19252fdd25c4d8061948c8c227be0a7fc7 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 28 Jul 2022 15:19:24 -0700 Subject: [PATCH 01/65] all: Issue 416 Implement GET `/unique_address` of the callback API (#430) * added GetUniqueAddressResponse * add transaction_id to the callback API * add unique address integration and related classes * add sep31 transactional annotations * fixed test errrors * add transaction_id parameter and changed the configuration property name * added unit tests for Sep31DepositInfoGeneratorApi * added test_updateDepositInfo_api test and moved the test class to avoid reflection call * add Sep31DepositInfoGeneratorApi tests * fixed endpoint typo * Empty-Commit * fixed typo * updated sequence diagram * moved unique_address endpoint configuration to Sep31Config * check distributionWalletMemoType * add distributionWallet validity check * fixed according to PR review --- .../anchor/reference/config/AppSettings.java | 3 + .../controller/UniqueAddressController.java | 26 ++++++ .../service/UniqueAddressService.java | 52 +++++++++++ .../resources/anchor-reference-server.yaml | 5 + .../callback/GetUniqueAddressResponse.java | 24 +++++ .../callback/UniqueAddressIntegration.java | 13 +++ core/build.gradle.kts | 1 + .../stellar/anchor/config/Sep31Config.java | 5 +- .../sep31/Sep31DepositInfoGenerator.java | 3 +- .../stellar/anchor/sep31/Sep31Service.java | 15 ++- .../kotlin/org/stellar/anchor/TestHelper.kt | 6 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 20 ++-- .../Communication/Callbacks API.yml | 11 ++- .../anchor/platform/IntegrationConfig.java | 9 ++ .../stellar/anchor/platform/SepConfig.java | 20 ++-- .../RestUniqueAddressIntegration.java | 56 ++++++++++++ .../platform/config/PropertySep31Config.java | 14 ++- .../service/Sep31DepositInfoGeneratorApi.java | 33 +++++++ .../Sep31DepositInfoGeneratorCircle.java | 2 +- .../Sep31DepositInfoGeneratorSelf.java | 2 +- .../resources/anchor-config-defaults.yaml | 11 ++- .../Sep31DepositInfoGeneratorApiTest.kt | 91 +++++++++++++++++++ .../Sep31DepositInfoGeneratorTest.kt | 56 +++++++++--- 23 files changed, 432 insertions(+), 46 deletions(-) create mode 100644 anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java create mode 100644 anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/callback/GetUniqueAddressResponse.java create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/callback/UniqueAddressIntegration.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/callback/RestUniqueAddressIntegration.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt rename platform/src/test/kotlin/org/stellar/anchor/{platform => sep31}/Sep31DepositInfoGeneratorTest.kt (79%) diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/AppSettings.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/AppSettings.java index 9c7f30bcb2..4ec7df7ed7 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/AppSettings.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/AppSettings.java @@ -11,4 +11,7 @@ public class AppSettings { String version; String platformApiEndpoint; String hostUrl; + String distributionWallet; + String distributionWalletMemo; + String distributionWalletMemoType; } diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java new file mode 100644 index 0000000000..639d7a3291 --- /dev/null +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java @@ -0,0 +1,26 @@ +package org.stellar.anchor.reference.controller; + +import com.google.gson.Gson; +import org.springframework.web.bind.annotation.*; +import org.stellar.anchor.api.callback.GetUniqueAddressResponse; +import org.stellar.anchor.reference.service.UniqueAddressService; +import org.stellar.anchor.util.GsonUtils; + +@RestController +public class UniqueAddressController { + final UniqueAddressService uniqueAddressService; + static final Gson gson = GsonUtils.builder().create(); + + public UniqueAddressController(UniqueAddressService uniqueAddressService) { + this.uniqueAddressService = uniqueAddressService; + } + + @RequestMapping( + value = "/unique_address", + method = {RequestMethod.GET}) + @ResponseBody + public GetUniqueAddressResponse getRate( + @RequestParam(name = "transaction_id") String transactionId) { + return uniqueAddressService.getUniqueAddress(transactionId); + } +} diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java new file mode 100644 index 0000000000..39a04d1f82 --- /dev/null +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java @@ -0,0 +1,52 @@ +package org.stellar.anchor.reference.service; + +import java.util.Objects; +import org.springframework.stereotype.Service; +import org.stellar.anchor.api.callback.GetUniqueAddressResponse; +import org.stellar.anchor.api.exception.SepException; +import org.stellar.anchor.reference.config.AppSettings; +import org.stellar.anchor.util.MemoHelper; +import org.stellar.sdk.KeyPair; + +@Service +public class UniqueAddressService { + String distributionWallet; + String distributionWalletMemo; + String distributionWalletMemoType; + + UniqueAddressService(AppSettings appSettings) throws SepException { + this.distributionWallet = appSettings.getDistributionWallet(); + if (Objects.toString(appSettings.getDistributionWallet(), "").isEmpty()) { + throw new SepException("distributionWallet is empty"); + } + + try { + KeyPair.fromAccountId(this.distributionWallet); + } catch (Exception ex) { + throw new SepException( + String.format("Invalid distributionWallet: [%s]", this.distributionWallet)); + } + + if (!Objects.toString(appSettings.getDistributionWalletMemo(), "").isEmpty() + && !Objects.toString(appSettings.getDistributionWalletMemoType(), "").isEmpty()) { + // check if memo and memoType are valid + MemoHelper.makeMemo( + appSettings.getDistributionWalletMemo(), appSettings.getDistributionWalletMemoType()); + } + this.distributionWalletMemo = appSettings.getDistributionWalletMemo(); + this.distributionWalletMemoType = appSettings.getDistributionWalletMemoType(); + } + + public GetUniqueAddressResponse getUniqueAddress(String transactionId) { + // transactionId may be used to query the transaction information if the anchor would like to + // return a transaction-dependent unique address. + return GetUniqueAddressResponse.builder() + .uniqueAddress( + GetUniqueAddressResponse.UniqueAddress.builder() + .stellarAddress(distributionWallet) + .memo(distributionWalletMemo) + .memoType(distributionWalletMemoType) + .build()) + .build(); + } +} diff --git a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml index b89bc0314b..effe6bbc57 100644 --- a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml +++ b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml @@ -13,6 +13,11 @@ anchor.settings: # The URL where the anchor reference server can be accessed by the platform. hostUrl: http://localhost:8081 + # The Stellar wallet to which the customer will send the Stellar assets. + distributionWallet: GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR + distributionWalletMemo: + distributionWalletMemoType: + # These are secrets shared between Anchor and Platform that are used to safely communicate from `Platform->Anchor` # and `Anchor->Platform`, specially when they are in different clusters. # diff --git a/api-schema/src/main/java/org/stellar/anchor/api/callback/GetUniqueAddressResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/callback/GetUniqueAddressResponse.java new file mode 100644 index 0000000000..cd4fd6ad63 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/callback/GetUniqueAddressResponse.java @@ -0,0 +1,24 @@ +package org.stellar.anchor.api.callback; + +import com.google.gson.annotations.SerializedName; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class GetUniqueAddressResponse { + @SerializedName("unique_address") + UniqueAddress uniqueAddress; + + @Data + @Builder + public static class UniqueAddress { + @SerializedName("stellar_address") + String stellarAddress; + + String memo; + + @SerializedName("memo_type") + String memoType; + } +} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/callback/UniqueAddressIntegration.java b/api-schema/src/main/java/org/stellar/anchor/api/callback/UniqueAddressIntegration.java new file mode 100644 index 0000000000..2d4019e1d7 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/callback/UniqueAddressIntegration.java @@ -0,0 +1,13 @@ +package org.stellar.anchor.api.callback; + +import org.stellar.anchor.api.exception.AnchorException; + +public interface UniqueAddressIntegration { + /** + * Gets the unique address of a transaction to which the Stellar fund can be sent. + * + * @param transactionId + * @return The GetUniqueAddressResponse + */ + GetUniqueAddressResponse getUniqueAddress(String transactionId) throws AnchorException; +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 36d4d8c344..95bfb0ff4d 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(libs.javax.jaxb.api) implementation(libs.java.stellar.sdk) implementation("io.micrometer:micrometer-registry-prometheus:1.9.0") + implementation("javax.transaction:javax.transaction-api:1.3") implementation(project(":api-schema")) diff --git a/core/src/main/java/org/stellar/anchor/config/Sep31Config.java b/core/src/main/java/org/stellar/anchor/config/Sep31Config.java index 9cef09b182..6ae98a62ed 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep31Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep31Config.java @@ -5,6 +5,8 @@ public interface Sep31Config { String getFeeIntegrationEndPoint(); + String getUniqueAddressIntegrationEndPoint(); + PaymentType getPaymentType(); DepositInfoGeneratorType getDepositInfoGeneratorType(); @@ -16,6 +18,7 @@ enum PaymentType { enum DepositInfoGeneratorType { SELF, - CIRCLE + CIRCLE, + API } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31DepositInfoGenerator.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31DepositInfoGenerator.java index aecfca11fe..42fa723db1 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31DepositInfoGenerator.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31DepositInfoGenerator.java @@ -1,5 +1,6 @@ package org.stellar.anchor.sep31; +import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.sep.sep31.Sep31DepositInfo; public interface Sep31DepositInfoGenerator { @@ -9,5 +10,5 @@ public interface Sep31DepositInfoGenerator { * @param txn the original SEP-31 transaction the deposit info will be used for. * @return a Sep31DepositInfo instance containing the destination address, memo and memoType. */ - Sep31DepositInfo getSep31DepositInfo(Sep31Transaction txn); + Sep31DepositInfo generate(Sep31Transaction txn) throws AnchorException; } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index af3d18c6b6..4222357736 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -12,6 +12,7 @@ import java.time.Instant; import java.util.*; import java.util.stream.Collectors; +import javax.transaction.Transactional; import lombok.Data; import lombok.SneakyThrows; import org.stellar.anchor.api.callback.CustomerIntegration; @@ -80,6 +81,7 @@ public Sep31InfoResponse getInfo() { return infoResponse; } + @Transactional(rollbackOn = {AnchorException.class, RuntimeException.class}) public Sep31PostTransactionResponse postTransaction( JwtToken jwtToken, Sep31PostTransactionRequest request) throws AnchorException { Context.reset(); @@ -161,8 +163,11 @@ public Sep31PostTransactionResponse postTransaction( Context.get().setTransaction(txn); updateAmounts(); - updateDepositInfo(txn); - sep31TransactionStore.save(txn); + + Context.get().setTransaction(sep31TransactionStore.save(txn)); + txn = Context.get().getTransaction(); + + updateDepositInfo(); StellarId senderStellarId = StellarId.builder().id(txn.getSenderId()).build(); StellarId receiverStellarId = StellarId.builder().id(txn.getReceiverId()).build(); @@ -270,8 +275,9 @@ void updateTxAmountsWhenNoQuoteWasUsed() { Context.get().getFee().setAmount(feeStr); } - private void updateDepositInfo(Sep31Transaction txn) { - Sep31DepositInfo depositInfo = sep31DepositInfoGenerator.getSep31DepositInfo(txn); + void updateDepositInfo() throws AnchorException { + Sep31Transaction txn = Context.get().getTransaction(); + Sep31DepositInfo depositInfo = sep31DepositInfoGenerator.generate(txn); infoF("Updating transaction ({}) with depositInfo ({})", txn.getId(), depositInfo); txn.setStellarAccountId(depositInfo.getStellarAddress()); txn.setStellarMemo(depositInfo.getMemo()); @@ -293,6 +299,7 @@ public Sep31GetTransactionResponse getTransaction(String id) throws AnchorExcept return fromTransactionToResponse(txn); } + @Transactional(rollbackOn = {AnchorException.class, RuntimeException.class}) public Sep31GetTransactionResponse patchTransaction(Sep31PatchTransactionRequest request) throws AnchorException { if (request == null) { diff --git a/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt b/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt index f3c65f1950..b57b43b4d3 100644 --- a/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt +++ b/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt @@ -4,10 +4,10 @@ import org.stellar.anchor.auth.JwtToken class TestHelper { companion object { - public const val TEST_ACCOUNT = "GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2" - public const val TEST_MUXED_ACCOUNT = + const val TEST_ACCOUNT = "GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2" + const val TEST_MUXED_ACCOUNT = "MBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLEAAAAAAAAAPCIBR34" - public const val TEST_MEMO = "123456" + const val TEST_MEMO = "123456" fun createJwtToken( account: String = TEST_ACCOUNT, 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 ce911ac6dc..ac1a7c5de9 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -635,9 +635,7 @@ class Sep31ServiceTest { // mock sep31 deposit info generation val txForDepositInfoGenerator = slot() - every { - sep31DepositInfoGenerator.getSep31DepositInfo(capture(txForDepositInfoGenerator)) - } answers + every { sep31DepositInfoGenerator.generate(capture(txForDepositInfoGenerator)) } answers { val tx: Sep31Transaction = txForDepositInfoGenerator.captured var memo = StringUtils.truncate(tx.id, 32) @@ -652,7 +650,11 @@ class Sep31ServiceTest { // mock transaction save val slotTxn = slot() - every { txnStore.save(capture(slotTxn)) } returns null + every { txnStore.save(capture(slotTxn)) } answers + { + firstArg().id = "ABC-123" + firstArg() + } // POST transaction val jwtToken = TestHelper.createJwtToken(accountMemo = TestHelper.TEST_MEMO) @@ -665,7 +667,7 @@ class Sep31ServiceTest { request = Sep12GetCustomerRequest.builder().id(receiverId).type("sep31-receiver").build() verify(exactly = 1) { customerIntegration.getCustomer(request) } verify(exactly = 1) { quoteStore.findByQuoteId("my_quote_id") } - verify(exactly = 1) { sep31DepositInfoGenerator.getSep31DepositInfo(any()) } + verify(exactly = 1) { sep31DepositInfoGenerator.generate(any()) } verify(exactly = 1) { eventPublishService.publish(any()) } // validate the values of the saved sep31Transaction @@ -794,9 +796,15 @@ class Sep31ServiceTest { @Test fun test_postTransaction_quoteNotSupported() { - every { sep31DepositInfoGenerator.getSep31DepositInfo(any()) } returns + every { sep31DepositInfoGenerator.generate(any()) } returns Sep31DepositInfo("GA7FYRB5VREZKOBIIKHG5AVTPFGWUBPOBF7LTYG4GTMFVIOOD2DWAL7I", "123456", "id") + every { txnStore.save(any()) } answers + { + firstArg().id = "ABC-123" + firstArg() + } + val assetServiceQuotesNotSupported: AssetService = ResourceJsonAssetService( "test_assets.json.quotes_not_supported", diff --git a/docs/03 - Implementing the Anchor Server/Communication/Callbacks API.yml b/docs/03 - Implementing the Anchor Server/Communication/Callbacks API.yml index 68636ade1f..b02eea7036 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Callbacks API.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Callbacks API.yml @@ -33,9 +33,18 @@ paths: Here is how this flow would be used for Receiving Anchors that need to create their unique `stellar_address:memo` pairs outside the platform, using Circle: - [![](https://mermaid.ink/img/pako:eNqlVV1v2jAU_StXedo0Aqr6lodKiNGtD9NQYW-RJmPfEAvHzmKnqEL8993ECQFq0NrlyfHHOcfnfngfcSMwSiKLf2rUHL9KtqlYkWqgz0mnMIHlfBHf38GqYtoy7qTR8KjMDnbS5ZBGe86UWjO-nS6eDpNaS0L6zYSo0No08kglq5zksmTawdKhUqwKLKAWUm9gqnluAuufnpGjfKEtn2GhmMtMVdzedQ1pJiuu0M-fs8YPD19CPAksfi5XsLdY3t8dJm7wwnoYUxIsGQG37IA1syiADHQ5QtlhAzc6k5u6Yg2eh2u-kI5LfV51At_mq5vMYdThzt4RwkGNpAOBwVoZvuU5kxo6kPGA4vfHdDQOyXlGV1fagsYdeCGUKNYHvpeUFFiYNKLAyGp8U98lyxCUD_JQyP0gaDHRhdkepZY2RwpjLVWbNE0Yu_I4SQlfGc3aOzRdkxKfJ-hw5zD3-EpWd1WXwA-2xb4Gif-1QO36Q342vlEEfcoKdMidpTS5gIKclSUlkYDa9g79rwvn8eh9OGqxdNdGyUmDmr80Uo5hOG1dTzozAbKjUUGqynBEIjkiknwHJmvHGTXDUauiv3DvhTPt7xERZkoOdocr8Yrx09XsO-z7ltFWeDjjSkHlK0JZaR1ztR1_JPN_tagtwVXEf22kbasK9NFJIkXjGM-Rb1sqD9_b_DbJ31kyb-RL3Zxou-44GkUF0o8U9BruG_w0ov0FplFCQ4EZq5Vr3rMDbfUuz4V0poqSjCmLo4jVzixfNT9O-F3dk9rNHv4CY--UbA)](https://mermaid.live/edit#pako:eNqlVV1v2jAU_StXedo0Aqr6lodKiNGtD9NQYW-RJmPfEAvHzmKnqEL8993ECQFq0NrlyfHHOcfnfngfcSMwSiKLf2rUHL9KtqlYkWqgz0mnMIHlfBHf38GqYtoy7qTR8KjMDnbS5ZBGe86UWjO-nS6eDpNaS0L6zYSo0No08kglq5zksmTawdKhUqwKLKAWUm9gqnluAuufnpGjfKEtn2GhmMtMVdzedQ1pJiuu0M-fs8YPD19CPAksfi5XsLdY3t8dJm7wwnoYUxIsGQG37IA1syiADHQ5QtlhAzc6k5u6Yg2eh2u-kI5LfV51At_mq5vMYdThzt4RwkGNpAOBwVoZvuU5kxo6kPGA4vfHdDQOyXlGV1fagsYdeCGUKNYHvpeUFFiYNKLAyGp8U98lyxCUD_JQyP0gaDHRhdkepZY2RwpjLVWbNE0Yu_I4SQlfGc3aOzRdkxKfJ-hw5zD3-EpWd1WXwA-2xb4Gif-1QO36Q342vlEEfcoKdMidpTS5gIKclSUlkYDa9g79rwvn8eh9OGqxdNdGyUmDmr80Uo5hOG1dTzozAbKjUUGqynBEIjkiknwHJmvHGTXDUauiv3DvhTPt7xERZkoOdocr8Yrx09XsO-z7ltFWeDjjSkHlK0JZaR1ztR1_JPN_tagtwVXEf22kbasK9NFJIkXjGM-Rb1sqD9_b_DbJ31kyb-RL3Zxou-44GkUF0o8U9BruG_w0ov0FplFCQ4EZq5Vr3rMDbfUuz4V0poqSjCmLo4jVzixfNT9O-F3dk9rNHv4CY--UbA) + [![](https://mermaid.ink/img/pako:eNqlVctu2zAQ_JUFTy1iOwhyE9AUhuu0ORQ1YvdmIKDJVUSYIlWSihEY_veuRMuPhHaTVCc9VjPD4exyzYSVyDLm8U-NRuA3xR8dL-cG6AoqaMxgOp70r69g5rjxXARlDdxqu4KVCgXM2VpwrRdcLIeTu81lbRQhPXApHXo_ZxGp4i4ooSpuAkwDas1d4gMaqcwjDI0obOL7p3sUqJ6o5DNMNA-5deX5qlNII-WExvj-mLV_c3OR4slg8ms6g7XH6vpqcxn2XvgIYyuCJSPgnB2w4B4lkIGhQKi22CCsydVj7XiDF-GaK6WD9KXlTfkTwoGsf8JcvDYqg-_j2dkFfD1geFDyS5plb2U0mnDRIC0PgcNCW7EUBVcGtqCDPUqs79Ov_ZS8ewy1Mx4MriAKo_z5mKdOYlZiaeeM9lu5wVl9L1n2Zn6Qh5IUb5KWn9y6W2WUL5DSUSvdZrFJx7brDgyPDdd8e4emU1L6x7nfrznNPTjRLNtmzuAnX2LX2sT_XKIJ3U_xbf9Mb3WdIDGgCJ5i8gIKCl5VFCIJte8c-l8Xjvej82GnxdNaGyUHc2_81EjZbcPhRLwzuU2Q7YxKUjkrEIlkh0jyA9i8vc9pxvZaFd2COy-CbR93iDDSam93uhNPGD-cjX7AuptEbcenE1dJal-ZSqUPPNR-8JHk_25RW4KTiG-dz-3oSozny0zJxjFRoFi2VBG-s_l1yN_ZMq_kK9P80Q7zAeuxEulBSTpk1w3-nFF9iXOW0a3EnNc6NMfkhkqjy2OpgnUsy7n22GO8Dnb6bATLgquxK9oe1NuqzV8v2LI1)](https://mermaid.live/edit#pako:eNqlVctu2zAQ_JUFTy1iOwhyE9AUhuu0ORQ1YvdmIKDJVUSYIlWSihEY_veuRMuPhHaTVCc9VjPD4exyzYSVyDLm8U-NRuA3xR8dL-cG6AoqaMxgOp70r69g5rjxXARlDdxqu4KVCgXM2VpwrRdcLIeTu81lbRQhPXApHXo_ZxGp4i4ooSpuAkwDas1d4gMaqcwjDI0obOL7p3sUqJ6o5DNMNA-5deX5qlNII-WExvj-mLV_c3OR4slg8ms6g7XH6vpqcxn2XvgIYyuCJSPgnB2w4B4lkIGhQKi22CCsydVj7XiDF-GaK6WD9KXlTfkTwoGsf8JcvDYqg-_j2dkFfD1geFDyS5plb2U0mnDRIC0PgcNCW7EUBVcGtqCDPUqs79Ov_ZS8ewy1Mx4MriAKo_z5mKdOYlZiaeeM9lu5wVl9L1n2Zn6Qh5IUb5KWn9y6W2WUL5DSUSvdZrFJx7brDgyPDdd8e4emU1L6x7nfrznNPTjRLNtmzuAnX2LX2sT_XKIJ3U_xbf9Mb3WdIDGgCJ5i8gIKCl5VFCIJte8c-l8Xjvej82GnxdNaGyUHc2_81EjZbcPhRLwzuU2Q7YxKUjkrEIlkh0jyA9i8vc9pxvZaFd2COy-CbR93iDDSam93uhNPGD-cjX7AuptEbcenE1dJal-ZSqUPPNR-8JHk_25RW4KTiG-dz-3oSozny0zJxjFRoFi2VBG-s_l1yN_ZMq_kK9P80Q7zAeuxEulBSTpk1w3-nFF9iXOW0a3EnNc6NMfkhkqjy2OpgnUsy7n22GO8Dnb6bATLgquxK9oe1NuqzV8v2LI1) tags: - Unique Address + parameters: + - in: query + name: transaction_id + description: | + The platform server will save the posted transaction and pass the `transaction_id` through this endpoint. + If the anchor wishes to return transaction-dependent unique_address, the anchor may query the [`GET /transactions` endpoint of the Platform API](https://github.com/stellar/java-stellar-anchor-sdk/blob/main/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication/Platform%20API.yml). + schema: + type: string + required: true responses: '200': description: Success. diff --git a/platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java b/platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java index 7aee16c07d..eda5b4a122 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java @@ -8,12 +8,14 @@ import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.api.callback.FeeIntegration; import org.stellar.anchor.api.callback.RateIntegration; +import org.stellar.anchor.api.callback.UniqueAddressIntegration; import org.stellar.anchor.auth.AuthHelper; import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.config.*; import org.stellar.anchor.platform.callback.RestCustomerIntegration; import org.stellar.anchor.platform.callback.RestFeeIntegration; import org.stellar.anchor.platform.callback.RestRateIntegration; +import org.stellar.anchor.platform.callback.RestUniqueAddressIntegration; @Configuration public class IntegrationConfig { @@ -45,6 +47,13 @@ OkHttpClient httpClient() { .build(); } + @Bean + UniqueAddressIntegration uniqueAddressIntegration( + Sep31Config sep31Config, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { + return new RestUniqueAddressIntegration( + sep31Config.getUniqueAddressIntegrationEndPoint(), httpClient, authHelper, gson); + } + @Bean CustomerIntegration customerIntegration( Sep12Config sep12Config, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { diff --git a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java b/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java index f87531661d..89caf4fb3a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java @@ -8,6 +8,7 @@ import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.api.callback.FeeIntegration; import org.stellar.anchor.api.callback.RateIntegration; +import org.stellar.anchor.api.callback.UniqueAddressIntegration; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.config.*; @@ -19,10 +20,7 @@ import org.stellar.anchor.paymentservice.circle.CirclePaymentService; import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig; import org.stellar.anchor.platform.data.*; -import org.stellar.anchor.platform.service.ResourceReaderAssetService; -import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorCircle; -import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf; -import org.stellar.anchor.platform.service.SpringResourceReader; +import org.stellar.anchor.platform.service.*; import org.stellar.anchor.sep1.Sep1Service; import org.stellar.anchor.sep10.Sep10Service; import org.stellar.anchor.sep12.Sep12Service; @@ -158,22 +156,20 @@ CirclePaymentService circlePaymentService( return new CirclePaymentService(circlePaymentConfig, circleConfig, horizon); } - @Bean - Sep31DepositInfoGenerator sep31DepositInfoGeneratorCircle( - CirclePaymentService circlePaymentService) { - return new Sep31DepositInfoGeneratorCircle(circlePaymentService); - } - @Bean Sep31DepositInfoGenerator sep31DepositInfoGenerator( - Sep31Config sep31Config, Sep31DepositInfoGenerator sep31DepositInfoGeneratorCircle) { + Sep31Config sep31Config, + CirclePaymentService circlePaymentService, + UniqueAddressIntegration uniqueAddressIntegration) { switch (sep31Config.getDepositInfoGeneratorType()) { case SELF: return new Sep31DepositInfoGeneratorSelf(); case CIRCLE: - return sep31DepositInfoGeneratorCircle; + return new Sep31DepositInfoGeneratorCircle(circlePaymentService); + case API: + return new Sep31DepositInfoGeneratorApi(uniqueAddressIntegration); default: throw new RuntimeException("Not supported"); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/callback/RestUniqueAddressIntegration.java b/platform/src/main/java/org/stellar/anchor/platform/callback/RestUniqueAddressIntegration.java new file mode 100644 index 0000000000..7b56eeed83 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/callback/RestUniqueAddressIntegration.java @@ -0,0 +1,56 @@ +package org.stellar.anchor.platform.callback; + +import static org.stellar.anchor.platform.callback.PlatformIntegrationHelper.*; + +import com.google.gson.Gson; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.springframework.http.HttpStatus; +import org.stellar.anchor.api.callback.GetUniqueAddressResponse; +import org.stellar.anchor.api.callback.UniqueAddressIntegration; +import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.auth.AuthHelper; + +public class RestUniqueAddressIntegration implements UniqueAddressIntegration { + private final String anchorEndpoint; + private final OkHttpClient httpClient; + private final AuthHelper authHelper; + private final Gson gson; + + public RestUniqueAddressIntegration( + String anchorEndpoint, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { + try { + new URI(anchorEndpoint); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("invalid 'baseUri'"); + } + + this.anchorEndpoint = anchorEndpoint; + this.httpClient = httpClient; + this.authHelper = authHelper; + this.gson = gson; + } + + @Override + public GetUniqueAddressResponse getUniqueAddress(String transactionId) throws AnchorException { + HttpUrl url = + HttpUrl.get(anchorEndpoint) + .newBuilder() + .addQueryParameter("transaction_id", transactionId) + .addPathSegment("unique_address") + .build(); + Request request = getRequestBuilder(authHelper).url(url).get().build(); + Response response = call(httpClient, request); + String content = getContent(response); + + if (!List.of(HttpStatus.OK.value(), HttpStatus.NO_CONTENT.value()).contains(response.code())) { + throw httpError(content, response.code(), gson); + } + return gson.fromJson(content, GetUniqueAddressResponse.class); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java index 89f8b79dc7..9a0d8f32cb 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java @@ -1,9 +1,9 @@ package org.stellar.anchor.platform.config; -import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.CIRCLE; -import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.SELF; +import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.*; import static org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND; +import java.util.Objects; import lombok.Data; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -13,7 +13,8 @@ @Data public class PropertySep31Config implements Sep31Config, Validator { boolean enabled = false; - String feeIntegrationEndPoint = "http://localhost:8081"; + String feeIntegrationEndPoint; + String uniqueAddressIntegrationEndPoint; PaymentType paymentType = STRICT_SEND; DepositInfoGeneratorType depositInfoGeneratorType = SELF; @@ -39,6 +40,13 @@ public void validate(Object target, Errors errors) { "badConfig-circle", "depositInfoGeneratorType set as circle, but circle config not properly configured"); } + } else if (config.getDepositInfoGeneratorType().equals(API)) { + if (Objects.toString(uniqueAddressIntegrationEndPoint, "").isEmpty()) { + errors.rejectValue( + "uniqueAddressIntegrationEndPoint", + "badConfig-uniqueAddressIntegrationEndPoint", + "depositInfoGeneratorType set as API, but uniqueAddressIntegrationEndPoint not properly configured"); + } } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java new file mode 100644 index 0000000000..1a8fd14c1c --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java @@ -0,0 +1,33 @@ +package org.stellar.anchor.platform.service; + +import java.util.Objects; +import org.stellar.anchor.api.callback.GetUniqueAddressResponse; +import org.stellar.anchor.api.callback.UniqueAddressIntegration; +import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.api.sep.sep31.Sep31DepositInfo; +import org.stellar.anchor.sep31.Sep31DepositInfoGenerator; +import org.stellar.anchor.sep31.Sep31Transaction; +import org.stellar.anchor.util.MemoHelper; + +public class Sep31DepositInfoGeneratorApi implements Sep31DepositInfoGenerator { + private UniqueAddressIntegration uniqueAddressIntegration; + + public Sep31DepositInfoGeneratorApi(UniqueAddressIntegration uniqueAddressIntegration) { + this.uniqueAddressIntegration = uniqueAddressIntegration; + } + + @Override + public Sep31DepositInfo generate(Sep31Transaction txn) throws AnchorException { + GetUniqueAddressResponse response = uniqueAddressIntegration.getUniqueAddress(txn.getId()); + GetUniqueAddressResponse.UniqueAddress uniqueAddress = response.getUniqueAddress(); + + if (!Objects.toString(uniqueAddress.getMemo(), "").isEmpty() + && !Objects.toString(uniqueAddress.getMemoType(), "").isEmpty()) { + // Check the validity of the returned memo and memo type + MemoHelper.makeMemo(uniqueAddress.getMemo(), uniqueAddress.getMemoType()); + } + + return new Sep31DepositInfo( + uniqueAddress.getStellarAddress(), uniqueAddress.getMemo(), uniqueAddress.getMemoType()); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java index 2dd7482519..91e8d38cf1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java @@ -17,7 +17,7 @@ public Sep31DepositInfoGeneratorCircle(CirclePaymentService circlePaymentService } @Override - public Sep31DepositInfo getSep31DepositInfo(Sep31Transaction txn) { + public Sep31DepositInfo generate(Sep31Transaction txn) { return circlePaymentService .getDistributionAccountAddress() .flatMap(circlePaymentService::createNewStellarAddress) diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorSelf.java b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorSelf.java index 35833af879..a8b4586486 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorSelf.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorSelf.java @@ -11,7 +11,7 @@ public class Sep31DepositInfoGeneratorSelf implements Sep31DepositInfoGenerator { @Override - public Sep31DepositInfo getSep31DepositInfo(Sep31Transaction txn) { + public Sep31DepositInfo generate(Sep31Transaction txn) { String memo = StringUtils.truncate(txn.getId(), 32); memo = StringUtils.leftPad(memo, 32, '0'); memo = new String(Base64.getEncoder().encode(memo.getBytes())); diff --git a/platform/src/main/resources/anchor-config-defaults.yaml b/platform/src/main/resources/anchor-config-defaults.yaml index 386d08c029..67e0d16c9e 100644 --- a/platform/src/main/resources/anchor-config-defaults.yaml +++ b/platform/src/main/resources/anchor-config-defaults.yaml @@ -76,6 +76,9 @@ app-config: # The secret key of JWT encryption jwtSecretKey: ${JWT_SECRET} + anchor-callback: + endpoint: http://localhost:8081 + # These are secrets shared between Anchor and Platform that are used to safely communicate from `Platform->Anchor` # and `Anchor->Platform`, specially when they are in different clusters. # @@ -174,9 +177,12 @@ app-config: sep31: enabled: true - # The callback API endpoint. + # The /fee callback API endpoint. feeIntegrationEndPoint: http://localhost:8081 + # The /unique_address callback API endpoint. + uniqueAddressIntegrationEndPoint: http://localhost:8081 + # # paymentType: used to determine how amount_in is calculated from amount in the POST /transaction call # Possible values: STRICT_SEND or STRICT_RECEIVE. default=STRICT_SEND @@ -190,7 +196,8 @@ app-config: # Possible values: # self: the memo and memo type are generated in the local code, and the distribution account is used for the deposit address. # circle: the memo and memo type are generated through Circle API, as well as the deposit address. - depositInfoGeneratorType: self # self or circle + # api: the memo and memo type are generated through calling the anchor's GET /unique_address endpoint. + depositInfoGeneratorType: self # self or circle or api # sep-38 sep38: diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt new file mode 100644 index 0000000000..f112a8e8ab --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt @@ -0,0 +1,91 @@ +package org.stellar.anchor.platform.service + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import java.util.* +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +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.stellar.anchor.api.callback.GetUniqueAddressResponse +import org.stellar.anchor.api.callback.UniqueAddressIntegration +import org.stellar.anchor.api.exception.HttpException +import org.stellar.anchor.sep31.Sep31Transaction + +class Sep31DepositInfoGeneratorApiTest { + @MockK(relaxed = true) private lateinit var uniqueAddressIntegration: UniqueAddressIntegration + @MockK(relaxed = true) private lateinit var txn: Sep31Transaction + val txnId = "this_is_a_transaction_id" + val stellarAddress = "GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2" + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + } + + @AfterEach + fun teardown() { + clearAllMocks() + unmockkAll() + } + + @ParameterizedTest + @CsvSource( + value = [",", "123,id", "ABCD,text", "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=,hash"] + ) + fun test_generate(memo: String?, memoType: String?) { + val uniqueAddress = + GetUniqueAddressResponse.UniqueAddress.builder() + .stellarAddress(stellarAddress) + .memo(memo) + .memoType(memoType) + .build() + val uniqueAddressResponse = + GetUniqueAddressResponse.builder().uniqueAddress(uniqueAddress).build() + + every { txn.getId() } returns txnId + every { uniqueAddressIntegration.getUniqueAddress(any()) } returns uniqueAddressResponse + + val generator = Sep31DepositInfoGeneratorApi(uniqueAddressIntegration) + val depositInfo = generator.generate(txn) + assertEquals(stellarAddress, depositInfo.stellarAddress) + assertEquals(memo, depositInfo.memo) + assertEquals(memoType, depositInfo.memoType) + } + + @ParameterizedTest + @CsvSource(value = ["A123,id", "=,hash"]) + fun test_generate_error(memo: String?, memoType: String?) { + val uniqueAddress = + GetUniqueAddressResponse.UniqueAddress.builder() + .stellarAddress(stellarAddress) + .memo(memo) + .memoType(memoType) + .build() + val uniqueAddressResponse = + GetUniqueAddressResponse.builder().uniqueAddress(uniqueAddress).build() + + every { txn.getId() } returns txnId + every { uniqueAddressIntegration.getUniqueAddress(any()) } returns uniqueAddressResponse + + val generator = Sep31DepositInfoGeneratorApi(uniqueAddressIntegration) + assertThrows { generator.generate(txn) } + } + + @ParameterizedTest + @ValueSource(ints = [500, 501]) + fun test_generate_integration_exception(statusCode: Int) { + every { txn.getId() } returns txnId + every { uniqueAddressIntegration.getUniqueAddress(any()) } throws HttpException(statusCode) + + val generator = Sep31DepositInfoGeneratorApi(uniqueAddressIntegration) + val ex = assertThrows { generator.generate(txn) } + assertEquals(statusCode, ex.statusCode) + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/Sep31DepositInfoGeneratorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt similarity index 79% rename from platform/src/test/kotlin/org/stellar/anchor/platform/Sep31DepositInfoGeneratorTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt index 14b4beb4fa..dde05daf74 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -1,9 +1,8 @@ -package org.stellar.anchor.platform +package org.stellar.anchor.sep31 import com.google.gson.Gson import io.mockk.* import io.mockk.impl.annotations.MockK -import java.lang.reflect.Method import java.util.* import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse @@ -11,8 +10,11 @@ import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest import org.apache.commons.lang3.StringUtils import org.junit.jupiter.api.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.stellar.anchor.api.callback.CustomerIntegration import org.stellar.anchor.api.callback.FeeIntegration +import org.stellar.anchor.api.sep.sep31.Sep31DepositInfo import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig @@ -25,7 +27,6 @@ import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorCircle import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf -import org.stellar.anchor.sep31.* import org.stellar.anchor.sep38.Sep38QuoteStore import org.stellar.anchor.util.GsonUtils import org.stellar.sdk.Server @@ -117,10 +118,8 @@ class Sep31DepositInfoGeneratorTest { wantMemo = String(Base64.getEncoder().encode(wantMemo.toByteArray())) Assertions.assertEquals("YTIzOTJhZGQtODdjOS00MmYwLWE1YzEtNWYxNzI4MDM=", wantMemo) - val updateDepositInfoMethod: Method = - Sep31Service::class.java.getDeclaredMethod("updateDepositInfo", Sep31Transaction::class.java) - assert(updateDepositInfoMethod.trySetAccessible()) - assertDoesNotThrow { updateDepositInfoMethod.invoke(sep31Service, txn) } + Sep31Service.Context.get().transaction = txn + assertDoesNotThrow { sep31Service.updateDepositInfo() } Assertions.assertEquals( "GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY", @@ -196,10 +195,8 @@ class Sep31DepositInfoGeneratorTest { Assertions.assertNull(txn.stellarMemoType) Assertions.assertNull(txn.stellarMemo) - val updateDepositInfoMethod: Method = - Sep31Service::class.java.getDeclaredMethod("updateDepositInfo", Sep31Transaction::class.java) - assert(updateDepositInfoMethod.trySetAccessible()) - assertDoesNotThrow { updateDepositInfoMethod.invoke(sep31Service, txn) } + Sep31Service.Context.get().transaction = txn + assertDoesNotThrow { sep31Service.updateDepositInfo() } Assertions.assertEquals( "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", @@ -208,4 +205,41 @@ class Sep31DepositInfoGeneratorTest { Assertions.assertEquals("text", txn.stellarMemoType) Assertions.assertEquals("2454278437550473431", txn.stellarMemo) } + + @ParameterizedTest + @CsvSource( + value = [",", "YTIzOTJhZGQtODdjOS00MmYwLWE1YzEtNWYxNzI4MDM=,hash", "123,id", "John Doe,text"] + ) + fun test_updateDepositInfo_api(memo: String?, memoType: String?) { + val nonEmptyMemo = Objects.toString(memo, "") + val nonEmptyMemoType = Objects.toString(memoType, "") + every { sep31DepositInfoGenerator.generate(any()) } returns + Sep31DepositInfo( + "GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY", + nonEmptyMemo, + nonEmptyMemoType + ) + + Assertions.assertEquals("a2392add-87c9-42f0-a5c1-5f1728030b68", txn.id) + Assertions.assertEquals( + "GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY", + txn.stellarAccountId + ) + Assertions.assertNull(txn.stellarMemoType) + Assertions.assertNull(txn.stellarMemo) + + var wantMemo = StringUtils.truncate("a2392add-87c9-42f0-a5c1-5f1728030b68", 32) + wantMemo = String(Base64.getEncoder().encode(wantMemo.toByteArray())) + Assertions.assertEquals("YTIzOTJhZGQtODdjOS00MmYwLWE1YzEtNWYxNzI4MDM=", wantMemo) + + Sep31Service.Context.get().transaction = txn + assertDoesNotThrow { sep31Service.updateDepositInfo() } + + Assertions.assertEquals( + "GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY", + txn.stellarAccountId + ) + Assertions.assertEquals(nonEmptyMemo, txn.stellarMemo) + Assertions.assertEquals(nonEmptyMemoType, txn.stellarMemoType) + } } From 225657765af178cc265a6e3ecf6b5e59a9ab3c28 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Fri, 29 Jul 2022 13:30:54 -0300 Subject: [PATCH 02/65] hotfix: solve issue where `uniqueAddressService` was crashing (#444) ### What Create annotation `ConditionalOnPropertyNotEmpty` and add it to the `UniqueAddressController` and `UniqueAddressService`, so they are only instantiated when the `anchor.settings.distributionWallet` property is not empty. ### Why Close #443 --- .../controller/UniqueAddressController.java | 5 ++- .../service/UniqueAddressService.java | 2 ++ .../util/ConditionalOnPropertyNotEmpty.java | 34 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/org/stellar/anchor/util/ConditionalOnPropertyNotEmpty.java diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java index 639d7a3291..6d38d5ae9e 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java @@ -1,15 +1,14 @@ package org.stellar.anchor.reference.controller; -import com.google.gson.Gson; import org.springframework.web.bind.annotation.*; import org.stellar.anchor.api.callback.GetUniqueAddressResponse; import org.stellar.anchor.reference.service.UniqueAddressService; -import org.stellar.anchor.util.GsonUtils; +import org.stellar.anchor.util.ConditionalOnPropertyNotEmpty; @RestController +@ConditionalOnPropertyNotEmpty("anchor.settings.distributionWallet") public class UniqueAddressController { final UniqueAddressService uniqueAddressService; - static final Gson gson = GsonUtils.builder().create(); public UniqueAddressController(UniqueAddressService uniqueAddressService) { this.uniqueAddressService = uniqueAddressService; diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java index 39a04d1f82..7e3717d699 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java @@ -5,10 +5,12 @@ import org.stellar.anchor.api.callback.GetUniqueAddressResponse; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.reference.config.AppSettings; +import org.stellar.anchor.util.ConditionalOnPropertyNotEmpty; import org.stellar.anchor.util.MemoHelper; import org.stellar.sdk.KeyPair; @Service +@ConditionalOnPropertyNotEmpty("anchor.settings.distributionWallet") public class UniqueAddressService { String distributionWallet; String distributionWalletMemo; diff --git a/core/src/main/java/org/stellar/anchor/util/ConditionalOnPropertyNotEmpty.java b/core/src/main/java/org/stellar/anchor/util/ConditionalOnPropertyNotEmpty.java new file mode 100644 index 0000000000..cb1eda2b6a --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/util/ConditionalOnPropertyNotEmpty.java @@ -0,0 +1,34 @@ +package org.stellar.anchor.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.type.AnnotatedTypeMetadata; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Conditional(ConditionalOnPropertyNotEmpty.OnPropertyNotEmptyCondition.class) +public @interface ConditionalOnPropertyNotEmpty { + String value(); + + class OnPropertyNotEmptyCondition implements Condition { + + @Override + public boolean matches(@NotNull ConditionContext context, AnnotatedTypeMetadata metadata) { + Map attrs = + metadata.getAnnotationAttributes(ConditionalOnPropertyNotEmpty.class.getName()); + if (attrs == null) { + return false; + } + String propertyName = (String) attrs.get("value"); + String val = context.getEnvironment().getProperty(propertyName); + return val != null && !val.trim().isEmpty(); + } + } +} From 9f9bfd8c63fd4cac9f7e95f76aebf539e62eea6b Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Fri, 29 Jul 2022 17:29:20 -0300 Subject: [PATCH 03/65] enhancement & fixes: update the code in preparation for handling SEP-31 non-happy-paths caused by `PENDING_CUSTOMER_INFO_UPDATE` (#440) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What Update the code in preparation for handling SEP-31 non-happy-paths caused by `PENDING_CUSTOMER_INFO_UPDATE`. The changes include updates in SEP-31-related classes. The most relevant changes are: * **Update SEP-31 sequence diagram to preview the non-happy-path involving `pending_customer_info_update`** (not covering the implementation yet) * Stop supporting `PENDING_TRANSACTION_INFO_UPDATE` in the TransactionEvent, to be compliant with the documentation. * Add missing SEP-31 status `EXPIRED`. * Fix PlatformAPI non-compliant field names. * Add new check to SEP-12 to make sure the user status is ACCEPTED before proceeding with the SEP-31 tx creation. * Fix Reference server’s `CustomerService` that was ignoring some customer info depending on the customer type. Additionally, included some refactoring: * Updates/fixes in the `Sep31Transaction` interface: * Add fields that were missing in the `Sep31Transaction` interface, but were present in the `JdbcSep31Transaction` class. * Move `Sep31Transaction.Refunds` and `Sep31Transaction.RefundPayments` to their own separate file. * Updates in the `TransactionService`: * Rely on the `Sep31Transaction` interface instead of the `JdbcSep31Transaction` class. * Start supporting almost all fields * Add `TransactionServiceTest` class to test unit testing TransactionService (still only covering the GET request). * Move `Sep31CustomerInfoNeededException` and `Sep31MissingFieldException` out of the Sep31Service class into their own file. * Remove duplicated classes and move them to a shared folder: StellarId, Customers, Refund, RefundPayment. * Add documentation to some methods in Sep31Service. ### Why We'll need to rely on the PlatformAPI for the non-happy path and I had to make a bigger update to make sure it is compliant and ready for the new flow. --- .../reference/controller/RateController.java | 3 +- .../reference/model/StellarIdConverter.java | 2 +- .../reference/service/CustomerService.java | 18 +- .../Sep31CustomerInfoNeededException.java | 13 + .../exception/Sep31MissingFieldException.java | 16 ++ .../api/platform/GetTransactionResponse.java | 29 +-- .../api/platform/PatchTransactionRequest.java | 3 +- .../stellar/anchor/api/platform/Refunds.java | 18 -- .../org/stellar/anchor/api/sep/AssetInfo.java | 4 + .../anchor/api/sep/SepTransactionStatus.java | 8 +- .../sep31/Sep31GetTransactionResponse.java | 8 +- .../stellar/anchor/api/shared}/Customers.java | 2 +- .../stellar/anchor/api/shared}/Refund.java | 3 +- .../anchor/api/shared}/RefundPayment.java | 3 +- .../stellar/anchor/api/shared}/StellarId.java | 2 +- .../anchor/event/models/QuoteEvent.java | 1 + .../anchor/event/models/TransactionEvent.java | 5 +- .../stellar/anchor/sep31/RefundPayment.java | 15 ++ .../anchor/sep31/RefundPaymentBuilder.java | 4 +- .../org/stellar/anchor/sep31/Refunds.java | 18 ++ .../stellar/anchor/sep31/RefundsBuilder.java | 12 +- .../stellar/anchor/sep31/Sep31Service.java | 239 ++++++++++-------- .../anchor/sep31/Sep31Transaction.java | 98 +++++-- .../anchor/sep31/Sep31TransactionBuilder.java | 19 +- .../anchor/sep31/Sep31TransactionStore.java | 4 +- .../stellar/anchor/sep38/Sep38QuoteStore.java | 3 +- .../stellar/anchor/sep38/Sep38Service.java | 2 +- .../org/stellar/anchor/util/SepHelper.java | 1 + .../anchor/sep31/PojoSep31RefundPayment.java | 6 +- .../anchor/sep31/PojoSep31Refunds.java | 28 +- .../anchor/sep31/PojoSep31Transaction.java | 22 +- .../stellar/anchor/sep31/Sep31HelperTest.kt | 1 + .../stellar/anchor/sep31/Sep31ServiceTest.kt | 215 ++++++++++++---- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 2 +- docs/00 - Stellar Anchor Platform.md | 56 +++- .../Communication/Platform API.yml | 7 + .../org/stellar/anchor/platform/Sep12Tests.kt | 14 +- .../platform/controller/Sep31Controller.java | 4 +- .../platform/data/JdbcSep31RefundPayment.java | 4 +- .../platform/data/JdbcSep31Refunds.java | 7 +- .../platform/data/JdbcSep31Transaction.java | 3 +- .../data/JdbcSep31TransactionStore.java | 6 +- .../PaymentOperationToEventListener.java | 17 +- .../platform/service/TransactionService.java | 105 ++++++-- .../PaymentOperationToEventListenerTest.kt | 5 + .../service/TransactionServiceTest.kt | 204 +++++++++++++++ .../stellar-anchor-tests-sep-config.json | 5 +- 47 files changed, 929 insertions(+), 335 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31CustomerInfoNeededException.java create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31MissingFieldException.java delete mode 100644 api-schema/src/main/java/org/stellar/anchor/api/platform/Refunds.java rename {core/src/main/java/org/stellar/anchor/event/models => api-schema/src/main/java/org/stellar/anchor/api/shared}/Customers.java (81%) rename {core/src/main/java/org/stellar/anchor/event/models => api-schema/src/main/java/org/stellar/anchor/api/shared}/Refund.java (84%) rename {core/src/main/java/org/stellar/anchor/event/models => api-schema/src/main/java/org/stellar/anchor/api/shared}/RefundPayment.java (91%) rename {core/src/main/java/org/stellar/anchor/event/models => api-schema/src/main/java/org/stellar/anchor/api/shared}/StellarId.java (82%) create mode 100644 core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java create mode 100644 core/src/main/java/org/stellar/anchor/sep31/Refunds.java create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/RateController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/RateController.java index 3c916a2d3d..29d0a4e2fc 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/RateController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/RateController.java @@ -6,7 +6,6 @@ import org.stellar.anchor.api.callback.GetRateRequest; import org.stellar.anchor.api.callback.GetRateResponse; import org.stellar.anchor.api.exception.AnchorException; -import org.stellar.anchor.reference.config.AppSettings; import org.stellar.anchor.reference.service.RateService; import org.stellar.anchor.util.GsonUtils; @@ -15,7 +14,7 @@ public class RateController { private final RateService rateService; private static final Gson gson = GsonUtils.builder().create(); - public RateController(AppSettings appSettings, RateService rateService) { + public RateController(RateService rateService) { this.rateService = rateService; } diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/model/StellarIdConverter.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/model/StellarIdConverter.java index c33b35d0ed..0db89ca999 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/model/StellarIdConverter.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/model/StellarIdConverter.java @@ -3,7 +3,7 @@ import com.google.gson.Gson; import javax.persistence.AttributeConverter; import javax.persistence.Converter; -import org.stellar.anchor.event.models.StellarId; +import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.util.GsonUtils; @Converter diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java index 77c19b2177..a71d8f672a 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java @@ -162,16 +162,14 @@ private void updateCustomer(Customer customer, PutCustomerRequest request) { if (request.getEmailAddress() != null) { customer.setEmail(request.getEmailAddress()); } - if (Customer.Type.SEP31_RECEIVER.toString().equals(request.getType())) { - if (request.getBankAccountNumber() != null) { - customer.setBankAccountNumber(request.getBankAccountNumber()); - } - if (request.getBankNumber() != null) { - customer.setBankRoutingNumber(request.getBankNumber()); - } - if (request.getClabeNumber() != null) { - customer.setClabeNumber(request.getClabeNumber()); - } + if (request.getBankAccountNumber() != null) { + customer.setBankAccountNumber(request.getBankAccountNumber()); + } + if (request.getBankNumber() != null) { + customer.setBankRoutingNumber(request.getBankNumber()); + } + if (request.getClabeNumber() != null) { + customer.setClabeNumber(request.getClabeNumber()); } customerRepo.save(customer); } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31CustomerInfoNeededException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31CustomerInfoNeededException.java new file mode 100644 index 0000000000..9a594facbd --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31CustomerInfoNeededException.java @@ -0,0 +1,13 @@ +package org.stellar.anchor.api.exception; + +public class Sep31CustomerInfoNeededException extends AnchorException { + private final String type; + + public Sep31CustomerInfoNeededException(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31MissingFieldException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31MissingFieldException.java new file mode 100644 index 0000000000..ffe40c2592 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/Sep31MissingFieldException.java @@ -0,0 +1,16 @@ +package org.stellar.anchor.api.exception; + +import org.stellar.anchor.api.sep.AssetInfo; + +public class Sep31MissingFieldException extends AnchorException { + private final AssetInfo.Sep31TxnFieldSpecs missingFields; + + public Sep31MissingFieldException(AssetInfo.Sep31TxnFieldSpecs missingFields) { + super(); + this.missingFields = missingFields; + } + + public AssetInfo.Sep31TxnFieldSpecs getMissingFields() { + return missingFields; + } +} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java index 33ffc98381..2871377b14 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java @@ -7,6 +7,9 @@ import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.Customers; +import org.stellar.anchor.api.shared.Refund; +import org.stellar.anchor.api.shared.StellarId; @Data @SuperBuilder @@ -45,16 +48,16 @@ public class GetTransactionResponse { Instant transferReceivedAt; String message; - Refunds refunds; + Refund refunds; @SerializedName("stellar_transactions") List stellarTransactions; - @SerializedName("external_id") - String externalId; + @SerializedName("external_transaction_id") + String externalTransactionId; - @SerializedName("custodial_id") - String custodialId; + @SerializedName("custodial_transaction_id") + String custodialTransactionId; Customers customers; StellarId creator; @@ -87,20 +90,4 @@ public static class Payment { Amount amount; } - - @Data - public static class Customers { - StellarId sender; - StellarId receiver; - } - - @Data - public static class StellarId { - String id; - String account; - String memo; - - @SerializedName("memo_type") - String memoType; - } } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PatchTransactionRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PatchTransactionRequest.java index 3e3a774535..7c0a8b5c0e 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PatchTransactionRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PatchTransactionRequest.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Data; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.Refund; @Data @Builder @@ -25,7 +26,7 @@ public class PatchTransactionRequest { Instant transferReceivedAt; String message; - Refunds refunds; + Refund refunds; @SerializedName("external_transaction_id") String externalTransactionId; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/Refunds.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/Refunds.java deleted file mode 100644 index 39c45550a1..0000000000 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/Refunds.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.stellar.anchor.api.platform; - -import com.google.gson.annotations.SerializedName; -import java.time.Instant; -import lombok.Data; -import org.stellar.anchor.api.shared.Amount; - -@Data -public class Refunds { - String type; - Amount amount; - - @SerializedName("requested_at") - Instant requestedAt; - - @SerializedName("refunded_at") - Instant refundedAt; -} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/AssetInfo.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/AssetInfo.java index dd0910f975..709f258372 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/AssetInfo.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/AssetInfo.java @@ -3,6 +3,7 @@ import com.google.gson.annotations.SerializedName; import java.util.List; import java.util.Map; +import lombok.AllArgsConstructor; import lombok.Data; @SuppressWarnings("unused") @@ -133,10 +134,13 @@ public static class Sep31TxnFieldSpecs { } @Data + @AllArgsConstructor public static class Sep31TxnFieldSpec { String description; List choices; boolean optional; + + public Sep31TxnFieldSpec() {} } @Data diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/SepTransactionStatus.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/SepTransactionStatus.java index 6409cedfe4..33e26ae254 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/SepTransactionStatus.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/SepTransactionStatus.java @@ -14,11 +14,15 @@ public enum SepTransactionStatus { TOO_LARGE("too_large", "the transaction amount is too big"), PENDING_SENDER("pending_sender", null), PENDING_RECEIVER("pending_receiver", null), - PENDING_TRANSACTION_INFO_UPDATE("pending_transaction_info_update", null), + PENDING_TRANSACTION_INFO_UPDATE( + "pending_transaction_info_update", "waiting for more transaction information"), PENDING_CUSTOMER_INFO_UPDATE( - "pending_customer_info_update", "waiting for more transaction information"), + "pending_customer_info_update", "waiting for more customer information"), COMPLETED("completed", "complete"), REFUNDED("refunded", "the deposit/withdrawal is fully refunded"), + EXPIRED( + "expired", + " funds were never received by the anchor and the transaction is considered abandoned by the Sending Client. If a SEP-38 quote was specified when the transaction was initiated, the transaction should expire when the quote expires, otherwise anchors are responsible for determining when transactions are considered expired."), ERROR("error", "error"), PENDING_EXTERNAL("pending_external", "waiting on an external entity"), PENDING_STELLAR("pending_stellar", "stellar is executing the transaction"); 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 6fe966b5cb..577d4874de 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 @@ -3,12 +3,16 @@ import com.google.gson.annotations.SerializedName; import java.time.Instant; import java.util.List; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.stellar.anchor.api.sep.AssetInfo; @Data @Builder +@AllArgsConstructor +@NoArgsConstructor public class Sep31GetTransactionResponse { TransactionResponse transaction; @@ -71,6 +75,7 @@ public static class TransactionResponse { } @Data + @Builder public static class Refunds { @SerializedName("amount_refunded") String amountRefunded; @@ -82,8 +87,9 @@ public static class Refunds { } @Data + @Builder public static class Sep31RefundPayment { - String Id; + String id; String amount; String fee; } diff --git a/core/src/main/java/org/stellar/anchor/event/models/Customers.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java similarity index 81% rename from core/src/main/java/org/stellar/anchor/event/models/Customers.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java index 6852f62f69..072e7d4221 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/Customers.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.event.models; +package org.stellar.anchor.api.shared; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/core/src/main/java/org/stellar/anchor/event/models/Refund.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/Refund.java similarity index 84% rename from core/src/main/java/org/stellar/anchor/event/models/Refund.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/Refund.java index fcd4dcc5fb..99dc8fb8e6 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/Refund.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/Refund.java @@ -1,11 +1,10 @@ -package org.stellar.anchor.event.models; +package org.stellar.anchor.api.shared; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import org.stellar.anchor.api.shared.Amount; @Data @Builder diff --git a/core/src/main/java/org/stellar/anchor/event/models/RefundPayment.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/RefundPayment.java similarity index 91% rename from core/src/main/java/org/stellar/anchor/event/models/RefundPayment.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/RefundPayment.java index e2ea093134..522f7da502 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/RefundPayment.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/RefundPayment.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.event.models; +package org.stellar.anchor.api.shared; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.annotations.SerializedName; @@ -6,7 +6,6 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import org.stellar.anchor.api.shared.Amount; @Data @Builder diff --git a/core/src/main/java/org/stellar/anchor/event/models/StellarId.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarId.java similarity index 82% rename from core/src/main/java/org/stellar/anchor/event/models/StellarId.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/StellarId.java index 1ab5e231e8..4ccbedabc5 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/StellarId.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarId.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.event.models; +package org.stellar.anchor.api.shared; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/core/src/main/java/org/stellar/anchor/event/models/QuoteEvent.java b/core/src/main/java/org/stellar/anchor/event/models/QuoteEvent.java index 0b57a7e593..b909be78d1 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/QuoteEvent.java +++ b/core/src/main/java/org/stellar/anchor/event/models/QuoteEvent.java @@ -8,6 +8,7 @@ import lombok.Builder; import lombok.Data; import org.stellar.anchor.api.sep.sep38.RateFee; +import org.stellar.anchor.api.shared.StellarId; @Data @Builder diff --git a/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java b/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java index 22fe0143dd..78ba746dcf 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java +++ b/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java @@ -9,6 +9,9 @@ import lombok.Builder; import lombok.Data; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.Customers; +import org.stellar.anchor.api.shared.Refund; +import org.stellar.anchor.api.shared.StellarId; @Data @Builder @@ -104,10 +107,10 @@ public enum Status { PENDING_SENDER("pending_sender"), PENDING_STELLAR("pending_stellar"), PENDING_CUSTOMER_INFO_UPDATE("pending_customer_info_update"), - PENDING_TRANSACTION_INFO_UPDATE("pending_transaction_info_update"), PENDING_RECEIVER("pending_receiver"), PENDING_EXTERNAL("pending_external"), COMPLETED("completed"), + EXPIRED("expired"), ERROR("error"); @JsonValue public final String status; diff --git a/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java b/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java new file mode 100644 index 0000000000..ff410774b4 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java @@ -0,0 +1,15 @@ +package org.stellar.anchor.sep31; + +public interface RefundPayment { + String getId(); + + void setId(String id); + + String getAmount(); + + void setAmount(String amount); + + String getFee(); + + void setFee(String fee); +} diff --git a/core/src/main/java/org/stellar/anchor/sep31/RefundPaymentBuilder.java b/core/src/main/java/org/stellar/anchor/sep31/RefundPaymentBuilder.java index 94a4b38f23..c67324898e 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/RefundPaymentBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/RefundPaymentBuilder.java @@ -1,9 +1,7 @@ package org.stellar.anchor.sep31; -import org.stellar.anchor.sep31.Sep31Transaction.RefundPayment; - public class RefundPaymentBuilder { - RefundPayment refundPayment; + private final RefundPayment refundPayment; public RefundPaymentBuilder(Sep31TransactionStore factory) { refundPayment = factory.newRefundPayment(); diff --git a/core/src/main/java/org/stellar/anchor/sep31/Refunds.java b/core/src/main/java/org/stellar/anchor/sep31/Refunds.java new file mode 100644 index 0000000000..a24b3382b7 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/sep31/Refunds.java @@ -0,0 +1,18 @@ +package org.stellar.anchor.sep31; + +import java.util.List; + +public interface Refunds { + + String getAmountRefunded(); + + void setAmountRefunded(String amountRefunded); + + String getAmountFee(); + + void setAmountFee(String amountFee); + + List getRefundPayments(); + + void setRefundPayments(List refundPayments); +} diff --git a/core/src/main/java/org/stellar/anchor/sep31/RefundsBuilder.java b/core/src/main/java/org/stellar/anchor/sep31/RefundsBuilder.java index 063ce638ac..2980a238b7 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/RefundsBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/RefundsBuilder.java @@ -3,28 +3,28 @@ import java.util.List; public class RefundsBuilder { - Sep31Transaction.Refunds refunds; + private final Refunds refunds; - RefundsBuilder(Sep31TransactionStore factory) { + public RefundsBuilder(Sep31TransactionStore factory) { refunds = factory.newRefunds(); } - RefundsBuilder amountRefunded(String amountRefunded) { + public RefundsBuilder amountRefunded(String amountRefunded) { refunds.setAmountRefunded(amountRefunded); return this; } - RefundsBuilder amountFee(String amountFee) { + public RefundsBuilder amountFee(String amountFee) { refunds.setAmountFee(amountFee); return this; } - RefundsBuilder payments(List payments) { + public RefundsBuilder payments(List payments) { refunds.setRefundPayments(payments); return this; } - Sep31Transaction.Refunds build() { + public Refunds build() { return refunds; } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index 4222357736..a6df823470 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -25,16 +25,16 @@ import org.stellar.anchor.api.sep.SepTransactionStatus; import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerRequest; import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerResponse; +import org.stellar.anchor.api.sep.sep12.Sep12Status; import org.stellar.anchor.api.sep.sep31.*; -import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse.TransactionResponse; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.Customers; +import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.Sep31Config; import org.stellar.anchor.event.EventPublishService; -import org.stellar.anchor.event.models.Customers; -import org.stellar.anchor.event.models.StellarId; import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.sep38.Sep38Quote; import org.stellar.anchor.sep38.Sep38QuoteStore; @@ -73,7 +73,7 @@ public Sep31Service( this.feeIntegration = feeIntegration; this.customerIntegration = customerIntegration; this.eventService = eventService; - this.infoResponse = createFromAssets(assetService.listAllAssets()); + this.infoResponse = sep31InfoResponseFromAssetInfoList(assetService.listAllAssets()); Log.info("Sep31Service initialized."); } @@ -96,6 +96,7 @@ public Sep31PostTransactionResponse postTransaction( String.format( "asset %s:%s is not supported.", request.getAssetCode(), request.getAssetIssuer())); } + Context.get().setAsset(assetInfo); // Pre-validation validateAmount(request.getAmount()); @@ -105,16 +106,24 @@ public Sep31PostTransactionResponse postTransaction( assetInfo.getSend().getMinAmount(), assetInfo.getSend().getMaxAmount()); validateLanguage(appConfig, request.getLang()); + + /* + * TODO: + * - conclude if we can drop the usage of `fields`. + * TODO: if we can't stop using fields, we should: + * - check if `fields` are needed. If not, ignore this part of the code + * - make sure fields are not getting stored in the database + * - make sure fields are being forwarded in the TransactionEvent + */ if (request.getFields() == null) { infoF( "POST /transaction with id ({}) cannot have empty `fields`", jwtToken.getTransactionId()); throw new BadRequestException("'fields' field cannot be empty"); } - - Context.get().setAsset(assetInfo); Context.get().setTransactionFields(request.getFields().getTransaction()); - validateRequiredFields(); + + // Validation that execute HTTP requests validateSenderAndReceiver(); preValidateQuote(); @@ -127,7 +136,6 @@ public Sep31PostTransactionResponse postTransaction( .account(Objects.requireNonNullElse(jwtToken.getMuxedAccount(), jwtToken.getAccount())) .build(); - AssetInfo asset = Context.get().getAsset(); Amount fee = Context.get().getFee(); Sep31Transaction txn = new Sep31TransactionBuilder(sep31TransactionStore) @@ -151,12 +159,13 @@ public Sep31PostTransactionResponse postTransaction( .receiverId(Context.get().getRequest().getReceiverId()) .creator(creatorStellarId) // updateAmounts will update these ⬇️ + .amountExpected(request.getAmount()) .amountIn(request.getAmount()) .amountInAsset(assetInfo.getAssetName()) .amountOut(null) .amountOutAsset(null) // updateDepositInfo will update these ⬇️ - .stellarAccountId(asset.getDistributionAccount()) + .stellarAccountId(assetInfo.getDistributionAccount()) .stellarMemo(null) .stellarMemoType(null) .build(); @@ -181,7 +190,7 @@ public Sep31PostTransactionResponse postTransaction( .status(TransactionEvent.Status.PENDING_SENDER) .statusChange( new TransactionEvent.StatusChange(null, TransactionEvent.Status.PENDING_SENDER)) - .amountExpected(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) + .amountExpected(new Amount(txn.getAmountExpected(), txn.getAmountInAsset())) .amountIn(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) .amountOut(new Amount(txn.getAmountOut(), txn.getAmountOutAsset())) .amountFee(new Amount(txn.getAmountFee(), txn.getAmountFeeAsset())) @@ -210,6 +219,12 @@ public Sep31PostTransactionResponse postTransaction( .build(); } + /** + * Will update the amountIn, amountOut and amountFee, as well as the assets, taking into account + * if quotes or if the {callbackApi}/fee endpoint was used. + * + * @throws AnchorException is something went wrong. + */ void updateAmounts() throws AnchorException { Sep31PostTransactionRequest request = Context.get().getRequest(); if (request.getQuoteId() != null) { @@ -219,7 +234,12 @@ void updateAmounts() throws AnchorException { updateTxAmountsWhenNoQuoteWasUsed(); } - void updateTxAmountsBasedOnQuote() throws AnchorException { + /** + * updateTxAmountsBasedOnQuote will update the amountIn, amountOut and fee based on the quote. + * + * @throws ServerErrorException if the quote object is missing + */ + void updateTxAmountsBasedOnQuote() throws ServerErrorException { Sep38Quote quote = Context.get().getQuote(); if (quote == null) { infoF("Quote for transaction ({}) not found", Context.get().getTransaction().getId()); @@ -230,12 +250,17 @@ void updateTxAmountsBasedOnQuote() throws AnchorException { debugF("Updating transaction ({}) with quote ({})", txn.getId(), quote.getId()); txn.setAmountInAsset(quote.getSellAsset()); txn.setAmountIn(quote.getSellAmount()); + txn.setAmountExpected(quote.getSellAmount()); txn.setAmountOutAsset(quote.getBuyAsset()); txn.setAmountOut(quote.getBuyAmount()); txn.setAmountFee(quote.getFee().getTotal()); txn.setAmountFeeAsset(quote.getFee().getAsset()); } + /** + * updateTxAmountsWhenNoQuoteWasUsed will update the transaction amountIn and amountOut based on + * the request amount and the fee. + */ void updateTxAmountsWhenNoQuoteWasUsed() { Sep31PostTransactionRequest request = Context.get().getRequest(); Sep31Transaction txn = Context.get().getTransaction(); @@ -264,6 +289,7 @@ void updateTxAmountsWhenNoQuoteWasUsed() { // Update transaction txn.setAmountIn(formatAmount(amountIn, scale)); + txn.setAmountExpected(formatAmount(amountIn, scale)); txn.setAmountInAsset(reqAsset.getAssetName()); txn.setAmountOut(formatAmount(amountOut, scale)); txn.setAmountOutAsset(reqAsset.getAssetName()); @@ -275,6 +301,10 @@ void updateTxAmountsWhenNoQuoteWasUsed() { Context.get().getFee().setAmount(feeStr); } + /** + * updateDepositInfo will populate the transaction's deposit information (stellar_account_id, memo + * and memo_type), as provided by the sep31DepositInfoGenerator. + */ void updateDepositInfo() throws AnchorException { Sep31Transaction txn = Context.get().getTransaction(); Sep31DepositInfo depositInfo = sep31DepositInfoGenerator.generate(txn); @@ -296,7 +326,7 @@ public Sep31GetTransactionResponse getTransaction(String id) throws AnchorExcept throw new NotFoundException(String.format("transaction (id=%s) not found", id)); } - return fromTransactionToResponse(txn); + return txn.toSep31GetTransactionResponse(); } @Transactional(rollbackOn = {AnchorException.class, RuntimeException.class}) @@ -309,18 +339,17 @@ public Sep31GetTransactionResponse patchTransaction(Sep31PatchTransactionRequest if (Objects.toString(request.getId(), "").isEmpty()) { infoF("id cannot be null or empty"); - throw new BadRequestException("id cannot be null or empty"); + throw new BadRequestException("id cannot be null nor empty"); } Context.reset(); Sep31Transaction txn = sep31TransactionStore.findByTransactionId(request.getId()); - Context.get().setTransaction(txn); - if (txn == null) { infoF("Transaction ({}) not found", request.getId()); throw new NotFoundException(String.format("transaction (id=%s) not found", request.getId())); } + Context.get().setTransaction(txn); // validate if the transaction is in the pending_transaction_info_update status if (!Objects.equals( @@ -343,10 +372,19 @@ public Sep31GetTransactionResponse patchTransaction(Sep31PatchTransactionRequest validateRequiredFields(); Sep31Transaction savedTxn = sep31TransactionStore.save(txn); - - return fromTransactionToResponse(savedTxn); + return savedTxn.toSep31GetTransactionResponse(); } + /** + * validatePatchTransactionFields will validate if the fields provided in the PATCH request are + * expected by the transaction. + * + * @param txn is the Sep31Transaction already stored in the database. + * @param request is the Sep31PatchTransactionRequest request + * @throws BadRequestException if the stored request is not expecting any info update. + * @throws BadRequestException if one of the provided fields is not being expected by the stored + * transaction. + */ void validatePatchTransactionFields(Sep31Transaction txn, Sep31PatchTransactionRequest request) throws BadRequestException { if (txn.getRequiredInfoUpdates() == null @@ -355,23 +393,35 @@ void validatePatchTransactionFields(Sep31Transaction txn, Sep31PatchTransactionR throw new BadRequestException( String.format("Transaction (%s) is not expecting any updates", txn.getId())); } + Map expectedFields = txn.getRequiredInfoUpdates().getTransaction(); - Map fields = request.getFields().getTransaction(); - // validate if any of the fields from the request is not expected in the transaction. - List unexpectedFields = - fields.keySet().stream() - .filter(key -> !expectedFields.containsKey(key)) - .collect(Collectors.toList()); + Map requestFields = request.getFields().getTransaction(); - if (unexpectedFields.size() > 0) { - infoF("{} is not a expected field", unexpectedFields.get(0)); - throw new BadRequestException( - String.format("[%s] is not a expected field", unexpectedFields.get(0))); + // validate if any of the fields from the request is not expected in the transaction. + for (String fieldName : requestFields.keySet()) { + if (!expectedFields.containsKey(fieldName)) { + infoF("{} is not a expected field", fieldName); + throw new BadRequestException(String.format("[%s] is not a expected field", fieldName)); + } } } - void preValidateQuote() throws AnchorException { + /** + * preValidateQuote will validate if the requested asset supports/requires quotes. + * + *

If quotes are supported and a `quote_id` was provided, this method will: - fetch the quote + * using the callbackAPI. - validate if the quote is valid. - validate if the transaction fields + * are compliant with the quote fields. - update the Context with the quote data. + * + * @throws BadRequestException if quotes are required but none was used in the request. + * @throws BadRequestException if a quote with the provided id could not be found. + * @throws BadRequestException if the transaction `amount` is different from the quote + * `sell_amount`. + * @throws BadRequestException if the transaction `asset` is different from the quote + * `sell_asset`. + */ + void preValidateQuote() throws BadRequestException { Sep31PostTransactionRequest request = Context.get().getRequest(); AssetInfo assetInfo = Context.get().getAsset(); boolean isQuotesRequired = assetInfo.getSep31().isQuotesRequired(); @@ -407,8 +457,7 @@ void preValidateQuote() throws AnchorException { } // Check quote asset: `post_transaction.asset == quote.sell_asset` - String assetName = - assetService.getAsset(request.getAssetCode(), request.getAssetIssuer()).getAssetName(); + String assetName = Context.get().getAsset().getAssetName(); if (!assetName.equals(quote.getSellAsset())) { infoF( "Quote ({}) - sellAsset ({}) is different from the SEP-31 transaction asset ({})", @@ -424,7 +473,15 @@ void preValidateQuote() throws AnchorException { Context.get().setQuote(quote); } - void updateFee() throws AnchorException { + /** + * updateFee will update the transaction fee. If a quote was used, it will get the quote info and + * use the quote fees for it, otherwise it will call `GET {callbackAPI}/fee` to get the fee + * information + * + * @throws SepValidationException if the quote is missing the `fee` field. + * @throws AnchorException if something else goes wrong. + */ + void updateFee() throws SepValidationException, AnchorException { Sep38Quote quote = Context.get().getQuote(); if (quote != null) { if (quote.getFee() == null) { @@ -438,8 +495,7 @@ void updateFee() throws AnchorException { Sep31PostTransactionRequest request = Context.get().getRequest(); JwtToken token = Context.get().getJwtToken(); - String assetName = - assetService.getAsset(request.getAssetCode(), request.getAssetIssuer()).getAssetName(); + String assetName = Context.get().getAsset().getAssetName(); infoF("Requesting fee for request ({})", request); Amount fee = feeIntegration @@ -461,7 +517,17 @@ void updateFee() throws AnchorException { Context.get().setFee(fee); } - void validateSenderAndReceiver() throws AnchorException { + /** + * validateSenderAndReceiver will validate if the SEP-31 sender and receiver exist and their + * status is ACCEPTED. + * + * @throws BadRequestException if `sender_id` or `receiver_id` is empty. + * @throws Sep31CustomerInfoNeededException if the SEP-12 customer does not exist or if its status + * is not ACCEPTED. + * @throws AnchorException is something else went wrong. + */ + private void validateSenderAndReceiver() + throws AnchorException, BadRequestException, Sep31CustomerInfoNeededException { String receiverId = Context.get().getRequest().getReceiverId(); if (receiverId == null) { infoF("'receiver_id' cannot be empty for request ({})", Context.get().getRequest()); @@ -480,7 +546,7 @@ void validateSenderAndReceiver() throws AnchorException { Sep12GetCustomerRequest request = Sep12GetCustomerRequest.builder().id(receiverId).type(receiverType).build(); Sep12GetCustomerResponse receiver = this.customerIntegration.getCustomer(request); - if (receiver == null) { + if (receiver == null || receiver.getStatus() != Sep12Status.ACCEPTED) { infoF("Customer (receiver) info needed for request ({})", Context.get().getRequest()); throw new Sep31CustomerInfoNeededException("sep31-receiver"); } @@ -502,22 +568,23 @@ void validateSenderAndReceiver() throws AnchorException { } request = Sep12GetCustomerRequest.builder().id(senderId).type(senderType).build(); Sep12GetCustomerResponse sender = this.customerIntegration.getCustomer(request); - if (sender == null) { + if (sender == null || sender.getStatus() != Sep12Status.ACCEPTED) { infoF("Customer (sender) info needed for request ({})", Context.get().getRequest()); throw new Sep31CustomerInfoNeededException("sep31-sender"); } } - void validateRequiredFields() throws AnchorException { + /** + * validateRequiredFields will validate if the fields provided in the `POST /transactions` or + * `PATCH /transactions/{id}` request body contains all the fields expected by the Anchor, and + * pre-configured in the `app-config.app.assets`. + * + * @throws BadRequestException if the asset is invalid or id the fields are missing from the + * request + * @throws Sep31MissingFieldException if not all fields were provided. + */ + void validateRequiredFields() throws BadRequestException, Sep31MissingFieldException { AssetInfo assetInfo = Context.get().getAsset(); - Map fields = Context.get().getTransactionFields(); - if (fields == null) { - infoF( - "'fields' field must have one 'transaction' field for request ({})", - Context.get().getRequest()); - throw new BadRequestException("'fields' field must have one 'transaction' field"); - } - if (assetInfo == null) { infoF("Missing asset information for request ({})", Context.get().getRequest()); throw new BadRequestException("Missing asset information."); @@ -530,13 +597,21 @@ void validateRequiredFields() throws AnchorException { String.format("Asset [%s] has no fields definition", assetInfo.getCode())); } + Map requestFields = Context.get().getTransactionFields(); + if (requestFields == null) { + infoF( + "'fields' field must have one 'transaction' field for request ({})", + Context.get().getRequest()); + throw new BadRequestException("'fields' field must have one 'transaction' field"); + } + Map missingFields = fieldSpecs.getFields().getTransaction().entrySet().stream() .filter( entry -> { AssetInfo.Sep31TxnFieldSpec field = entry.getValue(); if (field.isOptional()) return false; - return fields.get(entry.getKey()) == null; + return requestFields.get(entry.getKey()) == null; }) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); @@ -552,37 +627,8 @@ void validateRequiredFields() throws AnchorException { } } - Sep31GetTransactionResponse fromTransactionToResponse(Sep31Transaction txn) { - return Sep31GetTransactionResponse.builder() - .transaction( - TransactionResponse.builder() - .id(txn.getId()) - .status(txn.getStatus()) - .statusEta(txn.getStatusEta()) - .amountIn(txn.getAmountIn()) - .amountInAsset(txn.getAmountInAsset()) - .amountOut(txn.getAmountOut()) - .amountOutAsset(txn.getAmountOutAsset()) - .amountFee(txn.getAmountFee()) - .amountFeeAsset(txn.getAmountFeeAsset()) - .stellarAccountId(txn.getStellarAccountId()) - .stellarMemo(txn.getStellarMemo()) - .stellarMemoType(txn.getStellarMemoType()) - .startedAt(txn.getStartedAt()) - .completedAt(txn.getCompletedAt()) - .stellarTransactionId(txn.getStellarTransactionId()) - .externalTransactionId(txn.getExternalTransactionId()) - .refunded(txn.getRefunded()) - // TODO: handle refund after mvp - // .refunds(txn.getRefunds()) - .requiredInfoMessage(txn.getRequiredInfoMessage()) - .requiredInfoUpdates(txn.getRequiredInfoUpdates()) - .build()) - .build(); - } - @SneakyThrows - static Sep31InfoResponse createFromAssets(List assetInfos) { + private static Sep31InfoResponse sep31InfoResponseFromAssetInfoList(List assetInfos) { Sep31InfoResponse response = new Sep31InfoResponse(); response.setReceive(new HashMap<>()); for (AssetInfo assetInfo : assetInfos) { @@ -611,14 +657,14 @@ static Sep31InfoResponse createFromAssets(List assetInfos) { @Data public static class Context { - Sep31Transaction transaction; - Sep31PostTransactionRequest request; - Sep38Quote quote; - JwtToken jwtToken; - Amount fee; - AssetInfo asset; - Map transactionFields; - static ThreadLocal context = new ThreadLocal<>(); + private Sep31Transaction transaction; + private Sep31PostTransactionRequest request; + private Sep38Quote quote; + private JwtToken jwtToken; + private Amount fee; + private AssetInfo asset; + private Map transactionFields; + private static ThreadLocal context = new ThreadLocal<>(); public static Context get() { if (context.get() == null) { @@ -631,29 +677,4 @@ public static void reset() { context.set(null); } } - - public static class Sep31MissingFieldException extends AnchorException { - private final Sep31TxnFieldSpecs missingFields; - - public Sep31MissingFieldException(Sep31TxnFieldSpecs missingFields) { - super(); - this.missingFields = missingFields; - } - - public Sep31TxnFieldSpecs getMissingFields() { - return missingFields; - } - } - - public static class Sep31CustomerInfoNeededException extends AnchorException { - private final String type; - - Sep31CustomerInfoNeededException(String type) { - this.type = type; - } - - public String getType() { - return type; - } - } } 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 004b592e5e..c2f8efb6db 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -1,10 +1,13 @@ package org.stellar.anchor.sep31; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Map; import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.event.models.StellarId; +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse; +import org.stellar.anchor.api.shared.Customers; +import org.stellar.anchor.api.shared.StellarId; public interface Sep31Transaction { String getId(); @@ -19,6 +22,10 @@ public interface Sep31Transaction { void setStatusEta(Long statusEta); + String getAmountExpected(); + + void setAmountExpected(String amountExpected); + String getAmountIn(); void setAmountIn(String amountIn); @@ -59,6 +66,14 @@ public interface Sep31Transaction { void setStartedAt(Instant startedAt); + Instant getUpdatedAt(); + + void setUpdatedAt(Instant updatedAt); + + Instant getTransferReceivedAt(); + + void setTransferReceivedAt(Instant transferReceivedAt); + Instant getCompletedAt(); void setCompletedAt(Instant completedAt); @@ -111,32 +126,63 @@ public interface Sep31Transaction { void setCreator(StellarId creator); - interface Refunds { - - String getAmountRefunded(); - - void setAmountRefunded(String amountRefunded); - - String getAmountFee(); - - void setAmountFee(String amountFee); - - List getRefundPayments(); - - void setRefundPayments(List refundPayments); + default Customers getCustomers() { + return new Customers( + StellarId.builder().id(getSenderId()).build(), + StellarId.builder().id(getReceiverId()).build()); } - interface RefundPayment { - String getId(); - - void setId(String id); - - String getAmount(); - - void setAmount(String amount); - - String getFee(); - - void setFee(String fee); + default Sep31GetTransactionResponse toSep31GetTransactionResponse() { + Sep31GetTransactionResponse.Refunds refunds = null; + if (this.getRefunds() != null) { + List payments = null; + if (this.getRefunds().getRefundPayments() != null) { + for (RefundPayment refundPayment : this.getRefunds().getRefundPayments()) { + if (payments == null) { + payments = new ArrayList<>(); + } + + payments.add( + Sep31GetTransactionResponse.Sep31RefundPayment.builder() + .id(refundPayment.getId()) + .amount(refundPayment.getAmount()) + .fee(refundPayment.getFee()) + .build()); + } + } + + refunds = + Sep31GetTransactionResponse.Refunds.builder() + .amountRefunded(this.getRefunds().getAmountRefunded()) + .amountFee(this.getRefunds().getAmountFee()) + .payments(payments) + .build(); + } + + return Sep31GetTransactionResponse.builder() + .transaction( + Sep31GetTransactionResponse.TransactionResponse.builder() + .id(getId()) + .status(getStatus()) + .statusEta(getStatusEta()) + .amountIn(getAmountIn()) + .amountInAsset(getAmountInAsset()) + .amountOut(getAmountOut()) + .amountOutAsset(getAmountOutAsset()) + .amountFee(getAmountFee()) + .amountFeeAsset(getAmountFeeAsset()) + .stellarAccountId(getStellarAccountId()) + .stellarMemo(getStellarMemo()) + .stellarMemoType(getStellarMemoType()) + .startedAt(getStartedAt()) + .completedAt(getCompletedAt()) + .stellarTransactionId(getStellarTransactionId()) + .externalTransactionId(getExternalTransactionId()) + .refunded(getRefunded()) + .refunds(refunds) + .requiredInfoMessage(getRequiredInfoMessage()) + .requiredInfoUpdates(getRequiredInfoUpdates()) + .build()) + .build(); } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java index f8bd160033..314739a2de 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java @@ -3,7 +3,7 @@ import java.time.Instant; import java.util.Map; import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.event.models.StellarId; +import org.stellar.anchor.api.shared.StellarId; public class Sep31TransactionBuilder { final Sep31Transaction txn; @@ -29,6 +29,11 @@ public Sep31TransactionBuilder statusEta(Long statusEta) { return this; } + public Sep31TransactionBuilder amountExpected(String amountExpected) { + txn.setAmountExpected(amountExpected); + return this; + } + public Sep31TransactionBuilder amountIn(String amountIn) { txn.setAmountIn(amountIn); return this; @@ -74,6 +79,16 @@ public Sep31TransactionBuilder stellarMemoType(String stellarMemoType) { return this; } + public Sep31TransactionBuilder updatedAt(Instant updatedAt) { + txn.setUpdatedAt(updatedAt); + return this; + } + + public Sep31TransactionBuilder transferReceivedAt(Instant transferReceivedAt) { + txn.setTransferReceivedAt(transferReceivedAt); + return this; + } + public Sep31TransactionBuilder startedAt(Instant startedAt) { txn.setStartedAt(startedAt); return this; @@ -99,7 +114,7 @@ public Sep31TransactionBuilder refunded(Boolean refunded) { return this; } - public Sep31TransactionBuilder refunds(Sep31Transaction.Refunds refunds) { + public Sep31TransactionBuilder refunds(Refunds refunds) { txn.setRefunds(refunds); return this; } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java index 8b76d62a1f..8efd0b4be2 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java @@ -9,9 +9,9 @@ public interface Sep31TransactionStore { Sep31Transaction newTransaction(); - Sep31Transaction.Refunds newRefunds(); + Refunds newRefunds(); - Sep31Transaction.RefundPayment newRefundPayment(); + RefundPayment newRefundPayment(); /** * Find the Sep31Transaction by transaction_id diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteStore.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteStore.java index 9c528ed0ef..429078fee0 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteStore.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38QuoteStore.java @@ -12,9 +12,8 @@ public interface Sep38QuoteStore { * * @param quoteId The quote ID * @return The quote document. null if not found. - * @throws SepException if error happens */ - Sep38Quote findByQuoteId(@NonNull String quoteId) throws SepException; + Sep38Quote findByQuoteId(@NonNull String quoteId); /** * Save a quote. diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java index 0cab13385a..f4c0f06e1c 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java @@ -22,12 +22,12 @@ import org.stellar.anchor.api.exception.ServerErrorException; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep38.*; +import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.Sep38Config; import org.stellar.anchor.event.EventPublishService; import org.stellar.anchor.event.models.QuoteEvent; -import org.stellar.anchor.event.models.StellarId; import org.stellar.anchor.util.Log; public class Sep38Service { diff --git a/core/src/main/java/org/stellar/anchor/util/SepHelper.java b/core/src/main/java/org/stellar/anchor/util/SepHelper.java index 8de4134c98..1d73bd465e 100644 --- a/core/src/main/java/org/stellar/anchor/util/SepHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/SepHelper.java @@ -147,5 +147,6 @@ public static boolean validateTransactionStatus(SepTransactionStatus status, int PENDING_RECEIVER, PENDING_EXTERNAL, COMPLETED, + EXPIRED, ERROR); } diff --git a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31RefundPayment.java b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31RefundPayment.java index 371f3f8d17..07ed43b359 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31RefundPayment.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31RefundPayment.java @@ -1,9 +1,13 @@ package org.stellar.anchor.sep31; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data -public class PojoSep31RefundPayment implements Sep31Transaction.RefundPayment { +@AllArgsConstructor +@NoArgsConstructor +public class PojoSep31RefundPayment implements RefundPayment { String id; String amount; String fee; diff --git a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Refunds.java b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Refunds.java index ae8d62f17f..8fa77e06bd 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Refunds.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Refunds.java @@ -1,12 +1,34 @@ package org.stellar.anchor.sep31; +import java.util.ArrayList; import java.util.List; import lombok.Data; -import org.stellar.anchor.sep31.Sep31Transaction.RefundPayment; @Data -public class PojoSep31Refunds implements Sep31Transaction.Refunds { +public class PojoSep31Refunds implements Refunds { String amountRefunded; String amountFee; - List refundPayments; + List refundPayments; + + @Override + public void setRefundPayments(List refundPayments) { + if (refundPayments == null) { + this.refundPayments = null; + return; + } + + List newRefundPayments = new ArrayList<>(); + for (RefundPayment rp : refundPayments) { + newRefundPayments.add(new PojoSep31RefundPayment(rp.getId(), rp.getAmount(), rp.getFee())); + } + this.refundPayments = newRefundPayments; + } + + @Override + public List getRefundPayments() { + if (this.refundPayments == null) { + return null; + } + return new ArrayList<>(this.refundPayments); + } } diff --git a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java index 5332f95625..e11569ef40 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java @@ -2,11 +2,9 @@ import java.time.Instant; import java.util.Map; -import java.util.Set; import lombok.Data; import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.event.models.StellarId; -import org.stellar.anchor.event.models.StellarTransaction; +import org.stellar.anchor.api.shared.StellarId; @Data public class PojoSep31Transaction implements Sep31Transaction { @@ -32,13 +30,25 @@ public class PojoSep31Transaction implements Sep31Transaction { AssetInfo.Sep31TxnFieldSpecs requiredInfoUpdates; Map fields; Boolean refunded; - Refunds refunds; + PojoSep31Refunds refunds; Instant updatedAt; Instant transferReceivedAt; - String message; String amountExpected; String receiverId; String senderId; StellarId creator; - Set stellarTransactions = new java.util.LinkedHashSet<>(); + + @Override + public void setRefunds(Refunds refunds) { + if (refunds == null) { + this.refunds = null; + return; + } + + PojoSep31Refunds newRefunds = new PojoSep31Refunds(); + newRefunds.setAmountRefunded(refunds.getAmountRefunded()); + newRefunds.setAmountFee(refunds.getAmountFee()); + newRefunds.setRefundPayments(refunds.getRefundPayments()); + this.refunds = newRefunds; + } } diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt index dfa51935a1..f9dcb61fcd 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt @@ -37,6 +37,7 @@ class Sep31HelperTest { "pending_receiver", "pending_sender", "completed", + "expired", "error"] ) fun test_validateStatus(status: String) { 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 ac1a7c5de9..d4106e9fe0 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -4,6 +4,7 @@ import com.google.gson.Gson import io.mockk.* import io.mockk.impl.annotations.MockK import java.time.Instant +import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.util.* import org.apache.commons.lang3.StringUtils @@ -22,10 +23,13 @@ import org.stellar.anchor.api.exception.* import org.stellar.anchor.api.sep.AssetInfo import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerRequest import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerResponse +import org.stellar.anchor.api.sep.sep12.Sep12Status import org.stellar.anchor.api.sep.sep31.* import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest.Sep31TxnFields import org.stellar.anchor.api.sep.sep38.RateFee import org.stellar.anchor.api.shared.Amount +import org.stellar.anchor.api.shared.Customers +import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.auth.JwtService @@ -34,8 +38,6 @@ import org.stellar.anchor.config.Sep31Config import org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_RECEIVE import org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND import org.stellar.anchor.event.EventPublishService -import org.stellar.anchor.event.models.Customers -import org.stellar.anchor.event.models.StellarId import org.stellar.anchor.event.models.TransactionEvent import org.stellar.anchor.sep31.Sep31Service.* import org.stellar.anchor.sep38.PojoSep38Quote @@ -171,16 +173,25 @@ class Sep31ServiceTest { { "id": "a2392add-87c9-42f0-a5c1-5f1728030b68", "status": "pending_sender", - "stellarAccountId": "GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY", + "statusEta": "100", "amountIn": "100", "amountInAsset": "USDC", + "amountOut": "98", + "amountOutAsset": "USD", + "amountFee": "2", + "amountFeeAsset": "USDC", + "stellarAccountId": "GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY", + "stellarMemo": "123456", + "stellarMemoType": "text", + "startedAt": "2022-04-18T14:00:00.000Z", + "transferReceivedAt": "2022-04-18T14:30:00.000Z", + "updatedAt": "2022-04-18T15:00:00.000Z", + "completedAt": "2022-04-18T15:00:00.000Z", + "stellarTransactionId": "18db1b8dffa78a0567faadeab7b08b7be8b3f65c40018d609cc530b757e67bc2", + "externalTransactionId": "external-id", + "requiredInfoMessage": "Don't forget to foo bar", + "quoteId": "quote_id", "clientDomain": "demo-wallet-server.stellar.org", - "fields": { - "receiver_account_number": "1", - "type": "SWIFT", - "receiver_routing_number": "1" - }, - "stellarTransactions": [], "requiredInfoUpdates" : { "transaction" : { "type": { @@ -192,6 +203,35 @@ class Sep31ServiceTest { "optional": false } } + }, + "fields": { + "receiver_account_number": "1", + "type": "SWIFT", + "receiver_routing_number": "1" + }, + "refunded": true, + "refunds": { + "amountRefunded": "90", + "amountFee": "8", + "refundPayments": [ + { + "id": "111", + "amount": "50", + "fee": 4 + }, + { + "id": "222", + "amount": "40", + "fee": 4 + } + ] + }, + "stellarTransactions": [], + "amountExpected": "100", + "receiverId": "6820d44d-0881-4c94-aa55-1f4166b912f0", + "senderId": "3e3fa1f8-f24f-4be0-aab9-407b17753624", + "creator": { + "id": "5d35ef3f-9b80-457d-90e5-ad888536c6b9" } } """ @@ -360,6 +400,7 @@ class Sep31ServiceTest { val ex = assertThrows { sep31Service.updateTxAmountsBasedOnQuote() } assertEquals("Quote not found.", ex.message) } + @Test fun test_getTransaction() { assertThrows { sep31Service.getTransaction(null) } @@ -370,24 +411,66 @@ class Sep31ServiceTest { assertEquals("transaction (id=not_found) not found", ex.message) every { txnStore.findByTransactionId("found") } returns txn - val getTransactionResponse = sep31Service.getTransaction("found") - val tr = getTransactionResponse.transaction - assertEquals(tr.status, txn.status) - if (tr.statusEta != null) assertEquals(tr.statusEta, txn.statusEta) - assertEquals(tr.amountIn, txn.amountIn) - assertEquals(tr.amountInAsset, txn.amountInAsset) - assertEquals(tr.amountOut, txn.amountOut) - assertEquals(tr.amountOutAsset, txn.amountOutAsset) - assertEquals(tr.amountFee, txn.amountFee) - assertEquals(tr.amountFeeAsset, txn.amountFeeAsset) - assertEquals(tr.stellarMemo, txn.stellarMemo) - assertEquals(tr.stellarMemoType, txn.stellarMemoType) - assertEquals(tr.completedAt, txn.completedAt) - assertEquals(tr.stellarTransactionId, txn.stellarTransactionId) - assertEquals(tr.externalTransactionId, txn.externalTransactionId) - assertEquals(tr.refunded, txn.refunded) - assertEquals(tr.requiredInfoMessage, txn.requiredInfoMessage) - assertEquals(tr.requiredInfoUpdates, txn.requiredInfoUpdates) + val gotTxResponse = sep31Service.getTransaction("found") + + val wantStartedAt = + DateTimeFormatter.ISO_INSTANT.parse("2022-04-18T14:00:00.000Z", Instant::from) + val wantCompletedAt = + DateTimeFormatter.ISO_INSTANT.parse("2022-04-18T15:00:00.000Z", Instant::from) + val wantRefunds = + Sep31GetTransactionResponse.Refunds.builder() + .amountRefunded("90") + .amountFee("8") + .payments( + listOf( + Sep31GetTransactionResponse.Sep31RefundPayment.builder() + .id("111") + .amount("50") + .fee("4") + .build(), + Sep31GetTransactionResponse.Sep31RefundPayment.builder() + .id("222") + .amount("40") + .fee("4") + .build(), + ) + ) + .build() + + val wantRequiredInfoUpdates = AssetInfo.Sep31TxnFieldSpecs() + wantRequiredInfoUpdates.transaction = + mapOf( + "type" to + AssetInfo.Sep31TxnFieldSpec("type of deposit to make", listOf("SEPA", "SWIFT"), false) + ) + + val wantTxResponse = + Sep31GetTransactionResponse( + Sep31GetTransactionResponse.TransactionResponse.builder() + .id("a2392add-87c9-42f0-a5c1-5f1728030b68") + .status("pending_sender") + .statusEta(100) + .amountIn("100") + .amountInAsset("USDC") + .amountOut("98") + .amountOutAsset("USD") + .amountFee("2") + .amountFeeAsset("USDC") + .stellarAccountId("GAYR3FVW2PCXTNHHWHEAFOCKZQV4PEY2ZKGIKB47EKPJ3GSBYA52XJBY") + .stellarMemo("123456") + .stellarMemoType("text") + .startedAt(wantStartedAt) + .completedAt(wantCompletedAt) + .stellarTransactionId("18db1b8dffa78a0567faadeab7b08b7be8b3f65c40018d609cc530b757e67bc2") + .externalTransactionId("external-id") + .refunded(true) + .refunds(wantRefunds) + .requiredInfoMessage("Don't forget to foo bar") + .requiredInfoUpdates(wantRequiredInfoUpdates) + .build() + ) + + assertEquals(wantTxResponse, gotTxResponse) } @Test @@ -407,7 +490,7 @@ class Sep31ServiceTest { assertThrows { sep31Service.patchTransaction(Sep31PatchTransactionRequest.builder().build()) } - assertEquals("id cannot be null or empty", ex2.message) + assertEquals("id cannot be null nor empty", ex2.message) every { txnStore.findByTransactionId(any()) } returns null val ex3 = assertThrows { sep31Service.patchTransaction(patchRequest) } @@ -526,27 +609,45 @@ class Sep31ServiceTest { assertInstanceOf(Sep31CustomerInfoNeededException::class.java, ex) assertEquals("sep31-receiver", (ex as Sep31CustomerInfoNeededException).type) - // missing sender_id + // receiver status is not ACCEPTED val receiverId = "137938d4-43a7-4252-a452-842adcee474c" postTxRequest.receiverId = receiverId var request = Sep12GetCustomerRequest.builder().id(receiverId).type("sep31-receiver").build() - every { customerIntegration.getCustomer(request) } returns Sep12GetCustomerResponse() + val mockReceiver = Sep12GetCustomerResponse() + mockReceiver.id = receiverId + every { customerIntegration.getCustomer(request) } returns mockReceiver + ex = assertThrows { sep31Service.postTransaction(jwtToken, postTxRequest) } + assertInstanceOf(Sep31CustomerInfoNeededException::class.java, ex) + assertEquals("sep31-receiver", (ex as Sep31CustomerInfoNeededException).type) + + // missing sender_id + mockReceiver.status = Sep12Status.ACCEPTED + every { customerIntegration.getCustomer(request) } returns mockReceiver ex = assertThrows { sep31Service.postTransaction(jwtToken, postTxRequest) } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sender_id cannot be empty.", ex.message) - // not found receiver_id + // not found sender_id postTxRequest.senderId = "sender_bar" ex = assertThrows { sep31Service.postTransaction(jwtToken, postTxRequest) } assertInstanceOf(Sep31CustomerInfoNeededException::class.java, ex) assertEquals("sep31-sender", (ex as Sep31CustomerInfoNeededException).type) - // ----- QUOTE_ID IS USED ⬇️ ----- - // not found quote_id + // sender status is not ACCEPTED val senderId = "d2bd1412-e2f6-4047-ad70-a1a2f133b25c" postTxRequest.senderId = senderId request = Sep12GetCustomerRequest.builder().id(senderId).type("sep31-sender").build() - every { customerIntegration.getCustomer(request) } returns Sep12GetCustomerResponse() + val mockSender = Sep12GetCustomerResponse() + mockSender.id = receiverId + every { customerIntegration.getCustomer(request) } returns mockSender + ex = assertThrows { sep31Service.postTransaction(jwtToken, postTxRequest) } + assertInstanceOf(Sep31CustomerInfoNeededException::class.java, ex) + assertEquals("sep31-sender", (ex as Sep31CustomerInfoNeededException).type) + + // ----- QUOTE_ID IS USED ⬇️ ----- + // not found quote_id + mockSender.status = Sep12Status.ACCEPTED + every { customerIntegration.getCustomer(request) } returns mockSender postTxRequest.quoteId = "not-found-quote-id" every { quoteStore.findByQuoteId(any()) } returns null @@ -631,7 +732,9 @@ class Sep31ServiceTest { ) // Make sure we can get the sender and receiver customers - every { customerIntegration.getCustomer(any()) } returns Sep12GetCustomerResponse() + val mockCustomer = Sep12GetCustomerResponse() + mockCustomer.status = Sep12Status.ACCEPTED + every { customerIntegration.getCustomer(any()) } returns mockCustomer // mock sep31 deposit info generation val txForDepositInfoGenerator = slot() @@ -691,6 +794,7 @@ class Sep31ServiceTest { "type": "1", "receiver_routing_number": "SWIFT" }, + "amountExpected": "100", "amountIn": "100", "amountInAsset": "$stellarUSDC", "amountOut": "12500", @@ -698,7 +802,6 @@ class Sep31ServiceTest { "stellarAccountId": "GA7FYRB5VREZKOBIIKHG5AVTPFGWUBPOBF7LTYG4GTMFVIOOD2DWAL7I", "stellarMemo": "$memo", "stellarMemoType": "hash", - "stellarTransactions": [], "receiverId":"137938d4-43a7-4252-a452-842adcee474c", "senderId":"d2bd1412-e2f6-4047-ad70-a1a2f133b25c", "creator": { @@ -779,7 +882,9 @@ class Sep31ServiceTest { ) // Make sure we can get the sender and receiver customers - every { customerIntegration.getCustomer(any()) } returns Sep12GetCustomerResponse() + val mockCustomer = Sep12GetCustomerResponse() + mockCustomer.status = Sep12Status.ACCEPTED + every { customerIntegration.getCustomer(any()) } returns mockCustomer // POST transaction val jwtToken = TestHelper.createJwtToken() @@ -848,6 +953,11 @@ class Sep31ServiceTest { ), ) + // Make sure we can get the sender and receiver customers + val mockCustomer = Sep12GetCustomerResponse() + mockCustomer.status = Sep12Status.ACCEPTED + every { customerIntegration.getCustomer(any()) } returns mockCustomer + // POST transaction val jwtToken = TestHelper.createJwtToken() var gotResponse: Sep31PostTransactionResponse? = null @@ -888,22 +998,35 @@ class Sep31ServiceTest { @Test fun test_validateRequiredFields() { + val ex1 = assertThrows { sep31Service.validateRequiredFields() } + assertEquals("Missing asset information.", ex1.message) + val assetInfo = assetService.getAsset("USDC") Context.get().setAsset(assetInfo) - Context.get().setTransactionFields(txn.fields) - sep31Service.validateRequiredFields() - assetInfo.code = "BAD" - val ex1 = assertThrows { sep31Service.validateRequiredFields() } - assertEquals("Asset [BAD] has no fields definition", ex1.message) - - Context.get().setAsset(null) val ex2 = assertThrows { sep31Service.validateRequiredFields() } - assertEquals("Missing asset information.", ex2.message) + assertEquals("Asset [BAD] has no fields definition", ex2.message) - Context.get().setTransactionFields(null) + assetInfo.code = "USDC" val ex3 = assertThrows { sep31Service.validateRequiredFields() } assertEquals("'fields' field must have one 'transaction' field", ex3.message) + + Context.get().setTransactionFields(mapOf()) + val ex4 = assertThrows { sep31Service.validateRequiredFields() } + val wantMissingFields = AssetInfo.Sep31TxnFieldSpecs() + wantMissingFields.transaction = + mapOf( + "receiver_account_number" to + AssetInfo.Sep31TxnFieldSpec("bank account number of the destination", null, false), + "type" to + AssetInfo.Sep31TxnFieldSpec("type of deposit to make", listOf("SEPA", "SWIFT"), false), + "receiver_routing_number" to + AssetInfo.Sep31TxnFieldSpec("routing number of the destination bank account", null, false) + ) + assertEquals(wantMissingFields, ex4.missingFields) + + Context.get().setTransactionFields(txn.fields) + assertDoesNotThrow { sep31Service.validateRequiredFields() } } @Test diff --git a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt index 3669162c9d..70940b952f 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -20,12 +20,12 @@ import org.stellar.anchor.api.exception.ServerErrorException import org.stellar.anchor.api.sep.AssetInfo import org.stellar.anchor.api.sep.sep38.* import org.stellar.anchor.api.sep.sep38.Sep38Context.* +import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.Sep38Config import org.stellar.anchor.event.EventPublishService import org.stellar.anchor.event.models.QuoteEvent -import org.stellar.anchor.event.models.StellarId class Sep38ServiceTest { internal class PropertySep38Config : Sep38Config { diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index 670edd0df1..15f4c86977 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -28,7 +28,7 @@ The full documentation can be found under the [`docs` directory](/docs), under t - [E - Publishing the SDK](/docs/02%20-%20Contributing/E%20-%20Publishing%20the%20SDK.md) - [F - Testing with AWS Services](/docs/02%20-%20Contributing/F%20-%20Testing%20with%20AWS%20Services.md) - [03 - Implementing the Anchor Server](/docs/03%20-%20Implementing%20the%20Anchor%20Server) - - [Communication](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication/) + - [Communication](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication) - [Callback API](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication/Callbacks%20API.yml) - [Events Schema](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication/Events%20Schema.yml) - [Platform API](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication/Platform%20API.yml) @@ -41,24 +41,24 @@ Here are the important terminology used in this project: - **Anchor**: on/off ramps of the Stellar network. More information is available [here](https://developers.stellar.org/docs/anchoring-assets/). - **Wallet**: a frontend application used to interact with the Stellar network on behalf of a user. -- **Sending Anchor**: a therminology used in the context of [SEP-31]. Refers to an entity that receives funds from a user and forwards it (after taking a fee) to a receiving anchor, in the SEP-31 `Sending Client->Sending Anchor->Receiving Anchor-> Receiving Client` flow. +- **Sending Anchor**: a terminology used in the context of [SEP-31]. Refers to an entity that receives funds from a user and forwards it (after taking a fee) to a receiving anchor, in the SEP-31 `Sending Client->Sending Anchor->Receiving Anchor-> Receiving Client` flow. - **Receiving Anchor**: a terminology used in the context of [SEP-31]. Refers to an entity that receives funds from a user and forwards it (after taking a fee) to a receiving client (or recipient), in the SEP-31 `Sending Client->Sending Anchor->Receiving Anchor-> Receiving Client` flow. This is what the Anchor Platform currently implements. - **Ecosystem**: the community of entities and users that utilize the Stellar network and/or provide solutions on the Stellar network. - **Anchor Platform (or Platform)**: the web application that will be exposing public endpoints and APIs. It is compliant with the [SEPs] to guarantee interoperability in the Stellar network and delegates business-specific logic to the Anchor Server. -- **Anchor Server**: a microservice that will be responsible for the Anchor-specific business logic used in the the Anchor Platform. This service interacts with the Anchor Platform to perform some actions like: +- **Anchor Server**: a microservice that will be responsible for the Anchor-specific business logic used in the Anchor Platform. This service interacts with the Anchor Platform to perform some actions like: - Calculate conversion rates between two assets. - Create or update a customer account. - Notify the Anchor about an incoming payment. - **Anchor Reference Server**: an Anchor Server implementation that is shipped as part of this repository for testing purposes. -- **Callback API (`Sync Platform->Anchor`)**: a syncronous API that the Platform will use to gather a business-specific data from the Anchor Server, in order to perform a SEP-compliant operation (like exchange rate or user registration, for instance) -- **Events Queue (`Async Platform->Anchor`)**: an asyncronous communication venue that the Platform will use to notify the Anchor Server about a pending action, like an incoming payment that needs to be processed. -- **Platform API (`Sync Anchor->Platform`)**: a syncronous API that the Anchor can use to fetch information (e.g. transactions or quotes) and also update the data of transactions stored in the Platform database. +- **Callback API (`Sync Platform->Anchor`)**: a synchronous API that the Platform will use to gather a business-specific data from the Anchor Server, in order to perform a SEP-compliant operation (like exchange rate or user registration, for instance) +- **Events Queue (`Async Platform->Anchor`)**: an asynchronous communication venue that the Platform will use to notify the Anchor Server about a pending action, like an incoming payment that needs to be processed. +- **Platform API (`Sync Anchor->Platform`)**: a synchronous API that the Anchor can use to fetch information (e.g. transactions or quotes) and also update the data of transactions stored in the Platform database. - **[SEPs]**: it means Stellar Ecosystem Proposals and refers to standards that are used by Stellar ecosystem participants to achieve interoperability in the network. The ones implemented by this project are: | Standard | Description | Configurable | Interacts with Anchor Server | Supported by the Platform API | Supported by the SDK | | :------: | :----------------------------------------------------------------------------: | :----------: | :--------------------------: | :---------------------------: | :------------------: | | [SEP-10] | Handles authentication. | YES | NO | YES | YES | | [SEP-12] | Handles KYC. | YES | YES | YES | YES | - | [SEP-24] | Handles deposit & withrawal of assets in/out the Stellar network. | YES | NO | No | YES | + | [SEP-24] | Handles deposit & withdrawal of assets in/out the Stellar network. | YES | NO | NO | YES | | [SEP-31] | Used for international remittances. **Only the receiver side is implemented.** | YES | YES | YES | YES | | [SEP-38] | Used for [rfq] **in conjunction with [SEP-31]**. | YES | YES | YES | YES | @@ -69,7 +69,7 @@ In order to deploy this project, you'll need to have the following microservices - **Database Server**: usually, you'll use a relational database like MySQL or PostgreSQL, but we also support SQLite, commonly used in local development. - **Queue Service**: we currently support [Kafka](https://kafka.apache.org/) and [Amazon SQS](https://aws.amazon.com/sqs/). - **Anchor Platform Server**: this is the main application that will be providing public endpoints for your Anchor application. -- **Anchor Server**: this is the microservice that will be responsible for the Anchor-specific business logic used in the the Anchor Platform. +- **Anchor Server**: this is the microservice that will be responsible for the Anchor-specific business logic used in the Anchor Platform. ## Architecture @@ -94,7 +94,7 @@ Here you can see the sequence diagram of the [SEP-31] flow, showing all the stak %% - The anchor successfully delivers off-chain funds on first attempt sequenceDiagram title: SEP-31 Transaction Flow - participant Client + participant Client as SEP-31 Sending Anchor Note over Client: In the SEP-31 flow, this is the Sending Anchor. participant Platform Note over Platform: In the SEP-31 flow, this is the Receiving Anchor. @@ -127,7 +127,7 @@ sequenceDiagram Platform-->>-Client: forwards response end - opt Get a Quote if Supported or Required + opt Get a Quote if Quotes are Supported or Required Client->>+Platform: GET [SEP-38]/price Platform->>+Anchor: GET /rate?type=indicative_price Anchor-->>-Platform: exchange rate @@ -143,21 +143,49 @@ sequenceDiagram Client->>+Platform: POST [SEP-31]/transactions Platform-->>Platform: checks customer statuses, links quote - Platform->>+Anchor: GET /fee - Anchor-->>Anchor: calculates fee - Anchor-->>-Platform: fee + + opt Get the Fee if Quotes Were Not Used + Platform->>+Anchor: GET /fee + Anchor-->>Anchor: calculates fee + Anchor-->>-Platform: fee + end + Platform-->>Platform: Sets fee on transaction Platform-->>-Client: transaction id, receiving account & memo + Platform->>+Anchor: POST [webhookURL]/transactions created Anchor-->>-Platform: 204 No Content Client->>+Stellar: submit Stellar payment Stellar-->>Platform: receives payment, matches w/ transaction - Platform-->>Platform: updates transaction status + Platform-->>Platform: updates transaction status to `pending_receiver` Stellar-->>-Client: success response Platform->>+Anchor: POST [webhookURL]/transactions received Anchor-->>Anchor: queues off-chain payment Anchor-->>-Platform: 204 No Content Anchor->>Recipient: Sends off-chain payment to recipient + + opt Mismatched info when delivering value to the Recipient (or a similar error) + Recipient-->>Anchor: error: Recipient info was wrong + Anchor-->>Anchor: Updates the receiver customer info from ACCEPTED to NEEDS_INFO + Anchor->>Platform: PATCH /transactions to mark the status as `pending_customer_info_update` + Platform-->>Platform: updates transaction to `pending_customer_info_update` + Client->>+Platform: GET /transactions?id= + Platform-->>-Client: transaction `pending_customer_info_update` + + Client->>+Platform: PUT [SEP-12]/customer?type= + Platform->>+Anchor: forwards request + Anchor-->>Anchor: validates KYC values + Anchor-->>-Platform: id, ACCEPTED + Platform-->>-Client: forwards response + + Platform-->>Platform: updates transaction status to `pending_receiver` + Platform->>+Anchor: POST [webhookURL]/transactions with the status change + Anchor-->>Anchor: queues off-chain payment + Anchor-->>-Platform: 204 No Content + Anchor->>Recipient: Sends off-chain payment to recipient + end + + Recipient-->>Anchor: successfully delivered funds to Recipient Anchor->>+Platform: PATCH /transactions Platform-->>Platform: updates transaction to complete Platform-->>-Anchor: updated transaction diff --git a/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml b/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml index ef6bdec425..1769ad1ac3 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml @@ -76,6 +76,7 @@ paths: "pending_receiver", "pending_external", "completed", + "expired", "error" ] responses: @@ -168,6 +169,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Transaction' + '400': + description: "Bad Request" + content: + application/json: + schema: + $ref: '#/components/schemas/Error' '404': description: "Transaction not found." content: diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep12Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep12Tests.kt index d0c56b15cf..7f12122bc2 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep12Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep12Tests.kt @@ -15,11 +15,14 @@ const val testCustomer1Json = { "first_name": "John", "last_name": "Doe", + "email_address": "johndoe@test.com", "address": "123 Washington Street", "city": "San Francisco", "state_or_province": "CA", "address_country_code": "US", - "clabe_number": "1234" + "clabe_number": "1234", + "bank_number": "abcd", + "bank_account_number": "1234" } """ @@ -28,10 +31,14 @@ const val testCustomer2Json = { "first_name": "Jane", "last_name": "Doe", + "email_address": "janedoe@test.com", "address": "321 Washington Street", "city": "San Francisco", "state_or_province": "CA", - "address_country_code": "US" + "address_country_code": "US", + "clabe_number": "5678", + "bank_number": "efgh", + "bank_account_number": "5678" } """ @@ -45,6 +52,7 @@ fun sep12TestAll(toml: Sep1Helper.TomlContent, jwt: String) { fun sep12TestHappyPath() { val customer = GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + customer.emailAddress = null // Upload a customer printRequest("Calling PUT /customer", customer) @@ -60,8 +68,6 @@ fun sep12TestHappyPath() { assertEquals(pr.id, gr?.id) customer.emailAddress = "john.doe@stellar.org" - customer.bankAccountNumber = "1234" - customer.bankNumber = "abcd" customer.type = "sep31-receiver" // Modify the customer diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java index 0aa110c8ad..29c59acba1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java @@ -9,13 +9,13 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.api.exception.Sep31CustomerInfoNeededException; +import org.stellar.anchor.api.exception.Sep31MissingFieldException; import org.stellar.anchor.api.sep.AssetInfo.Sep31TxnFieldSpecs; import org.stellar.anchor.api.sep.sep31.*; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.platform.condition.ConditionalOnAllSepsEnabled; import org.stellar.anchor.sep31.Sep31Service; -import org.stellar.anchor.sep31.Sep31Service.Sep31CustomerInfoNeededException; -import org.stellar.anchor.sep31.Sep31Service.Sep31MissingFieldException; @RestController @CrossOrigin(origins = "*") diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java index a0f73e4b0c..3adf81dbee 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java @@ -1,11 +1,11 @@ package org.stellar.anchor.platform.data; import lombok.Data; -import org.stellar.anchor.sep31.Sep31Transaction; +import org.stellar.anchor.sep31.RefundPayment; public class JdbcSep31RefundPayment { @Data - public static class JdbcRefundPayment implements Sep31Transaction.RefundPayment { + public static class JdbcRefundPayment implements RefundPayment { String Id; String amount; String fee; diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java index 33a5d28544..08ddbf58d5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java @@ -3,10 +3,11 @@ import com.google.gson.annotations.SerializedName; import java.util.List; import lombok.Data; -import org.stellar.anchor.sep31.Sep31Transaction; +import org.stellar.anchor.sep31.RefundPayment; +import org.stellar.anchor.sep31.Refunds; @Data -public class JdbcSep31Refunds implements Sep31Transaction.Refunds { +public class JdbcSep31Refunds implements Refunds { @SerializedName("amount_refunded") String amountRefunded; @@ -14,5 +15,5 @@ public class JdbcSep31Refunds implements Sep31Transaction.Refunds { String amountFee; @SerializedName("payments") - List refundPayments; + List refundPayments; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java index c110063a30..8faf1e293e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java @@ -10,8 +10,9 @@ import lombok.Getter; import lombok.Setter; import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.event.models.StellarId; +import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.reference.model.StellarIdConverter; +import org.stellar.anchor.sep31.Refunds; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.util.GsonUtils; diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java index 074f99f0ba..72d97b932b 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java @@ -7,6 +7,8 @@ import lombok.NonNull; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.SepException; +import org.stellar.anchor.sep31.RefundPayment; +import org.stellar.anchor.sep31.Refunds; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.sep31.Sep31TransactionStore; @@ -23,12 +25,12 @@ public Sep31Transaction newTransaction() { } @Override - public Sep31Transaction.Refunds newRefunds() { + public Refunds newRefunds() { return new JdbcSep31Refunds(); } @Override - public Sep31Transaction.RefundPayment newRefundPayment() { + public RefundPayment newRefundPayment() { return new JdbcSep31RefundPayment.JdbcRefundPayment(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index 14a206baa9..175d7ceae9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -16,6 +16,8 @@ import org.stellar.anchor.api.exception.SepException; 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.StellarId; import org.stellar.anchor.event.EventPublishService; import org.stellar.anchor.event.models.*; import org.stellar.anchor.platform.paymentobserver.ObservedPayment; @@ -114,6 +116,10 @@ public void onReceived(ObservedPayment payment) { if (txn.getStatus().equals(SepTransactionStatus.PENDING_SENDER.toString())) { txn.setStatus(SepTransactionStatus.PENDING_RECEIVER.toString()); txn.setStellarTransactionId(payment.getTransactionHash()); + Instant paymentTime = + DateTimeFormatter.ISO_INSTANT.parse(payment.getCreatedAt(), Instant::from); + txn.setUpdatedAt(paymentTime); + txn.setTransferReceivedAt(paymentTime); try { transactionStore.save(txn); Metrics.counter( @@ -140,9 +146,6 @@ private void sendToQueue(TransactionEvent event) { } TransactionEvent receivedPaymentToEvent(Sep31Transaction txn, ObservedPayment payment) { - Instant paymentTime = - DateTimeFormatter.ISO_INSTANT.parse(payment.getCreatedAt(), Instant::from); - TransactionEvent.Status oldStatus = TransactionEvent.Status.from(txn.getStatus()); TransactionEvent.Status newStatus = TransactionEvent.Status.PENDING_RECEIVER; TransactionEvent.StatusChange statusChange = @@ -159,16 +162,16 @@ TransactionEvent receivedPaymentToEvent(Sep31Transaction txn, ObservedPayment pa .kind(TransactionEvent.Kind.RECEIVE) .status(newStatus) .statusChange(statusChange) - .amountExpected(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) + .amountExpected(new Amount(txn.getAmountExpected(), txn.getAmountInAsset())) .amountIn(new Amount(payment.getAmount(), txn.getAmountInAsset())) .amountOut(new Amount(txn.getAmountOut(), txn.getAmountOutAsset())) // TODO: fix PATCH transaction fails if getAmountOut is null? .amountFee(new Amount(txn.getAmountFee(), txn.getAmountFeeAsset())) .quoteId(txn.getQuoteId()) .startedAt(txn.getStartedAt()) - .updatedAt(paymentTime) + .updatedAt(txn.getUpdatedAt()) .completedAt(null) - .transferReceivedAt(paymentTime) + .transferReceivedAt(txn.getTransferReceivedAt()) .message("Incoming payment for SEP-31 transaction") .refunds(null) .stellarTransactions( @@ -177,7 +180,7 @@ TransactionEvent receivedPaymentToEvent(Sep31Transaction txn, ObservedPayment pa .id(payment.getTransactionHash()) .memo(txn.getStellarMemo()) .memoType(txn.getStellarMemoType()) - .createdAt(paymentTime) + .createdAt(txn.getTransferReceivedAt()) .envelope(payment.getTransactionEnvelope()) .payments( new Payment[] { diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java index 878028feb2..32c0accb7e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java @@ -6,12 +6,9 @@ import static org.stellar.anchor.util.MathHelper.decimal; import io.micrometer.core.instrument.Metrics; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.BadRequestException; @@ -23,45 +20,51 @@ import org.stellar.anchor.api.platform.PatchTransactionsResponse; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.Refund; +import org.stellar.anchor.api.shared.RefundPayment; import org.stellar.anchor.asset.AssetService; -import org.stellar.anchor.platform.data.JdbcSep31Transaction; -import org.stellar.anchor.platform.data.JdbcSep31TransactionStore; +import org.stellar.anchor.event.models.TransactionEvent; +import org.stellar.anchor.sep31.Refunds; import org.stellar.anchor.sep31.Sep31Transaction; +import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep38.Sep38Quote; import org.stellar.anchor.sep38.Sep38QuoteStore; +import org.stellar.anchor.util.Log; @Service public class TransactionService { private final Sep38QuoteStore quoteStore; - private final JdbcSep31TransactionStore txnStore; + private final Sep31TransactionStore txnStore; private final List assets; static List validStatuses = List.of( PENDING_STELLAR.getName(), - PENDING_TRANSACTION_INFO_UPDATE.getName(), + PENDING_CUSTOMER_INFO_UPDATE.getName(), PENDING_RECEIVER.getName(), PENDING_EXTERNAL.getName(), COMPLETED.getName(), + EXPIRED.getName(), ERROR.getName()); TransactionService( - Sep38QuoteStore quoteStore, JdbcSep31TransactionStore txnStore, AssetService assetService) { + Sep38QuoteStore quoteStore, Sep31TransactionStore txnStore, AssetService assetService) { this.quoteStore = quoteStore; this.txnStore = txnStore; this.assets = assetService.listAllAssets(); } public GetTransactionResponse getTransaction(String txnId) throws AnchorException { - JdbcSep31Transaction txn = (JdbcSep31Transaction) txnStore.findByTransactionId(txnId); + if (Objects.toString(txnId, "").isEmpty()) { + Log.info("Rejecting GET {platformApi}/transaction/:id because the id is empty."); + throw new BadRequestException("transaction id cannot be empty"); + } + + Sep31Transaction txn = txnStore.findByTransactionId(txnId); if (txn == null) { throw new NotFoundException(String.format("transaction (id=%s) is not found", txnId)); } - GetTransactionResponse txnResponse = fromTransactionToResponse(txn); - GetTransactionResponse response = new GetTransactionResponse(); - BeanUtils.copyProperties(txnResponse, response); - - return response; + return fromTransactionToResponse(txn); } public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest request) @@ -74,12 +77,12 @@ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest requ fetchedTxns.stream() .collect(Collectors.toMap(Sep31Transaction::getId, Function.identity())); - List txnsToSave = new LinkedList<>(); + List txnsToSave = new LinkedList<>(); List responses = new LinkedList<>(); - List statusUpdatedTxns = new LinkedList<>(); + List statusUpdatedTxns = new LinkedList<>(); for (PatchTransactionRequest patch : patchRequests) { - JdbcSep31Transaction txn = (JdbcSep31Transaction) sep31Transactions.get(patch.getId()); + Sep31Transaction txn = sep31Transactions.get(patch.getId()); if (txn != null) { String txnOriginalStatus = txn.getStatus(); // validate and update the transaction. @@ -94,21 +97,63 @@ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest requ throw new BadRequestException(String.format("transaction(id=%s) not found", patch.getId())); } } - for (JdbcSep31Transaction txn : txnsToSave) { + for (Sep31Transaction txn : txnsToSave) { // TODO: consider 2-phase commit DB transaction management. txnStore.save(txn); } - for (JdbcSep31Transaction txn : statusUpdatedTxns) { + for (Sep31Transaction txn : statusUpdatedTxns) { Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", txn.getStatus()) .increment(); } return new PatchTransactionsResponse(responses); } - GetTransactionResponse fromTransactionToResponse(JdbcSep31Transaction txn) { + GetTransactionResponse fromTransactionToResponse(Sep31Transaction txn) { + Refunds txnRefunds = txn.getRefunds(); + Refund refunds = null; + if (txnRefunds != null) { + String amountInAsset = txn.getAmountInAsset(); + RefundPayment[] payments = null; + Refund.RefundBuilder refundsBuilder = + Refund.builder() + .amountRefunded(new Amount(txnRefunds.getAmountRefunded(), amountInAsset)) + .amountFee(new Amount(txnRefunds.getAmountFee(), amountInAsset)); + + // populate refunds payments + for (int i = 0; i < txnRefunds.getRefundPayments().size(); i++) { + org.stellar.anchor.sep31.RefundPayment refundPayment = + txnRefunds.getRefundPayments().get(i); + RefundPayment platformRefundPayment = + RefundPayment.builder() + .id(refundPayment.getId()) + .idType(RefundPayment.IdType.STELLAR) + .amount(new Amount(refundPayment.getAmount(), amountInAsset)) + .fee(new Amount(refundPayment.getFee(), amountInAsset)) + .requestedAt(null) + .refundedAt(null) + .build(); + + if (payments == null) { + payments = new RefundPayment[txnRefunds.getRefundPayments().size()]; + } + payments[i] = platformRefundPayment; + } + + refunds = refundsBuilder.payments(payments).build(); + } + + List stellarTransactions = null; + if (!Objects.toString(txn.getStellarTransactionId(), "").isEmpty()) { + GetTransactionResponse.StellarTransaction stellarTxn = + new GetTransactionResponse.StellarTransaction(); + stellarTxn.setId(txn.getStellarTransactionId()); + stellarTransactions = List.of(stellarTxn); + } + return GetTransactionResponse.builder() .id(txn.getId()) .sep(31) + .kind(TransactionEvent.Kind.RECEIVE.getKind()) .status(txn.getStatus()) .amountExpected(new Amount(txn.getAmountExpected(), txn.getAmountInAsset())) .amountIn(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) @@ -119,13 +164,17 @@ GetTransactionResponse fromTransactionToResponse(JdbcSep31Transaction txn) { .updatedAt(txn.getUpdatedAt()) .completedAt(txn.getCompletedAt()) .transferReceivedAt(txn.getTransferReceivedAt()) - .message(txn.getMessage()) - .externalId(txn.getExternalTransactionId()) - // TODO: Add support for [refunds, stellarTransaction, custodialId, creator] + .message(txn.getRequiredInfoMessage()) // Assuming these meant to be the same. + .refunds(refunds) + .stellarTransactions(stellarTransactions) + .externalTransactionId(txn.getExternalTransactionId()) + // TODO .custodialTransactionId(txn.get) + .customers(txn.getCustomers()) + .creator(txn.getCreator()) .build(); } - void updateSep31Transaction(PatchTransactionRequest ptr, JdbcSep31Transaction txn) + void updateSep31Transaction(PatchTransactionRequest ptr, Sep31Transaction txn) throws AnchorException { if (ptr.getStatus() != null) { validatePlatformApiStatus(ptr.getStatus()); @@ -150,7 +199,7 @@ void updateSep31Transaction(PatchTransactionRequest ptr, JdbcSep31Transaction tx txn.setTransferReceivedAt(ptr.getTransferReceivedAt()); } if (ptr.getMessage() != null) { - txn.setMessage(ptr.getMessage()); + txn.setRequiredInfoMessage(ptr.getMessage()); } if (ptr.getExternalTransactionId() != null) { txn.setExternalTransactionId(ptr.getExternalTransactionId()); @@ -178,7 +227,7 @@ void validateAsset(Amount amount) throws BadRequestException { } } - void validateQuoteAndAmounts(JdbcSep31Transaction txn) throws AnchorException { + void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { // amount_in = amount_out + amount_fee if (txn.getQuoteId() == null) { // without exchange @@ -226,7 +275,7 @@ void validateQuoteAndAmounts(JdbcSep31Transaction txn) throws AnchorException { } } - void validateTimestamps(JdbcSep31Transaction txn) throws BadRequestException { + void validateTimestamps(Sep31Transaction txn) throws BadRequestException { if (txn.getTransferReceivedAt() != null && txn.getTransferReceivedAt().compareTo(txn.getStartedAt()) < 0) { throw new BadRequestException( diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt index 832ab12645..7a891a05fa 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt @@ -10,6 +10,8 @@ import org.junit.jupiter.api.Test import org.stellar.anchor.api.exception.SepException 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.StellarId import org.stellar.anchor.event.EventPublishService import org.stellar.anchor.event.models.* import org.stellar.anchor.platform.data.JdbcSep31Transaction @@ -135,6 +137,7 @@ class PaymentOperationToEventListenerTest { val slotMemo = slot() val sep31TxMock = JdbcSep31Transaction() sep31TxMock.id = "ceaa7677-a5a7-434e-b02a-8e0801b3e7bd" + sep31TxMock.amountExpected = "10" sep31TxMock.amountIn = "10" sep31TxMock.amountInAsset = fooAsset sep31TxMock.amountOut = "20" @@ -143,6 +146,8 @@ class PaymentOperationToEventListenerTest { sep31TxMock.amountFeeAsset = fooAsset sep31TxMock.quoteId = "cef1fc13-3f65-4612-b1f2-502d698c816b" sep31TxMock.startedAt = startedAtMock + sep31TxMock.updatedAt = createdAt + sep31TxMock.transferReceivedAt = createdAt sep31TxMock.stellarMemo = "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=" sep31TxMock.stellarMemoType = "hash" sep31TxMock.status = SepTransactionStatus.PENDING_SENDER.toString() diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt new file mode 100644 index 0000000000..a7b5ee1cec --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt @@ -0,0 +1,204 @@ +package org.stellar.anchor.platform.service + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import java.time.Instant +import org.junit.jupiter.api.AfterEach +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.assertThrows +import org.stellar.anchor.api.exception.AnchorException +import org.stellar.anchor.api.exception.BadRequestException +import org.stellar.anchor.api.exception.NotFoundException +import org.stellar.anchor.api.platform.GetTransactionResponse +import org.stellar.anchor.api.sep.AssetInfo +import org.stellar.anchor.api.shared.* +import org.stellar.anchor.api.shared.RefundPayment +import org.stellar.anchor.asset.AssetService +import org.stellar.anchor.event.models.TransactionEvent +import org.stellar.anchor.platform.data.JdbcSep31RefundPayment.JdbcRefundPayment +import org.stellar.anchor.platform.data.JdbcSep31Refunds +import org.stellar.anchor.platform.data.JdbcSep31Transaction +import org.stellar.anchor.sep31.* +import org.stellar.anchor.sep38.Sep38QuoteStore + +class TransactionServiceTest { + companion object { + private const val fiatUSD = "iso4217:USD" + private const val stellarUSDC = + "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" + private const val TEST_ACCOUNT = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + private const val TEST_MEMO = "test memo" + } + + @MockK(relaxed = true) private lateinit var sep38QuoteStore: Sep38QuoteStore + @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore + @MockK(relaxed = true) private lateinit var assetService: AssetService + private lateinit var transactionService: TransactionService + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + transactionService = TransactionService(sep38QuoteStore, sep31TransactionStore, assetService) + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun test_getTransaction_failure() { + // null tx id is rejected with 400 + var ex: AnchorException = assertThrows { transactionService.getTransaction(null) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("transaction id cannot be empty", ex.message) + + // empty tx id is rejected with 400 + ex = assertThrows { transactionService.getTransaction("") } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("transaction id cannot be empty", ex.message) + + // non-existent transaction is rejected with 404 + every { sep31TransactionStore.findByTransactionId(any()) } returns null + ex = assertThrows { transactionService.getTransaction("not-found-tx-id") } + assertInstanceOf(NotFoundException::class.java, ex) + assertEquals("transaction (id=not-found-tx-id) is not found", ex.message) + } + + @Test + fun test_getTransaction() { + val txId = "a4baff5f-778c-43d6-bbef-3e9fb41d096e" + + // Mock the store + every { sep31TransactionStore.newTransaction() } returns JdbcSep31Transaction() + every { sep31TransactionStore.newRefunds() } returns JdbcSep31Refunds() + every { sep31TransactionStore.newRefundPayment() } answers { JdbcRefundPayment() } + + // mock time + val mockStartedAt = Instant.now().minusSeconds(180) + val mockUpdatedAt = mockStartedAt.plusSeconds(60) + val mockTransferReceivedAt = mockUpdatedAt.plusSeconds(60) + val mockCompletedAt = mockTransferReceivedAt.plusSeconds(60) + + // mock refunds + val mockRefundPayment1 = + RefundPaymentBuilder(sep31TransactionStore).id("1111").amount("50.0000").fee("4.0000").build() + val mockRefundPayment2 = + RefundPaymentBuilder(sep31TransactionStore).id("2222").amount("40.0000").fee("4.0000").build() + val mockRefunds = + RefundsBuilder(sep31TransactionStore) + .amountRefunded("90.0000") + .amountFee("8.0000") + .payments(listOf(mockRefundPayment1, mockRefundPayment2)) + .build() + + // mock missing SEP-31 "transaction.fields" + val mockMissingFields = AssetInfo.Sep31TxnFieldSpecs() + mockMissingFields.transaction = + mapOf( + "receiver_account_number" to + AssetInfo.Sep31TxnFieldSpec("bank account number of the destination", null, false), + ) + + // mock the database SEP-31 transaction + val mockSep31Transaction = + Sep31TransactionBuilder(sep31TransactionStore) + .id(txId) + .status(TransactionEvent.Status.PENDING_RECEIVER.status) + .statusEta(120) + .amountExpected("100") + .amountIn("100.0000") + .amountInAsset(fiatUSD) + .amountOut("98.0000000") + .amountOutAsset(stellarUSDC) + .amountFee("2.0000") + .amountFeeAsset(fiatUSD) + .stellarAccountId(TEST_ACCOUNT) + .stellarMemo(TEST_MEMO) + .stellarMemoType("text") + .startedAt(mockStartedAt) + .updatedAt(mockUpdatedAt) + .transferReceivedAt(mockTransferReceivedAt) + .completedAt(mockCompletedAt) + .stellarTransactionId("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .externalTransactionId("external-tx-id") + .refunded(true) + .refunds(mockRefunds) + .requiredInfoMessage("Please don't forget to foo bar") + .requiredInfoUpdates(mockMissingFields) + .quoteId("quote-id") + .clientDomain("test.com") + .senderId("6c1770b0-0ea4-11ed-861d-0242ac120002") + .receiverId("31212353-f265-4dba-9eb4-0bbeda3ba7f2") + .creator(StellarId("141ee445-f32c-4c38-9d25-f4475d6c5558", null)) + .build() + every { sep31TransactionStore.findByTransactionId(txId) } returns mockSep31Transaction + + val gotGetTransactionResponse = transactionService.getTransaction(txId) + + val wantRefunds: Refund = + Refund.builder() + .amountRefunded(Amount("90.0000", fiatUSD)) + .amountFee(Amount("8.0000", fiatUSD)) + .payments( + arrayOf( + RefundPayment.builder() + .id("1111") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50.0000", fiatUSD)) + .fee(Amount("4.0000", fiatUSD)) + .requestedAt(null) + .refundedAt(null) + .build(), + RefundPayment.builder() + .id("2222") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("40.0000", fiatUSD)) + .fee(Amount("4.0000", fiatUSD)) + .requestedAt(null) + .refundedAt(null) + .build() + ) + ) + .build() + + val wantStellarTransaction = GetTransactionResponse.StellarTransaction() + wantStellarTransaction.id = "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300" + + val wantGetTransactionResponse: GetTransactionResponse = + GetTransactionResponse.builder() + .id(txId) + .sep(31) + .kind("receive") + .status(TransactionEvent.Status.PENDING_RECEIVER.status) + .amountExpected(Amount("100", fiatUSD)) + .amountIn(Amount("100.0000", fiatUSD)) + .amountOut(Amount("98.0000000", stellarUSDC)) + .amountFee(Amount("2.0000", fiatUSD)) + .quoteId("quote-id") + .startedAt(mockStartedAt) + .updatedAt(mockUpdatedAt) + .completedAt(mockCompletedAt) + .transferReceivedAt(mockTransferReceivedAt) + .message("Please don't forget to foo bar") + .refunds(wantRefunds) + .stellarTransactions(listOf(wantStellarTransaction)) + .externalTransactionId("external-tx-id") + .customers( + Customers( + StellarId("6c1770b0-0ea4-11ed-861d-0242ac120002", null), + StellarId("31212353-f265-4dba-9eb4-0bbeda3ba7f2", null) + ) + ) + .creator(StellarId("141ee445-f32c-4c38-9d25-f4475d6c5558", null)) + .build() + assertEquals(wantGetTransactionResponse, gotGetTransactionResponse) + } +} diff --git a/platform/src/test/resources/stellar-anchor-tests-sep-config.json b/platform/src/test/resources/stellar-anchor-tests-sep-config.json index 51487c7796..9853a0c68b 100644 --- a/platform/src/test/resources/stellar-anchor-tests-sep-config.json +++ b/platform/src/test/resources/stellar-anchor-tests-sep-config.json @@ -16,7 +16,10 @@ "receivingClient": { "first_name": "Lee", "last_name": "Sun", - "email_address": "lee@email.com" + "email_address": "lee@email.com", + "clabe_number": "1234", + "bank_number": "abcd", + "bank_account_number": "1234" }, "toBeDeleted": { "first_name": "Jane", From 5face15f17ba724dc2e1e02d6561e85007ae91f2 Mon Sep 17 00:00:00 2001 From: erika-sdf <92893480+erika-sdf@users.noreply.github.com> Date: Mon, 1 Aug 2022 13:57:19 -0700 Subject: [PATCH 04/65] helm: Fix typo in helm chart for kafkaPublisher.eventTypeToQueue (#451) * Fix typo in helm chart for kafkaPublisher.eventTypeToQueue --- helm-charts/sep-service/Chart.yaml | 2 +- helm-charts/sep-service/templates/configmap.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/helm-charts/sep-service/Chart.yaml b/helm-charts/sep-service/Chart.yaml index 56a6f401eb..412fbd85ba 100644 --- a/helm-charts/sep-service/Chart.yaml +++ b/helm-charts/sep-service/Chart.yaml @@ -7,4 +7,4 @@ maintainers: sources: - https://github.com/stellar/java-stellar-anchor-sdk name: sep -version: 0.3.84 +version: 0.3.85 diff --git a/helm-charts/sep-service/templates/configmap.yaml b/helm-charts/sep-service/templates/configmap.yaml index 13a34fe195..b20ce62544 100644 --- a/helm-charts/sep-service/templates/configmap.yaml +++ b/helm-charts/sep-service/templates/configmap.yaml @@ -187,7 +187,7 @@ data: bootstrapServer: {{ .Values.stellar.app_config.kafka_publisher.bootstrapServer | default "missing_bootstrapServer:port" }} useIAM: {{ .Values.stellar.app_config.kafka_publisher.useIAM | default "false" }} useSingleQueue: {{ .Values.stellar.app_config.kafka_publisher.useSingleQueue | default "false" }} - {{- if (((.Values.stellar.app_config).event).kafka_publisher).eventTypeQueue }} + {{- if ((.Values.stellar.app_config).kafka_publisher).eventTypeToQueue }} eventTypeToQueue: all: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.all | default "dev_ap_event_single_queue" }} quoteCreated: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.quoteCreated | default "dev_ap_event_quote_created" }} From 27ec41ff7e4b698d8ed5a59e908f112a97d83098 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 1 Aug 2022 17:51:10 -0700 Subject: [PATCH 05/65] platform: Fix error when not running LogAppenderTest from console (#453) * Fix the test error in IntelliJ but not in gradle * add more test methods --- .../anchor/platform/LogAppenderTest.kt | 73 +++++++++++++++---- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt index 808df87cee..884e09dc14 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt @@ -1,22 +1,67 @@ package org.stellar.anchor.platform -import java.io.* -import org.hamcrest.CoreMatchers -import org.hamcrest.MatcherAssert -import org.junit.jupiter.api.* +import io.mockk.* +import org.apache.logging.log4j.Level +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.Appender +import org.apache.logging.log4j.core.LogEvent +import org.apache.logging.log4j.core.LoggerContext +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.stellar.anchor.util.* class LogAppenderTest { - @Test - fun test_logJsonAppenderFormat() { - val outputStreamCaptor = ByteArrayOutputStream() - System.setOut(PrintStream(outputStreamCaptor)) - Log.info("hello world") - val outputStream = outputStreamCaptor.toString().trim() + private val appender = mockk() + private val capturedLogEvent = slot() + private val loggerContext: LoggerContext = LogManager.getContext(false) as LoggerContext + private val rootLoggerConfig = loggerContext.configuration.getLoggerConfig("org.stellar") + private val lastLevel = rootLoggerConfig.level + @BeforeEach + fun setup() { + every { appender.name } returns "mock appender" + every { appender.isStarted } returns true + every { appender.append(capture(capturedLogEvent)) } - MatcherAssert.assertThat( - outputStream, - CoreMatchers.endsWith("INFO - o.s.a.p.LogAppenderTest - hello world") - ) + rootLoggerConfig.addAppender(appender, Level.ALL, null) + rootLoggerConfig.level = Level.TRACE + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + rootLoggerConfig.removeAppender("mock appender") + rootLoggerConfig.level = lastLevel + } + + @ParameterizedTest + @CsvSource( + value = + [ + "error,error_message,ERROR", + "warn,warn_message,WARN", + "info,info_message,INFO", + "debug,debug_message,DEBUG", + "trace,trace_message,TRACE", + ]) + fun test_logger_outputAccurateLoggerName( + methodName: String, + wantMessage: String, + wantLevelName: String + ) { + when (methodName) { + "error" -> Log.error(wantMessage) + "warn" -> Log.warn(wantMessage) + "info" -> Log.info(wantMessage) + "debug" -> Log.debug(wantMessage) + "trace" -> Log.trace(wantMessage) + } + assertEquals(LogAppenderTest::class.qualifiedName, capturedLogEvent.captured.loggerName) + assertEquals(wantLevelName, capturedLogEvent.captured.level.toString()) + assertEquals(wantMessage, capturedLogEvent.captured.message.toString()) + capturedLogEvent.clear() } } From 6d7c91282bb7cbbd03d6d5eded1f0bdd569e2115 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 2 Aug 2022 14:46:21 -0700 Subject: [PATCH 06/65] all: Remove payment sub-project (#450) * Removed payment subproject and moved classes to platform * merge payment packages * fix test errors * fix printing payment test result error * Removed the payment gradle build file and folder --- .github/workflows/build_and_test.yml | 2 - payment/build.gradle.kts | 27 ------------- .../circle/config/CirclePaymentConfig.java | 7 ---- .../circle/config/StellarPaymentConfig.java | 15 -------- .../java/paymentservice/PaymentGateway.java | 38 ------------------- .../paymentservice/AccountCapabilitiesTest.kt | 26 ------------- platform/build.gradle.kts | 1 - .../config/PropertyCirclePaymentConfig.java | 10 ----- .../config/PropertyStellarPaymentConfig.java | 12 ------ .../platform/ConfigManagementConfig.java | 18 +++------ .../anchor/platform/PaymentConfig.java | 14 +++---- .../stellar/anchor/platform/SepConfig.java | 4 +- .../CirclePaymentObserverController.java | 2 +- .../platform/payment/common}/Account.java | 2 +- .../platform/payment/common}/Balance.java | 2 +- .../payment/common}/DepositInstructions.java | 2 +- .../payment/common}/DepositRequirements.java | 2 +- .../platform/payment/common}/Payment.java | 2 +- .../payment/common}/PaymentHistory.java | 2 +- .../payment/common}/PaymentNetwork.java | 2 +- .../payment/common}/PaymentService.java | 2 +- .../payment/config/CirclePaymentConfig.java | 9 +++++ .../payment/observer/PaymentListener.java | 9 +++++ .../observer}/circle/CircleGsonParsable.java | 8 ++-- .../circle}/CirclePaymentObserverService.java | 15 ++++---- .../circle/CirclePaymentService.java | 16 ++++---- .../circle/CircleResponseErrorHandler.java | 4 +- .../observer/circle}/ObservedPayment.java | 2 +- .../circle/StellarReconciliation.java | 6 +-- .../observer}/circle/model/CircleBalance.java | 8 ++-- .../circle/model/CircleBlockchainAddress.java | 8 ++-- .../circle/model}/CircleNotification.java | 2 +- .../observer}/circle/model/CirclePayment.java | 8 ++-- .../circle/model/CirclePaymentStatus.java | 4 +- .../observer}/circle/model/CirclePayout.java | 8 ++-- .../circle/model/CircleTransactionParty.java | 6 +-- .../circle/model/CircleTransfer.java | 6 +-- .../observer}/circle/model/CircleWallet.java | 10 ++--- .../model/CircleWireDepositInstructions.java | 8 ++-- .../model}/TransferNotificationBody.java | 3 +- .../request/CircleSendTransactionRequest.java | 6 +-- .../CircleAccountBalancesResponse.java | 4 +- .../response/CircleConfigurationResponse.java | 2 +- .../model/response/CircleDetailResponse.java | 2 +- .../circle/model/response/CircleError.java | 2 +- .../model/response/CircleListResponse.java | 2 +- .../response/CirclePaymentListResponse.java | 8 ++-- .../response/CirclePayoutListResponse.java | 8 ++-- .../response/CircleTransferListResponse.java | 8 ++-- .../observer}/circle/util/CircleAsset.java | 2 +- .../circle/util/NettyHttpClient.java | 2 +- ...dbcStellarPaymentStreamerCursorStore.java} | 7 ++-- ...oryStellarPaymentStreamerCursorStore.java} | 4 +- .../observer/stellar}/StellarPayment.java | 2 +- .../stellar}/StellarPaymentObserver.java | 16 +++++--- .../StellarPaymentStreamerCursorStore.java | 7 ++++ .../observer/stellar}/StellarTransaction.java | 2 +- .../paymentobserver/PaymentListener.java | 7 ---- .../PaymentStreamerCursorStore.java | 7 ---- .../PaymentOperationToEventListener.java | 4 +- .../Sep31DepositInfoGeneratorCircle.java | 4 +- .../circle/CirclePaymentServiceTest.kt | 17 +++++---- .../circle/CircleResponseErrorHandlerTest.kt | 1 + .../circle/ErrorHandlingTestCase.kt | 0 .../circle/StellarReconciliationTest.kt | 5 ++- .../model/CircleTransferSerializationTest.kt | 4 ++ .../utils/NettyHttpClientTest.kt | 2 +- .../anchor/platform/LogAppenderTest.kt | 23 +++++------ .../anchor/platform/PaymentConfigTest.kt | 6 +-- .../CirclePaymentObserverServiceTest.kt | 10 +++-- .../PaymentOperationToEventListenerTest.kt | 2 +- .../sep31/Sep31DepositInfoGeneratorTest.kt | 4 +- .../paymentservice/AccountCapabilitiesTest.kt | 28 ++++++++++++++ .../src/test/resources/mock_address.json | 0 .../mock_get_list_of_addresses_body.json | 0 ...ck_get_wire_deposit_instructions_body.json | 0 ...ellar_path_payment_response_page_body.json | 0 ...ck_stellar_payment_response_page_body.json | 0 .../mock_stellar_to_wallet_transfer.json | 0 .../mock_wallet_to_stellar_transfer.json | 0 .../mock_wallet_to_wallet_transfer.json | 0 .../resources/mock_wallet_to_wire_payout.json | 0 .../mock_wire_to_wallet_payment.json | 0 service-runner/build.gradle.kts | 2 - settings.gradle.kts | 3 -- 85 files changed, 224 insertions(+), 321 deletions(-) delete mode 100644 payment/build.gradle.kts delete mode 100644 payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/CirclePaymentConfig.java delete mode 100644 payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/StellarPaymentConfig.java delete mode 100644 payment/src/main/java/paymentservice/PaymentGateway.java delete mode 100644 payment/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt delete mode 100644 platform/src/main/java/org/stellar/anchor/payment/config/PropertyCirclePaymentConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/payment/config/PropertyStellarPaymentConfig.java rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/Account.java (98%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/Balance.java (94%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/DepositInstructions.java (98%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/DepositRequirements.java (99%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/Payment.java (92%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/PaymentHistory.java (85%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/PaymentNetwork.java (92%) rename {payment/src/main/java/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/common}/PaymentService.java (99%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/config/CirclePaymentConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/CircleGsonParsable.java (58%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver => payment/observer/circle}/CirclePaymentObserverService.java (94%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/CirclePaymentService.java (98%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/CircleResponseErrorHandler.java (86%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver => payment/observer/circle}/ObservedPayment.java (98%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/StellarReconciliation.java (92%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CircleBalance.java (79%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CircleBlockchainAddress.java (77%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver/circlemodels => payment/observer/circle/model}/CircleNotification.java (93%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CirclePayment.java (89%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CirclePaymentStatus.java (90%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CirclePayout.java (89%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CircleTransactionParty.java (93%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CircleTransfer.java (91%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CircleWallet.java (82%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/CircleWireDepositInstructions.java (81%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver/circlemodels => payment/observer/circle/model}/TransferNotificationBody.java (61%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/request/CircleSendTransactionRequest.java (83%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CircleAccountBalancesResponse.java (70%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CircleConfigurationResponse.java (83%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CircleDetailResponse.java (50%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CircleError.java (54%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CircleListResponse.java (72%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CirclePaymentListResponse.java (71%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CirclePayoutListResponse.java (71%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/model/response/CircleTransferListResponse.java (73%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/util/CircleAsset.java (92%) rename {payment/src/main/java/org/stellar/anchor/paymentservice => platform/src/main/java/org/stellar/anchor/platform/payment/observer}/circle/util/NettyHttpClient.java (96%) rename platform/src/main/java/org/stellar/anchor/platform/{service/JdbcPaymentStreamerCursorStore.java => payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java} (76%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver/MemoryPaymentStreamerCursorStore.java => payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java} (64%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver => payment/observer/stellar}/StellarPayment.java (85%) rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver => payment/observer/stellar}/StellarPaymentObserver.java (92%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java rename platform/src/main/java/org/stellar/anchor/platform/{paymentobserver => payment/observer/stellar}/StellarTransaction.java (85%) delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentListener.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentStreamerCursorStore.java rename {payment => platform}/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt (99%) rename {payment => platform}/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt (94%) rename {payment => platform}/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt (100%) rename {payment => platform}/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt (92%) rename {payment => platform}/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt (93%) rename {payment => platform}/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt (92%) rename platform/src/test/kotlin/org/stellar/anchor/platform/{paymentobserver => payment/observer}/CirclePaymentObserverServiceTest.kt (97%) create mode 100644 platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt rename {payment => platform}/src/test/resources/mock_address.json (100%) rename {payment => platform}/src/test/resources/mock_get_list_of_addresses_body.json (100%) rename {payment => platform}/src/test/resources/mock_get_wire_deposit_instructions_body.json (100%) rename {payment => platform}/src/test/resources/mock_stellar_path_payment_response_page_body.json (100%) rename {payment => platform}/src/test/resources/mock_stellar_payment_response_page_body.json (100%) rename {payment => platform}/src/test/resources/mock_stellar_to_wallet_transfer.json (100%) rename {payment => platform}/src/test/resources/mock_wallet_to_stellar_transfer.json (100%) rename {payment => platform}/src/test/resources/mock_wallet_to_wallet_transfer.json (100%) rename {payment => platform}/src/test/resources/mock_wallet_to_wire_payout.json (100%) rename {payment => platform}/src/test/resources/mock_wire_to_wallet_payment.json (100%) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5d2276d085..883b5bea99 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -25,8 +25,6 @@ jobs: ./gradlew clean build echo *** SEP test report *** cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/core/build/reports/tests/test/index.html - echo *** Payment test report *** - cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/payment/build/reports/tests/test/index.html echo *** Platform test report *** cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/platform/build/reports/tests/test/index.html echo *** Anchor reference server test report *** diff --git a/payment/build.gradle.kts b/payment/build.gradle.kts deleted file mode 100644 index 135b8a55c2..0000000000 --- a/payment/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - `java-library` - id("org.jetbrains.kotlin.jvm") version "1.6.10" -} - -dependencies { - api(libs.lombok) - - implementation(libs.log4j2.core) - implementation(libs.google.gson) - implementation(libs.reactor.core) - implementation(libs.commons.validator) - implementation(libs.reactor.netty) - implementation(libs.okhttp3) - - // Lombok should be used by all sub-projects to reduce Java verbosity - annotationProcessor(libs.lombok) - - // From jitpack.io - implementation(libs.java.stellar.sdk) - - // From projects - implementation(project(":api-schema")) - implementation(project(":core")) - - testImplementation(libs.okhttp3.mockserver) -} diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/CirclePaymentConfig.java b/payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/CirclePaymentConfig.java deleted file mode 100644 index 7fa90f3085..0000000000 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/CirclePaymentConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.paymentservice.circle.config; - -public interface CirclePaymentConfig { - String getName(); - - boolean isEnabled(); -} diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/StellarPaymentConfig.java b/payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/StellarPaymentConfig.java deleted file mode 100644 index 1e4d488d8b..0000000000 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/config/StellarPaymentConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.stellar.anchor.paymentservice.circle.config; - -/** - * This is a placeholder class. TODO: move to payment-stellar subproject under - * org.stellar.anchor.paymentservice.circle.config package. - */ -public interface StellarPaymentConfig { - String getName(); - - boolean isEnabled(); - - String getHorizonUrl(); - - String getSecretKey(); -} diff --git a/payment/src/main/java/paymentservice/PaymentGateway.java b/payment/src/main/java/paymentservice/PaymentGateway.java deleted file mode 100644 index 195aa760d8..0000000000 --- a/payment/src/main/java/paymentservice/PaymentGateway.java +++ /dev/null @@ -1,38 +0,0 @@ -package paymentservice; - -import java.util.HashMap; -import java.util.Map; - -public class PaymentGateway { - private final Map services; - - public PaymentGateway(Map services) { - this.services = services; - } - - public String getServices() { - // TODO: This is a demo function. Remove this method. - return String.join(",", services.keySet()); - } - - public PaymentService getService(String serviceName) { - return services.get(serviceName); - } - - public static class Builder { - HashMap map = new HashMap<>(); - - public PaymentGateway build() { - return new PaymentGateway(map); - } - - public Builder add(PaymentService service) { - if (map.get(service.getName()) != null) { - throw new RuntimeException( - "The serivce with the name [" + service.getName() + "] already exists."); - } - map.put(service.getName(), service); - return this; - } - } -} diff --git a/payment/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt b/payment/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt deleted file mode 100644 index 165c552292..0000000000 --- a/payment/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt +++ /dev/null @@ -1,26 +0,0 @@ -package paymentservice - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals -import org.junit.jupiter.api.Test - -internal class AccountCapabilitiesTest { - @Test - fun testEquals() { - var capabilities1 = paymentservice.Account.Capabilities() - var capabilities2 = paymentservice.Account.Capabilities() - assertEquals(capabilities1, capabilities2) - - capabilities1 = paymentservice.Account.Capabilities() - capabilities2 = paymentservice.Account.Capabilities(paymentservice.PaymentNetwork.STELLAR) - assertNotEquals(capabilities1, capabilities2) - - capabilities1 = paymentservice.Account.Capabilities(paymentservice.PaymentNetwork.CIRCLE) - capabilities2 = paymentservice.Account.Capabilities(paymentservice.PaymentNetwork.STELLAR) - assertNotEquals(capabilities1, capabilities2) - - capabilities1 = paymentservice.Account.Capabilities(paymentservice.PaymentNetwork.BANK_WIRE) - capabilities2 = paymentservice.Account.Capabilities(paymentservice.PaymentNetwork.BANK_WIRE) - assertEquals(capabilities1, capabilities2) - } -} diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index d229c952c2..0fdf2dab48 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -37,7 +37,6 @@ dependencies { // From projects implementation(project(":api-schema")) implementation(project(":core")) - implementation(project(":payment")) implementation(project(":anchor-reference-server")) testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/platform/src/main/java/org/stellar/anchor/payment/config/PropertyCirclePaymentConfig.java b/platform/src/main/java/org/stellar/anchor/payment/config/PropertyCirclePaymentConfig.java deleted file mode 100644 index 183824d99b..0000000000 --- a/platform/src/main/java/org/stellar/anchor/payment/config/PropertyCirclePaymentConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.stellar.anchor.payment.config; - -import lombok.Data; -import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig; - -@Data -public class PropertyCirclePaymentConfig implements CirclePaymentConfig { - private String name = ""; - private boolean enabled = false; -} diff --git a/platform/src/main/java/org/stellar/anchor/payment/config/PropertyStellarPaymentConfig.java b/platform/src/main/java/org/stellar/anchor/payment/config/PropertyStellarPaymentConfig.java deleted file mode 100644 index 8f64ddfb46..0000000000 --- a/platform/src/main/java/org/stellar/anchor/payment/config/PropertyStellarPaymentConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.stellar.anchor.payment.config; - -import lombok.Data; -import org.stellar.anchor.paymentservice.circle.config.StellarPaymentConfig; - -@Data -public class PropertyStellarPaymentConfig implements StellarPaymentConfig { - private String name = ""; - private boolean enabled = false; - private String horizonUrl; - private String secretKey; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java index 9cc5f462a1..3fa1be0dd6 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java @@ -4,13 +4,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.stellar.anchor.config.*; -import org.stellar.anchor.config.CirclePaymentObserverConfig; -import org.stellar.anchor.config.EventConfig; -import org.stellar.anchor.config.KafkaConfig; -import org.stellar.anchor.config.SqsConfig; -import org.stellar.anchor.payment.config.PropertyCirclePaymentConfig; -import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig; import org.stellar.anchor.platform.config.*; +import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; @Configuration public class ConfigManagementConfig { @@ -68,18 +63,17 @@ CircleConfig circleConfig() { return new PropertyCircleConfig(); } - @Bean - @ConfigurationProperties(prefix = "payment-gateway.circle") - CirclePaymentConfig circlePaymentConfig() { - return new PropertyCirclePaymentConfig(); - } - @Bean @ConfigurationProperties(prefix = "circle-payment-observer") CirclePaymentObserverConfig circlePaymentObserverConfig() { return new PropertyCirclePaymentObserverConfig(); } + @Bean + CirclePaymentConfig circlePaymentConfig() { + return new CirclePaymentConfig(); + } + @Bean @ConfigurationProperties(prefix = "event") EventConfig eventConfig(KafkaConfig kafkaConfig, SqsConfig sqsConfig) { diff --git a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java b/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java index 095412c275..ae2da586b2 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java @@ -11,10 +11,10 @@ import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.CirclePaymentObserverConfig; import org.stellar.anchor.horizon.Horizon; -import org.stellar.anchor.platform.paymentobserver.CirclePaymentObserverService; -import org.stellar.anchor.platform.paymentobserver.PaymentListener; -import org.stellar.anchor.platform.paymentobserver.PaymentStreamerCursorStore; -import org.stellar.anchor.platform.paymentobserver.StellarPaymentObserver; +import org.stellar.anchor.platform.payment.observer.PaymentListener; +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService; +import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentObserver; +import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentStreamerCursorStore; @Configuration public class PaymentConfig { @@ -22,7 +22,7 @@ public class PaymentConfig { public StellarPaymentObserver stellarPaymentObserverService( AssetService assetService, List paymentListeners, - PaymentStreamerCursorStore paymentStreamerCursorStore, + StellarPaymentStreamerCursorStore stellarPaymentStreamerCursorStore, AppConfig appConfig) throws ServerErrorException { // validate assetService @@ -44,7 +44,7 @@ public StellarPaymentObserver stellarPaymentObserverService( } // validate paymentStreamerCursorStore - if (paymentStreamerCursorStore == null) { + if (stellarPaymentStreamerCursorStore == null) { throw new ServerErrorException("Payment streamer cursor store cannot be empty."); } @@ -56,7 +56,7 @@ public StellarPaymentObserver stellarPaymentObserverService( StellarPaymentObserver stellarPaymentObserverService = StellarPaymentObserver.builder() .horizonServer(appConfig.getHorizonUrl()) - .paymentTokenStore(paymentStreamerCursorStore) + .paymentTokenStore(stellarPaymentStreamerCursorStore) .observers(paymentListeners) .accounts( stellarAssets.stream() diff --git a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java b/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java index 89caf4fb3a..57af887fcb 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java @@ -17,9 +17,9 @@ import org.stellar.anchor.filter.JwtTokenFilter; import org.stellar.anchor.filter.NoneFilter; import org.stellar.anchor.horizon.Horizon; -import org.stellar.anchor.paymentservice.circle.CirclePaymentService; -import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig; import org.stellar.anchor.platform.data.*; +import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentService; import org.stellar.anchor.platform.service.*; import org.stellar.anchor.sep1.Sep1Service; import org.stellar.anchor.sep10.Sep10Service; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java index 2cdeab2dca..342a2f3d12 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java @@ -14,7 +14,7 @@ import org.stellar.anchor.api.exception.ServerErrorException; import org.stellar.anchor.api.exception.UnprocessableEntityException; import org.stellar.anchor.api.sep.SepExceptionResponse; -import org.stellar.anchor.platform.paymentobserver.CirclePaymentObserverService; +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService; import shadow.com.google.common.reflect.TypeToken; @RestController diff --git a/payment/src/main/java/paymentservice/Account.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java similarity index 98% rename from payment/src/main/java/paymentservice/Account.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java index 5eec9aa70d..2114513924 100644 --- a/payment/src/main/java/paymentservice/Account.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import java.util.*; import lombok.Data; diff --git a/payment/src/main/java/paymentservice/Balance.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java similarity index 94% rename from payment/src/main/java/paymentservice/Balance.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java index c3f8f90e41..7a555625bd 100644 --- a/payment/src/main/java/paymentservice/Balance.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import lombok.Data; diff --git a/payment/src/main/java/paymentservice/DepositInstructions.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java similarity index 98% rename from payment/src/main/java/paymentservice/DepositInstructions.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java index 920208bda4..0fb49efec0 100644 --- a/payment/src/main/java/paymentservice/DepositInstructions.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import java.util.Map; import lombok.Data; diff --git a/payment/src/main/java/paymentservice/DepositRequirements.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java similarity index 99% rename from payment/src/main/java/paymentservice/DepositRequirements.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java index e6aa558ae6..5785837dc4 100644 --- a/payment/src/main/java/paymentservice/DepositRequirements.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import lombok.Data; import reactor.util.annotation.Nullable; diff --git a/payment/src/main/java/paymentservice/Payment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java similarity index 92% rename from payment/src/main/java/paymentservice/Payment.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java index 9df6bfbadb..de7c77cc04 100644 --- a/payment/src/main/java/paymentservice/Payment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import java.time.Instant; import java.util.Map; diff --git a/payment/src/main/java/paymentservice/PaymentHistory.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java similarity index 85% rename from payment/src/main/java/paymentservice/PaymentHistory.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java index 8694dac66b..7799ec0076 100644 --- a/payment/src/main/java/paymentservice/PaymentHistory.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import java.util.ArrayList; import java.util.List; diff --git a/payment/src/main/java/paymentservice/PaymentNetwork.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java similarity index 92% rename from payment/src/main/java/paymentservice/PaymentNetwork.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java index b72387fd3a..f5b060145d 100644 --- a/payment/src/main/java/paymentservice/PaymentNetwork.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import lombok.Getter; diff --git a/payment/src/main/java/paymentservice/PaymentService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java similarity index 99% rename from payment/src/main/java/paymentservice/PaymentService.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java index 8de20d9b2d..70f8c3f49e 100644 --- a/payment/src/main/java/paymentservice/PaymentService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java @@ -1,4 +1,4 @@ -package paymentservice; +package org.stellar.anchor.platform.payment.common; import java.math.BigDecimal; import org.stellar.anchor.api.exception.HttpException; diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/config/CirclePaymentConfig.java b/platform/src/main/java/org/stellar/anchor/platform/payment/config/CirclePaymentConfig.java new file mode 100644 index 0000000000..bfaeaf24a1 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/config/CirclePaymentConfig.java @@ -0,0 +1,9 @@ +package org.stellar.anchor.platform.payment.config; + +import lombok.Data; + +@Data +public class CirclePaymentConfig { + private String name = ""; + private boolean enabled = false; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java new file mode 100644 index 0000000000..9488485099 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java @@ -0,0 +1,9 @@ +package org.stellar.anchor.platform.payment.observer; + +import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; + +public interface PaymentListener { + void onReceived(ObservedPayment payment); + + void onSent(ObservedPayment payment); +} diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/CircleGsonParsable.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleGsonParsable.java similarity index 58% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/CircleGsonParsable.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleGsonParsable.java index f5b6f87373..aee6b945b0 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/CircleGsonParsable.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleGsonParsable.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.paymentservice.circle; +package org.stellar.anchor.platform.payment.observer.circle; import com.google.gson.Gson; -import org.stellar.anchor.paymentservice.circle.model.CirclePayment; -import org.stellar.anchor.paymentservice.circle.model.CirclePayout; -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer; +import org.stellar.anchor.platform.payment.observer.circle.model.CirclePayment; +import org.stellar.anchor.platform.payment.observer.circle.model.CirclePayout; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer; import org.stellar.anchor.util.GsonUtils; public interface CircleGsonParsable { diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/CirclePaymentObserverService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java similarity index 94% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/CirclePaymentObserverService.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java index 9e74a9ffb0..665551ce50 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/CirclePaymentObserverService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver; +package org.stellar.anchor.platform.payment.observer.circle; import static org.stellar.anchor.util.MathHelper.*; @@ -18,12 +18,13 @@ import org.stellar.anchor.api.exception.UnprocessableEntityException; import org.stellar.anchor.config.CirclePaymentObserverConfig; import org.stellar.anchor.horizon.Horizon; -import org.stellar.anchor.paymentservice.circle.model.CirclePaymentStatus; -import org.stellar.anchor.paymentservice.circle.model.CircleTransactionParty; -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer; -import org.stellar.anchor.paymentservice.circle.util.CircleAsset; -import org.stellar.anchor.platform.paymentobserver.circlemodels.CircleNotification; -import org.stellar.anchor.platform.paymentobserver.circlemodels.TransferNotificationBody; +import org.stellar.anchor.platform.payment.observer.PaymentListener; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleNotification; +import org.stellar.anchor.platform.payment.observer.circle.model.CirclePaymentStatus; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer; +import org.stellar.anchor.platform.payment.observer.circle.model.TransferNotificationBody; +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset; import org.stellar.anchor.util.GsonUtils; import org.stellar.anchor.util.Log; import org.stellar.anchor.util.StellarNetworkHelper; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/CirclePaymentService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java similarity index 98% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/CirclePaymentService.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java index 342ae316a9..3bfeb28803 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/CirclePaymentService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle; +package org.stellar.anchor.platform.payment.observer.circle; import static org.stellar.anchor.util.StellarNetworkHelper.toStellarNetwork; @@ -12,15 +12,15 @@ import org.stellar.anchor.api.exception.HttpException; import org.stellar.anchor.config.CircleConfig; import org.stellar.anchor.horizon.Horizon; -import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig; -import org.stellar.anchor.paymentservice.circle.model.*; -import org.stellar.anchor.paymentservice.circle.model.request.CircleSendTransactionRequest; -import org.stellar.anchor.paymentservice.circle.model.response.*; -import org.stellar.anchor.paymentservice.circle.util.CircleAsset; -import org.stellar.anchor.paymentservice.circle.util.NettyHttpClient; +import org.stellar.anchor.platform.payment.common.*; +import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; +import org.stellar.anchor.platform.payment.observer.circle.model.*; +import org.stellar.anchor.platform.payment.observer.circle.model.request.CircleSendTransactionRequest; +import org.stellar.anchor.platform.payment.observer.circle.model.response.*; +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset; +import org.stellar.anchor.platform.payment.observer.circle.util.NettyHttpClient; import org.stellar.sdk.Network; import org.stellar.sdk.Server; -import paymentservice.*; import reactor.core.publisher.Mono; import reactor.netty.ByteBufMono; import reactor.netty.http.client.HttpClient; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandler.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java similarity index 86% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandler.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java index 011ae87e2e..d0f9c8ca74 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.paymentservice.circle; +package org.stellar.anchor.platform.payment.observer.circle; import java.util.function.BiFunction; import org.jetbrains.annotations.NotNull; import org.stellar.anchor.api.exception.HttpException; -import org.stellar.anchor.paymentservice.circle.model.response.CircleError; +import org.stellar.anchor.platform.payment.observer.circle.model.response.CircleError; import reactor.core.publisher.Mono; import reactor.netty.ByteBufMono; import reactor.netty.http.client.HttpClientResponse; diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/ObservedPayment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/ObservedPayment.java similarity index 98% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/ObservedPayment.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/ObservedPayment.java index af5224e74d..9d621de859 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/ObservedPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/ObservedPayment.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver; +package org.stellar.anchor.platform.payment.observer.circle; import com.google.gson.annotations.SerializedName; import lombok.Builder; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/StellarReconciliation.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java similarity index 92% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/StellarReconciliation.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java index 1dece536e6..75838f9d67 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/StellarReconciliation.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java @@ -1,12 +1,12 @@ -package org.stellar.anchor.paymentservice.circle; +package org.stellar.anchor.platform.payment.observer.circle; import static org.stellar.anchor.util.MathHelper.decimal; import java.math.BigDecimal; import java.util.List; import org.stellar.anchor.api.exception.HttpException; -import org.stellar.anchor.paymentservice.circle.model.CircleTransactionParty; -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer; import org.stellar.sdk.Server; import org.stellar.sdk.responses.operations.OperationResponse; import org.stellar.sdk.responses.operations.PathPaymentBaseOperationResponse; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleBalance.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java similarity index 79% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleBalance.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java index 50b1bee483..108eb4bd4b 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleBalance.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import lombok.Data; -import org.stellar.anchor.paymentservice.circle.util.CircleAsset; -import paymentservice.Balance; -import paymentservice.PaymentNetwork; +import org.stellar.anchor.platform.payment.common.Balance; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset; import reactor.util.annotation.NonNull; import reactor.util.annotation.Nullable; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleBlockchainAddress.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java similarity index 77% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleBlockchainAddress.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java index 4c3ff7663b..251851cb4f 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleBlockchainAddress.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java @@ -1,10 +1,10 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import lombok.Data; -import org.stellar.anchor.paymentservice.circle.util.CircleAsset; +import org.stellar.anchor.platform.payment.common.DepositInstructions; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset; import org.stellar.sdk.Network; -import paymentservice.DepositInstructions; -import paymentservice.PaymentNetwork; import reactor.util.annotation.NonNull; import reactor.util.annotation.Nullable; diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/circlemodels/CircleNotification.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java similarity index 93% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/circlemodels/CircleNotification.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java index 3f69e3fdfb..b5fc70094c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/circlemodels/CircleNotification.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver.circlemodels; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.annotations.SerializedName; import java.time.Instant; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePayment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java similarity index 89% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePayment.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java index b497a996d9..6d3a038cf5 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.*; import java.lang.reflect.Type; @@ -6,10 +6,10 @@ import java.util.List; import java.util.Map; import lombok.Data; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.Payment; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; import org.stellar.anchor.util.GsonUtils; -import paymentservice.Account; -import paymentservice.Payment; -import paymentservice.PaymentNetwork; import shadow.com.google.common.reflect.TypeToken; @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePaymentStatus.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java similarity index 90% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePaymentStatus.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java index 33ddc22809..9885ab355b 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePaymentStatus.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java @@ -1,8 +1,8 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.annotations.SerializedName; import lombok.Getter; -import paymentservice.Payment; +import org.stellar.anchor.platform.payment.common.Payment; @Getter public enum CirclePaymentStatus { diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePayout.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java similarity index 89% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePayout.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java index 39fcc0e74b..6c91ee33ef 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CirclePayout.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.*; import com.google.gson.annotations.SerializedName; @@ -6,10 +6,10 @@ import java.time.Instant; import java.util.Map; import lombok.Data; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.Payment; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; import org.stellar.anchor.util.GsonUtils; -import paymentservice.Account; -import paymentservice.Payment; -import paymentservice.PaymentNetwork; import shadow.com.google.common.reflect.TypeToken; @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleTransactionParty.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java similarity index 93% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleTransactionParty.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java index e7696d1db4..d9ced13ec4 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleTransactionParty.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java @@ -1,8 +1,8 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.annotations.SerializedName; -import paymentservice.Account; -import paymentservice.PaymentNetwork; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; import reactor.util.annotation.Nullable; @lombok.Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleTransfer.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java similarity index 91% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleTransfer.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java index b96da48016..1a043ff2ff 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleTransfer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java @@ -1,13 +1,13 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.*; import java.lang.reflect.Type; import java.time.Instant; import java.util.Map; import lombok.Data; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.Payment; import org.stellar.anchor.util.GsonUtils; -import paymentservice.Account; -import paymentservice.Payment; import shadow.com.google.common.reflect.TypeToken; @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleWallet.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java similarity index 82% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleWallet.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java index 02a331546e..7c19dc26f8 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleWallet.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java @@ -1,13 +1,13 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import lombok.Data; -import org.stellar.anchor.paymentservice.circle.util.CircleAsset; -import paymentservice.Account; -import paymentservice.DepositInstructions; -import paymentservice.PaymentNetwork; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.DepositInstructions; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset; @Data public class CircleWallet { diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleWireDepositInstructions.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java similarity index 81% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleWireDepositInstructions.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java index 15f9d3c240..28590b79bc 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/CircleWireDepositInstructions.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java @@ -1,13 +1,13 @@ -package org.stellar.anchor.paymentservice.circle.model; +package org.stellar.anchor.platform.payment.observer.circle.model; import com.google.gson.Gson; import java.lang.reflect.Type; import java.util.Map; import lombok.Data; -import org.stellar.anchor.paymentservice.circle.util.CircleAsset; +import org.stellar.anchor.platform.payment.common.DepositInstructions; +import org.stellar.anchor.platform.payment.common.PaymentNetwork; +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset; import org.stellar.anchor.util.GsonUtils; -import paymentservice.DepositInstructions; -import paymentservice.PaymentNetwork; import shadow.com.google.common.reflect.TypeToken; @Data diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/circlemodels/TransferNotificationBody.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java similarity index 61% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/circlemodels/TransferNotificationBody.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java index 847eb0e597..882838f28c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/circlemodels/TransferNotificationBody.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java @@ -1,8 +1,7 @@ -package org.stellar.anchor.platform.paymentobserver.circlemodels; +package org.stellar.anchor.platform.payment.observer.circle.model; import java.util.Map; import lombok.Data; -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer; @Data public class TransferNotificationBody { diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/request/CircleSendTransactionRequest.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java similarity index 83% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/request/CircleSendTransactionRequest.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java index ba1f9efcf8..78f0599c13 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/request/CircleSendTransactionRequest.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.paymentservice.circle.model.request; +package org.stellar.anchor.platform.payment.observer.circle.model.request; import java.util.HashMap; import lombok.Data; -import org.stellar.anchor.paymentservice.circle.model.CircleBalance; -import org.stellar.anchor.paymentservice.circle.model.CircleTransactionParty; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty; @Data public class CircleSendTransactionRequest { diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleAccountBalancesResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java similarity index 70% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleAccountBalancesResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java index 56e35ae226..fe6d67065e 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleAccountBalancesResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import java.util.List; import lombok.Data; import lombok.EqualsAndHashCode; -import org.stellar.anchor.paymentservice.circle.model.CircleBalance; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance; @EqualsAndHashCode(callSuper = true) @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleConfigurationResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java similarity index 83% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleConfigurationResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java index 1fb8e946e5..8e05d47bb5 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleConfigurationResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleDetailResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java similarity index 50% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleDetailResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java index e4db60b07b..a880f28d0c 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleDetailResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import lombok.Data; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleError.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java similarity index 54% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleError.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java index 7bd332353a..458521dd12 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleError.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import lombok.Data; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java similarity index 72% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleListResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java index f58eac39d5..1a82b0e600 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleListResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import java.util.List; import lombok.Data; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CirclePaymentListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java similarity index 71% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CirclePaymentListResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java index db4f67d4a6..3326738342 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CirclePaymentListResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java @@ -1,10 +1,10 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import lombok.Data; import lombok.EqualsAndHashCode; -import org.stellar.anchor.paymentservice.circle.model.CirclePayment; -import paymentservice.Account; -import paymentservice.PaymentHistory; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.PaymentHistory; +import org.stellar.anchor.platform.payment.observer.circle.model.CirclePayment; @EqualsAndHashCode(callSuper = true) @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CirclePayoutListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java similarity index 71% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CirclePayoutListResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java index d9fb75d25a..938200bde4 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CirclePayoutListResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java @@ -1,10 +1,10 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import lombok.Data; import lombok.EqualsAndHashCode; -import org.stellar.anchor.paymentservice.circle.model.CirclePayout; -import paymentservice.Account; -import paymentservice.PaymentHistory; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.PaymentHistory; +import org.stellar.anchor.platform.payment.observer.circle.model.CirclePayout; @EqualsAndHashCode(callSuper = true) @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleTransferListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java similarity index 73% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleTransferListResponse.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java index 980c49b5ba..7847159ab0 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/model/response/CircleTransferListResponse.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java @@ -1,10 +1,10 @@ -package org.stellar.anchor.paymentservice.circle.model.response; +package org.stellar.anchor.platform.payment.observer.circle.model.response; import lombok.Data; import lombok.EqualsAndHashCode; -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer; -import paymentservice.Account; -import paymentservice.PaymentHistory; +import org.stellar.anchor.platform.payment.common.Account; +import org.stellar.anchor.platform.payment.common.PaymentHistory; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer; @EqualsAndHashCode(callSuper = true) @Data diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/util/CircleAsset.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java similarity index 92% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/util/CircleAsset.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java index 302d9dad14..e3736947b6 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/util/CircleAsset.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.util; +package org.stellar.anchor.platform.payment.observer.circle.util; import java.util.List; import org.stellar.sdk.Network; diff --git a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/util/NettyHttpClient.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java similarity index 96% rename from payment/src/main/java/org/stellar/anchor/paymentservice/circle/util/NettyHttpClient.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java index d9cbf5633b..ec02f61968 100644 --- a/payment/src/main/java/org/stellar/anchor/paymentservice/circle/util/NettyHttpClient.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.paymentservice.circle.util; +package org.stellar.anchor.platform.payment.observer.circle.util; import io.netty.channel.ChannelOption; import io.netty.handler.codec.http.HttpHeaderNames; diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/JdbcPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java similarity index 76% rename from platform/src/main/java/org/stellar/anchor/platform/service/JdbcPaymentStreamerCursorStore.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java index 39719b3583..49fbe7b464 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/JdbcPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java @@ -1,16 +1,15 @@ -package org.stellar.anchor.platform.service; +package org.stellar.anchor.platform.payment.observer.stellar; import java.util.Optional; import org.springframework.stereotype.Component; import org.stellar.anchor.platform.data.PaymentStreamerCursor; import org.stellar.anchor.platform.data.PaymentStreamerCursorRepo; -import org.stellar.anchor.platform.paymentobserver.PaymentStreamerCursorStore; @Component -public class JdbcPaymentStreamerCursorStore implements PaymentStreamerCursorStore { +public class JdbcStellarPaymentStreamerCursorStore implements StellarPaymentStreamerCursorStore { private final PaymentStreamerCursorRepo repo; - JdbcPaymentStreamerCursorStore(PaymentStreamerCursorRepo repo) { + JdbcStellarPaymentStreamerCursorStore(PaymentStreamerCursorRepo repo) { this.repo = repo; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/MemoryPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java similarity index 64% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/MemoryPaymentStreamerCursorStore.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java index 47eea2adad..64c413fd27 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/MemoryPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.platform.paymentobserver; +package org.stellar.anchor.platform.payment.observer.stellar; import java.util.HashMap; import java.util.Map; -public class MemoryPaymentStreamerCursorStore implements PaymentStreamerCursorStore { +public class MemoryStellarPaymentStreamerCursorStore implements StellarPaymentStreamerCursorStore { Map mapTokens = new HashMap<>(); @Override diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarPayment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java similarity index 85% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarPayment.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java index 77e98b0535..befd6266c3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver; +package org.stellar.anchor.platform.payment.observer.stellar; import com.google.gson.annotations.SerializedName; import lombok.Builder; diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java similarity index 92% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarPaymentObserver.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java index 5bb337e0fc..089c38f7b7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver; +package org.stellar.anchor.platform.payment.observer.stellar; import static org.stellar.anchor.api.platform.HealthCheckStatus.GREEN; import static org.stellar.anchor.api.platform.HealthCheckStatus.RED; @@ -16,6 +16,8 @@ import org.stellar.anchor.api.platform.HealthCheckResult; import org.stellar.anchor.api.platform.HealthCheckStatus; import org.stellar.anchor.healthcheck.HealthCheckable; +import org.stellar.anchor.platform.payment.observer.PaymentListener; +import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; import org.stellar.anchor.util.Log; import org.stellar.sdk.Server; import org.stellar.sdk.requests.EventListener; @@ -32,14 +34,14 @@ public class StellarPaymentObserver implements HealthCheckable { final SortedSet accounts; final Set observers; final List> streams; - final PaymentStreamerCursorStore paymentStreamerCursorStore; + final StellarPaymentStreamerCursorStore paymentStreamerCursorStore; final Map, String> mapStreamToAccount = new HashMap<>(); StellarPaymentObserver( String horizonServer, SortedSet accounts, Set observers, - PaymentStreamerCursorStore paymentStreamerCursorStore) { + StellarPaymentStreamerCursorStore paymentStreamerCursorStore) { this.server = new Server(horizonServer); this.observers = observers; this.accounts = accounts; @@ -143,7 +145,8 @@ public static class Builder { String horizonServer = "https://horizon-testnet.stellar.org"; SortedSet accounts = new TreeSet<>(); Set observers = new HashSet<>(); - PaymentStreamerCursorStore paymentStreamerCursorStore = new MemoryPaymentStreamerCursorStore(); + StellarPaymentStreamerCursorStore paymentStreamerCursorStore = + new MemoryStellarPaymentStreamerCursorStore(); public Builder() {} @@ -162,8 +165,9 @@ public Builder observers(List observers) { return this; } - public Builder paymentTokenStore(PaymentStreamerCursorStore paymentStreamerCursorStore) { - this.paymentStreamerCursorStore = paymentStreamerCursorStore; + public Builder paymentTokenStore( + StellarPaymentStreamerCursorStore stellarPaymentStreamerCursorStore) { + this.paymentStreamerCursorStore = stellarPaymentStreamerCursorStore; return this; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java new file mode 100644 index 0000000000..9e4ca03c62 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java @@ -0,0 +1,7 @@ +package org.stellar.anchor.platform.payment.observer.stellar; + +public interface StellarPaymentStreamerCursorStore { + void save(String account, String cursor); + + String load(String account); +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java similarity index 85% rename from platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarTransaction.java rename to platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java index e8f5d22f3c..ec3facd431 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/StellarTransaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver; +package org.stellar.anchor.platform.payment.observer.stellar; import com.google.gson.annotations.SerializedName; import java.time.Instant; diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentListener.java b/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentListener.java deleted file mode 100644 index 981af78c08..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.platform.paymentobserver; - -public interface PaymentListener { - void onReceived(ObservedPayment payment); - - void onSent(ObservedPayment payment); -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentStreamerCursorStore.java deleted file mode 100644 index fabef77341..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/paymentobserver/PaymentStreamerCursorStore.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.platform.paymentobserver; - -public interface PaymentStreamerCursorStore { - void save(String account, String cursor); - - String load(String account); -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index 175d7ceae9..32c62ebc1e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -20,8 +20,8 @@ import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.event.EventPublishService; import org.stellar.anchor.event.models.*; -import org.stellar.anchor.platform.paymentobserver.ObservedPayment; -import org.stellar.anchor.platform.paymentobserver.PaymentListener; +import org.stellar.anchor.platform.payment.observer.PaymentListener; +import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.util.GsonUtils; diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java index 91e8d38cf1..a96b46ceac 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java @@ -1,8 +1,8 @@ package org.stellar.anchor.platform.service; import org.stellar.anchor.api.sep.sep31.Sep31DepositInfo; -import org.stellar.anchor.paymentservice.circle.CirclePaymentService; -import org.stellar.anchor.paymentservice.circle.model.CircleBlockchainAddress; +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentService; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleBlockchainAddress; import org.stellar.anchor.sep31.Sep31DepositInfoGenerator; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.util.MemoHelper; diff --git a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt similarity index 99% rename from payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt index 8aeb212b67..68deec2bdb 100644 --- a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt @@ -28,20 +28,21 @@ import org.skyscreamer.jsonassert.JSONAssert import org.stellar.anchor.api.exception.HttpException import org.stellar.anchor.config.CircleConfig import org.stellar.anchor.horizon.Horizon -import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig -import org.stellar.anchor.paymentservice.circle.model.CircleBlockchainAddress -import org.stellar.anchor.paymentservice.circle.model.CircleWallet -import org.stellar.anchor.paymentservice.circle.model.CircleWireDepositInstructions -import org.stellar.anchor.paymentservice.circle.model.response.CircleDetailResponse -import org.stellar.anchor.paymentservice.circle.model.response.CircleListResponse -import org.stellar.anchor.paymentservice.circle.util.CircleAsset +import org.stellar.anchor.platform.payment.common.* +import org.stellar.anchor.platform.payment.config.CirclePaymentConfig +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentService +import org.stellar.anchor.platform.payment.observer.circle.model.CircleBlockchainAddress +import org.stellar.anchor.platform.payment.observer.circle.model.CircleWallet +import org.stellar.anchor.platform.payment.observer.circle.model.CircleWireDepositInstructions +import org.stellar.anchor.platform.payment.observer.circle.model.response.CircleDetailResponse +import org.stellar.anchor.platform.payment.observer.circle.model.response.CircleListResponse +import org.stellar.anchor.platform.payment.observer.circle.util.CircleAsset import org.stellar.anchor.util.FileUtil import org.stellar.anchor.util.GsonUtils import org.stellar.sdk.Network import org.stellar.sdk.Server import org.stellar.sdk.responses.Page import org.stellar.sdk.responses.operations.OperationResponse -import paymentservice.* import reactor.core.publisher.Mono import shadow.com.google.common.reflect.TypeToken diff --git a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt similarity index 94% rename from payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt index 5cb19f9126..2f04bdcea9 100644 --- a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt @@ -11,6 +11,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.HttpException +import org.stellar.anchor.platform.payment.observer.circle.CircleResponseErrorHandler import org.stellar.anchor.util.GsonUtils import reactor.core.publisher.Mono import reactor.netty.ByteBufMono diff --git a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt similarity index 100% rename from payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt rename to platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt diff --git a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt similarity index 92% rename from payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt index df93b2e737..a87bcbb6e5 100644 --- a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt @@ -11,8 +11,9 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer -import org.stellar.anchor.paymentservice.circle.util.NettyHttpClient +import org.stellar.anchor.platform.payment.observer.circle.StellarReconciliation +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer +import org.stellar.anchor.platform.payment.observer.circle.util.NettyHttpClient import org.stellar.anchor.util.GsonUtils import org.stellar.sdk.Server import reactor.netty.http.client.HttpClient diff --git a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt similarity index 93% rename from payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt index 61af36c4fb..4bb9880d04 100644 --- a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt @@ -8,6 +8,10 @@ import kotlin.test.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.skyscreamer.jsonassert.JSONAssert +import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance +import org.stellar.anchor.platform.payment.observer.circle.model.CirclePaymentStatus +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer import org.stellar.anchor.util.GsonUtils class CircleTransferSerializationTest { diff --git a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt similarity index 92% rename from payment/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt index 23e10aaa75..2c66fc9a05 100644 --- a/payment/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt @@ -2,7 +2,7 @@ package org.stellar.anchor.paymentservice.utils import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -import org.stellar.anchor.paymentservice.circle.util.NettyHttpClient +import org.stellar.anchor.platform.payment.observer.circle.util.NettyHttpClient class NettyHttpClientTest { @Test diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt index 884e09dc14..203334091d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt @@ -39,18 +39,19 @@ class LogAppenderTest { @ParameterizedTest @CsvSource( - value = - [ - "error,error_message,ERROR", - "warn,warn_message,WARN", - "info,info_message,INFO", - "debug,debug_message,DEBUG", - "trace,trace_message,TRACE", - ]) + value = + [ + "error,error_message,ERROR", + "warn,warn_message,WARN", + "info,info_message,INFO", + "debug,debug_message,DEBUG", + "trace,trace_message,TRACE", + ] + ) fun test_logger_outputAccurateLoggerName( - methodName: String, - wantMessage: String, - wantLevelName: String + methodName: String, + wantMessage: String, + wantLevelName: String ) { when (methodName) { "error" -> Log.error(wantMessage) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt index 31f2a62a9c..963505e05b 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt @@ -9,8 +9,8 @@ import org.stellar.anchor.api.exception.ServerErrorException import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig -import org.stellar.anchor.platform.paymentobserver.PaymentListener -import org.stellar.anchor.platform.paymentobserver.PaymentStreamerCursorStore +import org.stellar.anchor.platform.payment.observer.PaymentListener +import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentStreamerCursorStore class PaymentConfigTest { @Test @@ -71,7 +71,7 @@ class PaymentConfigTest { assertEquals("Payment streamer cursor store cannot be empty.", ex.message) // appConfig is null - val mockPaymentStreamerCursorStore = mockk() + val mockPaymentStreamerCursorStore = mockk() every { mockPaymentStreamerCursorStore.load(any()) } returns null ex = assertThrows { diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/paymentobserver/CirclePaymentObserverServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt similarity index 97% rename from platform/src/test/kotlin/org/stellar/anchor/platform/paymentobserver/CirclePaymentObserverServiceTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt index 14df1b46de..ddc7b02a5f 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/paymentobserver/CirclePaymentObserverServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.paymentobserver +package org.stellar.anchor.platform.payment.observer import io.mockk.* import io.mockk.impl.annotations.MockK @@ -16,9 +16,11 @@ import org.stellar.anchor.api.exception.BadRequestException import org.stellar.anchor.api.exception.UnprocessableEntityException import org.stellar.anchor.config.CirclePaymentObserverConfig import org.stellar.anchor.horizon.Horizon -import org.stellar.anchor.paymentservice.circle.model.CircleBalance -import org.stellar.anchor.paymentservice.circle.model.CircleTransactionParty -import org.stellar.anchor.paymentservice.circle.model.CircleTransfer +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService +import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment +import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty +import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer import org.stellar.sdk.* import org.stellar.sdk.responses.Page import org.stellar.sdk.responses.TransactionResponse diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt index 7a891a05fa..d2a21d1dce 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt @@ -16,7 +16,7 @@ import org.stellar.anchor.event.EventPublishService import org.stellar.anchor.event.models.* import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.data.JdbcSep31TransactionStore -import org.stellar.anchor.platform.paymentobserver.ObservedPayment +import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment class PaymentOperationToEventListenerTest { @MockK(relaxed = true) private lateinit var transactionStore: JdbcSep31TransactionStore diff --git a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt index dde05daf74..fea8854357 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -22,9 +22,9 @@ import org.stellar.anchor.config.CircleConfig import org.stellar.anchor.config.Sep31Config import org.stellar.anchor.event.EventPublishService import org.stellar.anchor.horizon.Horizon -import org.stellar.anchor.paymentservice.circle.CirclePaymentService -import org.stellar.anchor.paymentservice.circle.config.CirclePaymentConfig import org.stellar.anchor.platform.data.JdbcSep31Transaction +import org.stellar.anchor.platform.payment.config.CirclePaymentConfig +import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentService import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorCircle import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf import org.stellar.anchor.sep38.Sep38QuoteStore diff --git a/platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt b/platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt new file mode 100644 index 0000000000..f8a681d155 --- /dev/null +++ b/platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt @@ -0,0 +1,28 @@ +package paymentservice + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.stellar.anchor.platform.payment.common.Account +import org.stellar.anchor.platform.payment.common.PaymentNetwork + +internal class AccountCapabilitiesTest { + @Test + fun testEquals() { + var capabilities1 = Account.Capabilities() + var capabilities2 = Account.Capabilities() + assertEquals(capabilities1, capabilities2) + + capabilities1 = Account.Capabilities() + capabilities2 = Account.Capabilities(PaymentNetwork.STELLAR) + assertNotEquals(capabilities1, capabilities2) + + capabilities1 = Account.Capabilities(PaymentNetwork.CIRCLE) + capabilities2 = Account.Capabilities(PaymentNetwork.STELLAR) + assertNotEquals(capabilities1, capabilities2) + + capabilities1 = Account.Capabilities(PaymentNetwork.BANK_WIRE) + capabilities2 = Account.Capabilities(PaymentNetwork.BANK_WIRE) + assertEquals(capabilities1, capabilities2) + } +} diff --git a/payment/src/test/resources/mock_address.json b/platform/src/test/resources/mock_address.json similarity index 100% rename from payment/src/test/resources/mock_address.json rename to platform/src/test/resources/mock_address.json diff --git a/payment/src/test/resources/mock_get_list_of_addresses_body.json b/platform/src/test/resources/mock_get_list_of_addresses_body.json similarity index 100% rename from payment/src/test/resources/mock_get_list_of_addresses_body.json rename to platform/src/test/resources/mock_get_list_of_addresses_body.json diff --git a/payment/src/test/resources/mock_get_wire_deposit_instructions_body.json b/platform/src/test/resources/mock_get_wire_deposit_instructions_body.json similarity index 100% rename from payment/src/test/resources/mock_get_wire_deposit_instructions_body.json rename to platform/src/test/resources/mock_get_wire_deposit_instructions_body.json diff --git a/payment/src/test/resources/mock_stellar_path_payment_response_page_body.json b/platform/src/test/resources/mock_stellar_path_payment_response_page_body.json similarity index 100% rename from payment/src/test/resources/mock_stellar_path_payment_response_page_body.json rename to platform/src/test/resources/mock_stellar_path_payment_response_page_body.json diff --git a/payment/src/test/resources/mock_stellar_payment_response_page_body.json b/platform/src/test/resources/mock_stellar_payment_response_page_body.json similarity index 100% rename from payment/src/test/resources/mock_stellar_payment_response_page_body.json rename to platform/src/test/resources/mock_stellar_payment_response_page_body.json diff --git a/payment/src/test/resources/mock_stellar_to_wallet_transfer.json b/platform/src/test/resources/mock_stellar_to_wallet_transfer.json similarity index 100% rename from payment/src/test/resources/mock_stellar_to_wallet_transfer.json rename to platform/src/test/resources/mock_stellar_to_wallet_transfer.json diff --git a/payment/src/test/resources/mock_wallet_to_stellar_transfer.json b/platform/src/test/resources/mock_wallet_to_stellar_transfer.json similarity index 100% rename from payment/src/test/resources/mock_wallet_to_stellar_transfer.json rename to platform/src/test/resources/mock_wallet_to_stellar_transfer.json diff --git a/payment/src/test/resources/mock_wallet_to_wallet_transfer.json b/platform/src/test/resources/mock_wallet_to_wallet_transfer.json similarity index 100% rename from payment/src/test/resources/mock_wallet_to_wallet_transfer.json rename to platform/src/test/resources/mock_wallet_to_wallet_transfer.json diff --git a/payment/src/test/resources/mock_wallet_to_wire_payout.json b/platform/src/test/resources/mock_wallet_to_wire_payout.json similarity index 100% rename from payment/src/test/resources/mock_wallet_to_wire_payout.json rename to platform/src/test/resources/mock_wallet_to_wire_payout.json diff --git a/payment/src/test/resources/mock_wire_to_wallet_payment.json b/platform/src/test/resources/mock_wire_to_wallet_payment.json similarity index 100% rename from payment/src/test/resources/mock_wire_to_wallet_payment.json rename to platform/src/test/resources/mock_wire_to_wallet_payment.json diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index a61ec29e0b..f16f143848 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -9,9 +9,7 @@ dependencies { implementation(libs.commons.cli) implementation(libs.okhttp3) - // From projects - implementation(project(":payment")) implementation(project(":platform")) implementation(project(":anchor-reference-server")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index a141f84275..41a70ec093 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -40,9 +40,6 @@ include("api-schema") include("core") -/** Payment subprojects */ -include("payment") - /** Anchor Platform */ include("platform") From d550fc5e4ad0b0698807b813f30e871ebc619959 Mon Sep 17 00:00:00 2001 From: erika-sdf <92893480+erika-sdf@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:11:06 -0700 Subject: [PATCH 07/65] helm-charts: Add assets.json to helm deployment (#442) * helm-charts: Add configmap to ingest assets.json from helm values file. * Make assets configmap optional --- .../sep-service/{readme.md => README.md} | 73 ++++++++++++++++++- .../sep-service/templates/configmap.yaml | 26 +++++-- 2 files changed, 88 insertions(+), 11 deletions(-) rename helm-charts/sep-service/{readme.md => README.md} (73%) diff --git a/helm-charts/sep-service/readme.md b/helm-charts/sep-service/README.md similarity index 73% rename from helm-charts/sep-service/readme.md rename to helm-charts/sep-service/README.md index 0b6217d3ec..5450790608 100644 --- a/helm-charts/sep-service/readme.md +++ b/helm-charts/sep-service/README.md @@ -61,9 +61,75 @@ metadata: type: Opaque ``` +# Assets Configuration +The following is an example of the `assets` configuration that can be set in the helm chart's `values.yaml`. +Not setting this section will pass in an empty assets list. + +``` +assets: + - "schema": "stellar" + "code": "USDC" + "issuer": "GYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" + "distribution_account": "GZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "significant_decimals": 2 + "deposit": + "enabled": true + "fee_minimum": 0 + "fee_percent": 0 + "min_amount": 0 + "max_amount": 10000 + "withdraw": + "enabled": true + "fee_fixed": 0 + "fee_percent": 0 + "min_amount": 0 + "max_amount": 10000 + "send": + "fee_fixed": 0 + "fee_percent": 0 + "min_amount": 0 + "max_amount": 10000 + "sep31": + "quotes_supported": true + "quotes_required": true + "sep12": + "sender": + "types": + "sep31-sender": + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + "sep31-large-sender": + "description": "U.S. citizens that do not have sending limits" + "sep31-foreign-sender": + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + "receiver": + "types": + "sep31-receiver": + "description": "U.S. citizens receiving USD" + "sep31-foreign-receiver": + "description": "non-U.S. citizens receiving USD" + "fields": + "transaction": + "receiver_routing_number": + "description": "routing number of the destination bank account" + "receiver_account_number": + "description": "bank account number of the destination" + "type": + "description": "type of deposit to make" + "choices": + - "SEPA" + - "SWIFT" + "sep38": + "exchangeable_assets": + - "stellar:USDC:GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + - "iso4217:USD" + "sep24_enabled": true + "sep31_enabled": true + "sep38_enabled": true +``` + ## Helm Chart Kubernetes Configuration The following table lists the configurable parameters of the Anchor Platform chart and their default values. These are also reflected in the example [values.yaml](./values.yaml). -| Parameter | Description | Required? | Default Value | +| Parameter | Description | Required? | Default Value | |---|---|---|---| | fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | | service.containerPort | ingress backend container port | yes | 8080 | @@ -85,7 +151,7 @@ The following table lists the configurable parameters of the Anchor Platform cha ## Helm Chart Stellar Anchor Platform Configuration The following table lists the additional configurable parameters of the Anchor Platform chart and their default values. -| Parameter | Description | Required? | Default Value | +| Parameter | Description | Required? | Default Value | |---|---|---|---| | stellar.app_config.app.hostUrl | URL of the Anchor Platform SEP Service | y | n/a | | stellar.app_config.app.jwtSecretKey | web encryption key | ${JWT_SECRET_KEY} | @@ -110,4 +176,5 @@ The following table lists the additional configurable parameters of the Anchor P | event.enabled | sep1 true if event service enabled | yes | true | | event.publisherType | kafka|sqs | no | kafka | | kafka_publisher.bootstrapServer | kafka broker host:port | no | n/a | -| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | \ No newline at end of file +| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | +| assets | see [Assets Configuration](#assets-configuration) | yes | [] | \ No newline at end of file diff --git a/helm-charts/sep-service/templates/configmap.yaml b/helm-charts/sep-service/templates/configmap.yaml index b20ce62544..87faddfadb 100644 --- a/helm-charts/sep-service/templates/configmap.yaml +++ b/helm-charts/sep-service/templates/configmap.yaml @@ -1,3 +1,15 @@ +{{- if .Values.assets }} +kind: ConfigMap +apiVersion: v1 +metadata: + name: assets +data: + assets.json: | + { + "assets": {{ toPrettyJson .Values.assets | indent 4 }} + } +--- +{{- end }} apiVersion: v1 kind: ConfigMap metadata: @@ -66,11 +78,13 @@ data: {{- else }} endpoint: {{ .Values.stellar.app_config.app.backendUrl }} {{- end }} + # sep-1 sep1: enabled: true stellarFile: file:/config/stellar-wks.toml - {{- if ((.Values.stellar.app_config).sep10) }} + # sep-10 sep10: + {{- if ((.Values.stellar.app_config).sep10) }} enabled: {{ .Values.stellar.app_config.sep10.enabled | default "true" }} homeDomain: {{ .Values.stellar.app_config.sep10.homeDomain }} clientAttributionRequired: {{ .Values.stellar.app_config.sep10.clientAttributionRequired | default "false" }} @@ -85,7 +99,6 @@ data: {{- $.Values.stellar.app_config.sep10.omnibusAccountList | toYaml | nindent 10 }} {{- end }} {{- else }} - sep10: enabled: true homeDomain: "your_home_domain.com" clientAttributionRequired: "false" @@ -94,26 +107,23 @@ data: authTimeout: 900 jwtTimeout: 86400 signingSeed: ${SEP10_SIGNING_SEED} - {{- end }} # sep-12 - {{- if ((.Values.stellar.app_config).sep12) }} sep12: + {{- if ((.Values.stellar.app_config).sep12) }} enabled: {{ .Values.stellar.app_config.sep12.enabled | default "true" | toYaml }} customerIntegrationEndpoint: {{ .Values.stellar.app_config.sep12.customerIntegrationEndpoint | default .Values.stellar.app_config.app.backendUrl }} {{- else }} - sep12: enabled: "true" customerIntegrationEndpoint: {{ .Values.stellar.app_config.app.backendUrl }} {{- end }} # sep-24 - {{- if ((.Values.stellar.app_config).sep24) }} sep24: + {{- if ((.Values.stellar.app_config).sep24) }} enabled: {{ .Values.stellar.app_config.sep24.enabled | default "true" }} interactiveJwtExpiration: {{ .Values.stellar.app_config.sep24.interactiveJwtExpiration | default 3600 }} interactiveUrl: {{ .Values.stellar.app_config.sep24.interactiveUrl | default .Values.stellar.app_config.app.backendUrl }} {{- else }} - sep24: enabled: false interactiveJwtExpiration: 3600 interactiveUrl: {{ .Values.stellar.app_config.app.backendUrl }} @@ -262,4 +272,4 @@ data: ORG_DESCRIPTION = "Please Customize all DOCUMENTATION values for your organization" ORG_LOGO = "https://myorglogo.png" ORG_SUPPORT_EMAIL="myname@myemail.org" - {{- end }} + {{- end }} \ No newline at end of file From 021f436e4c41fab3f2f94f8b136bfa9b8fa4b40b Mon Sep 17 00:00:00 2001 From: reecexlm <98427531+reecexlm@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:37:50 -0700 Subject: [PATCH 08/65] Bump Helm Chart Version for publishing, add anchor_callback parameter to documentation (#446) * add anchor_callback parameter to documentation * fix description * stellar toml parameters * double quote literal handling * double quote literal handling * bump chart version to 0.3.85 * example_values * remove all my tags * example config * fixing long redudant ing and service names * fix chart * try variable assignment * Revert "fixing long redudant ing and service names" This reverts commit af474a47d4868316722edd42ac472ac76fa05313. * fix kafka eventTypeQueue template path * eventTypeToQueue typo * empty * fixed documentation formatting --- helm-charts/sep-service/README.md | 59 +++++-- helm-charts/sep-service/example_values.yaml | 158 ++++++++++++++++++ .../sep-service/templates/configmap.yaml | 10 +- 3 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 helm-charts/sep-service/example_values.yaml diff --git a/helm-charts/sep-service/README.md b/helm-charts/sep-service/README.md index 5450790608..1dc6ed6111 100644 --- a/helm-charts/sep-service/README.md +++ b/helm-charts/sep-service/README.md @@ -23,7 +23,32 @@ $ helm delete my-release The command removes all the Kubernetes components associated with the operator and deletes the release. ``` -## Database Access Configuration +## Configuration +Helm Chart Configuration will be used to configure deployment via Helm Chart values substitution. If a parameter value is not provided, Anchor Platform will attempt to use a `classpath` [default values file](../../platform/src/main/resources/anchor-config-defaults.yaml). For further information on all available parameters and Anchor Platform configuration, please refer to the [Anchor Platform Configuration Guide](../../docs/00%20-%20Stellar%20Anchor%20Platform.md) + +### Helm Chart Kubernetes Configuration +The following table lists the configurable parameters of the Anchor Platform chart and their default values. These are also reflected in the example [values.yaml](./values.yaml). +| Parameter | Description | Required? | Default Value | +|---|---|---|---| +| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | +| service.containerPort | ingress backend container port | yes | 8080 | +| service.servicePort | ingress backend service port | yes | 8080 | +| service.replicas | number of instances of anchor platform | yes | 1 | +| service.type | service type | yes | NodePort | +| service.name | name of service | yes | sep | +| image.repo | dockerhub image repository | yes | stellar | +| image.name | dockerhub image name | yes | anchor-platform | +| image.tag | dockerhub anchorplatform dag | yes | latest | +| deployment.replicas | number of instances | yes | 1 | +| deployment.envFrom | kubernetes secrets name | no | N/A | +| ingress.metadata | ingress metadata (list) | no | N/A | +| ingress.labels | ingress labels (list) | no | N/A | +| ingress.annotations | ingress annotations (list) | no | N/A | +| ingress.tls.host | tls certificate hostname | no | N/A | +| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | N/A | +| ingress.rules | ingress backend rules (list) | | | + +### Database Unless you are using sql-lite (default configuration) your values.yaml should contain database access configuration, it should contain both the stellar.anchor.data_access.type (currently only `data-spring-jdbc` is supported) and `stellar.anchor.data_access.setttings` which contains the name of the yaml key (nested under key `stellar`) containing your database configuration settings. For example, if you plan to use AWS Aurora, you would set the data_access type and settings along with the configuration for database access as follows: ``` @@ -46,7 +71,7 @@ stellar: spring.liquibase.change-log: classpath:/db/changelog/db.changelog-master.yaml ``` -# Secrets Configuration +### Secrets Configuration The following is an example kubernetes secrets manifest that will store base64 encoded secrets referenced using placeholders in the anchor platform configuration file. In the following example, by replacing configuration values with ${JWT_SECRET} and ${SEP10_SIGNING_SEED} anchor platform will read those values from environment variables injected by the kubernetes deployment. ``` @@ -148,33 +173,43 @@ The following table lists the configurable parameters of the Anchor Platform cha | ingress.tls.host | tls certificate hostname | no | n/a | | ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | n/a | | ingress.rules | ingress backend rulues (list) | | | +>>>>>>> main:helm-charts/sep-service/README.md ## Helm Chart Stellar Anchor Platform Configuration The following table lists the additional configurable parameters of the Anchor Platform chart and their default values. | Parameter | Description | Required? | Default Value | |---|---|---|---| -| stellar.app_config.app.hostUrl | URL of the Anchor Platform SEP Service | y | n/a | -| stellar.app_config.app.jwtSecretKey | web encryption key | ${JWT_SECRET_KEY} | -| stellar.app_config.app.stellarNetwork | TESTNET OR PUBNET| TESTNET | -| stellar.app_config.app.logLevel | TRACE,DEBUG,INFO,WARN,ERROR,FATAL | INFO | +| stellar.app_config.app.hostUrl | URL of the Anchor Platform SEP Service | y | N/A | +| stellar.app_config.app.jwtSecretKey | web encryption key | y | ${JWT_SECRET_KEY} | N/A | +| stellar.app_config.app.stellarNetwork | TESTNET OR PUBNET| n | TESTNET | +| stellar.app_config.app.logLevel | TRACE,DEBUG,INFO,WARN,ERROR,FATAL | n | INFO | | stellar.anchor.data_access.type | database access type | yes | data-spring-jdbc | | stellar.anchor.data_access.setttings | values config root-level key for jdbc config | yes | data-spring-jdbc-sqlite | | stellar.toml.documentation.ORG_NAME | organization name to configure stellar.toml | yes | My Organization | | stellar.toml.documentation.ORG_URL | your organization URL to configure stellar.toml | yes | https:/myorg.org | | stellar.toml.documentation.ORG_DESCRIPTION | your organization description to configure stellar.toml | yes | https://mylogo.png | | stellar.toml.documentation.ORG_SUPPORT_EMAIL | your organization support email address to configure stellar.toml | yes | myname@myorg.org | +| stellar.app_config.app.integration_auth.auth_type | Anchor Platform to Anchor Backend Authentication type JWT_TOKEN, API_KEY or NONE | n | NONE | +| stellar.app_config.app.integration_auth.platformToAnchorSecret | secret value | n | ${PLATFORM_TO_ANCHOR_SECRET} | +| stellar.app_config.app.integration_auth.anchorToPlatformSecret | secret value | n | ${ANCHOR_TO_PLATFORM_SECRET} | +| stellar.app_config.app.integration_auth.expirationMilliseconds | integration auth credential expiration ms | n | 30000 | +| stellar.app_config.app.anchor_callback | endpoint to retrieve unique address & memo for sep 31 post /transaction | n | N/A | | sep1.enabled | sep1 true if service enabled | yes | true | | sep10.enabled | sep1 true if service enabled | yes | true | -| sep10.homeDomain | | | | -| sep10.signingSeed | | | | +| sep10.homeDomain | a domain hosting a SEP-1 stellar.toml | n | N/A | +| sep10.signingSeed | secret key of Stellar Account used for auth. (public key in stellar.toml ACCOUNTS) | y | N/A | | sep12.enabled | sep1 true if service enabled | yes | true | -| sep12.customerIntegrationEndpoint| URL of SEP 12 KYC Endpoint | no | n/a | +| sep12.customerIntegrationEndpoint| URL of SEP 12 KYC Endpoint | no | N/A | | sep31.enabled | sep1 true if service enabled | yes | true | -| sep31.feeIntegrationEndPoint | URL of Fees Endpoint | no | n/a | +| sep31.feeIntegrationEndPoint | URL of Fees Endpoint | no | N/A | | sep38.enabled | sep1 true if service enabled | yes | true | -| sep38.quoteIntegrationEndPoint | URL of Quotes Endpoint | no | n/a | +| sep38.quoteIntegrationEndPoint | URL of Quotes Endpoint | no | N/A | | event.enabled | sep1 true if event service enabled | yes | true | | event.publisherType | kafka|sqs | no | kafka | +<<<<<<< HEAD:helm-charts/sep-service/readme.md +| kafka_publisher.bootstrapServer | kafka broker host:port | no | N/A | +| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | +======= | kafka_publisher.bootstrapServer | kafka broker host:port | no | n/a | | kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | -| assets | see [Assets Configuration](#assets-configuration) | yes | [] | \ No newline at end of file +| assets | see [Assets Configuration](#assets-configuration) | yes | [] | diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml new file mode 100644 index 0000000000..8b60c944aa --- /dev/null +++ b/helm-charts/sep-service/example_values.yaml @@ -0,0 +1,158 @@ +fullName: anchor-platform +service: + containerPort: 8080 + servicePort: 8080 + replicas: 1 + type: NodePort + name: sep +image: + repo: stellar + name: anchor-platform + tag: latest + pullPolicy: Always +deployment: + startupProbePeriodSeconds: 10 + startupProbeFailureThreshold: 30 + serviceAccountName: default + volumeMounts: + secrets: + - name: apsigningseed + mountPath: signingseed + configMaps: + - name: assets + mountPath: assets + hosts: + - host: "your_anchor_domain.com" + path: / + pathType: Prefix + backend: + servicePort: "{{ .Values.service.containerPort }}" + serviceName: "{{ .Values.fullName }}-svc-{{ .Values.service.name }}" + env: + - name: STELLAR_ANCHOR_CONFIG + value: file:/config/anchor-config.yaml + envFrom: + - secretRef: + name: apsigningseed +ingress: + labels: + app: appy + test: label_test + annotations: + kubernetes.io/ingress.class: "public" + cert-manager.io/cluster-issuer: "default" + ingress.kubernetes.io/ssl-redirect: "true" + ingress.kubernetes.io/browser-xss-filter: "true" + ingress.kubernetes.io/frame-deny: "true" + ingress.kubernetes.io/content-type-nosniff: "true" + ingress.kubernetes.io/hsts-max-age: "31536000" + ingress.kubernetes.io/hsts-include-subdomains: "true" + tls: + host: "your_anchor_domain.com" + secretName: anchor-platform-sep-server-cert + rules: + hosts: + - host: "your_anchor_domain.com" + path: / + pathType: Prefix + backend: + servicePort: "{{ .Values.service.containerPort }}" + serviceName: "{{ .Values.fullName }}-svc-{{ .Values.service.name }}" +stellar: + toml: + #accounts: ['"GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR"'] + signing_key: "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + documentation: + ORG_NAME: "Stellar Development Foundation" + ORG_URL: "https://www.stellar.org" + ORG_DESCRIPTION: "Stellar Network" + ORG_URL: "https://your_org_url.png" + ORG_SUPPORT_EMAIL: "reece@stellar.org" + anchor: + data_access: + type: data-spring-jdbc + settings: data-spring-jdbc-sqlite +# settings: data-spring-jdbc-aws-aurora-postgres +# data_spring_jdbc_aws_aurora_postgres: +# spring.jpa.generate-ddl: true +# spring.jpa.database: POSTGRESQL +# spring.jpa.show-sql: false +# spring.datasource.driver-class-name: org.postgresql.Driver +# spring.datasource.type: org.stellar.anchor.platform.databaseintegration.IAMAuthDataSource +# spring.datasource.url: jdbc:postgresql://database-aurora.region.amazonaws.com:port/instance +# spring.datasource.username: ${DB_USERNAME} +# spring.datasource.hikari.max-lifetime: 840000 # 14 minutes because IAM tokens are valid for 15min +## spring.mvc.converters.preferred-json-mapper: gson +# spring.flyway.user: ${FLYWAY_USER} +# spring.flyway.password: ${FLYWAY_PASSWORD} # can use a token value if authenticating via IAM +# spring.flyway.url: jdbc:postgresql://database-aurora.region.rds.amazonaws.com:port/instance +# spring.flyway.locations: classpath:/db/migration + data_spring_jdbc_sqlite: + spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect + spring.jpa.hibernate.ddl-auto: update + spring.jpa.generate-ddl: true + spring.jpa.hibernate.show_sql: false + spring.datasource.url: jdbc:sqlite:anchor-proxy.db + spring.datasource.driver-class-name: org.sqlite.JDBC + spring.datasource.username: ${SQLITE_USERNAME} + spring.datasource.password: ${SQLITE_PASSWORD} + spring.mvc.converters.preferred-json-mapper: gson + spring.flyway.user: ${SQLITE_USERNAME} + spring.flyway.password: ${SQLITE_USERNAME} # can use a token value if authenticating via IAM + spring.flyway.url: jdbc:sqlite:anchor-proxy.db + spring.flyway.locations: classpath:/db/migration + #spring.flyway.enabled: false + app_config: + app: + stellarNetwork: TESTNET + hostUrl: https://your_anchor_domain.com + backendUrl: https://your_anchor_domain.com + jwtSecretKey: ${JWT_SECRET} + assets: file:/assets/assets.json + platformToAnchorSecret: ${PLATFORM_TO_ANCHOR_SECRET} + anchorToPlatformSecret: ${ANCHOR_TO_PLATFORM_SECRET} + sep1: + enabled: true + stellarFile: file:/config/stellar-wks.toml + sep10: + enabled: true + homeDomain: your_anchor_domain.com + signingSeed: ${SEP10_SIGNING_SEED} + requireKnownOmnibusAccount: false + omnibusAccountList: + # - GDOHXZYP5ABGCTKAEROOJFN6X5GY7VQNXFNK2SHSAD32GSVMUJBPG75E + # - GC5KRPVW4TFA6ORIGTOFM3DEG6DSLACRXGDN3LNCAI7IPAGIWMVXUHVS + sep12: + enabled: true + customerIntegrationEndpoint: https://anchor-reference-server-dev.stellar.org + sep31: + enabled: true + feeIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org + sep38: + enabled: true + quoteIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org + circle: + circleUrl: https://api-sandbox.circle.com + apiKey: ${CIRCLE_API_KEY} + event: + enabled: true + publisherType: kafka + kafka_publisher: + bootstrapServer: reecekafka-0.reecekafka-headless.sandbox.svc.cluster.local:9092 + circle_payment_observer: + enabled: true + payment_gateway: + circle: + enabled: true + stellar: + enabled: false + name: "stellar" + horizonUrl: https://horizon-testnet.stellar.org + secretKey: secret # stellar account secret key + spring: + logging: + level: + root: DEBUG + org.springframework: DEBUG + org.springframework.web.filter: DEBUG + org.stellar: DEBUG diff --git a/helm-charts/sep-service/templates/configmap.yaml b/helm-charts/sep-service/templates/configmap.yaml index 87faddfadb..916e966e32 100644 --- a/helm-charts/sep-service/templates/configmap.yaml +++ b/helm-charts/sep-service/templates/configmap.yaml @@ -177,7 +177,7 @@ data: trackedWallet: {{ .Values.stellar.app_config.circle_payment_observer.trackedWallet | default "all" }} {{- end }} {{- if (.Values.stellar.app_config).event }} - event: + event: # If enabled, publish Events to a queue (publisherType) # publisherType - the type of queue to use for event publishing enabled: {{ .Values.stellar.app_config.event.enabled | default "true" }} @@ -229,10 +229,10 @@ data: mvc: async.request-timeout: 6000 stellar-wks.toml: | - ACCOUNTS = ["GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY"] - VERSION = "0.1.0" - NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" - SIGNING_KEY = "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" + ACCOUNTS = {{ .Values.stellar.toml.accounts | default "[\"GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY\"]" }} + VERSION = "{{ .Values.stellar.toml.version | default "0.1.0" }}" + NETWORK_PASSPHRASE = "{{ .Values.stellar.app_config.app.stellarNetworkPassphrase | default "Test SDF Network ; September 2015" }}" + SIGNING_KEY = "{{ .Values.stellar.toml.signing_key | default "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" }}" TRANSFER_SERVER = "{{ .Values.stellar.app_config.app.hostUrl }}/sep6" TRANSFER_SERVER_SEP0024 = "{{ .Values.stellar.app_config.app.hostUrl }}/sep24" WEB_AUTH_ENDPOINT = "{{ .Values.stellar.app_config.app.hostUrl }}/auth" From 1acb2a014dbb6f6dd3ca12d1e5606d791b2de6da Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 3 Aug 2022 10:52:13 -0700 Subject: [PATCH 09/65] add uniqueAddressIntegrationEndPoint to helm chart (#456) --- .../example-fargate/example_config/anchor_config.yaml | 3 +++ helm-charts/sep-service/example_values.yaml | 1 + helm-charts/sep-service/templates/configmap.yaml | 2 ++ .../src/test/resources/integration-test.anchor-config.yaml | 1 + platform/src/main/resources/anchor-docker-compose-config.yaml | 3 +++ 5 files changed, 10 insertions(+) diff --git a/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml b/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml index 5fd64cb2fa..507019d5f9 100644 --- a/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml +++ b/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml @@ -160,6 +160,9 @@ app-config: # The callback API endpoint. feeIntegrationEndPoint: http://ref.stellaranchordemo.com:8081 + # The /unique_address callback API endpoint. + uniqueAddressIntegrationEndPoint: http://ref.stellaranchordemo.com:8081 + # # paymentType: used to determine how amount_in is calculated from amount in the POST /transaction call # Possible values: STRICT_SEND or STRICT_RECEIVE. default=STRICT_SEND diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index 8b60c944aa..6bd84f27cd 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -128,6 +128,7 @@ stellar: sep31: enabled: true feeIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org + uniqueAddressIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org sep38: enabled: true quoteIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org diff --git a/helm-charts/sep-service/templates/configmap.yaml b/helm-charts/sep-service/templates/configmap.yaml index 916e966e32..d110bd1276 100644 --- a/helm-charts/sep-service/templates/configmap.yaml +++ b/helm-charts/sep-service/templates/configmap.yaml @@ -133,11 +133,13 @@ data: {{- if ((.Values.stellar.app_config).sep31) }} enabled: {{ .Values.stellar.app_config.sep31.enabled | default "true" }} feeIntegrationEndPoint: {{ .Values.stellar.app_config.sep31.feeIntegrationEndPoint | default .Values.stellar.app_config.app.backendUrl }} + uniqueAddressIntegrationEndPoint: {{ .Values.stellar.app_config.sep31.uniqueAddressIntegrationEndPoint | default .Values.stellar.app_config.app.backendUrl}} depositInfoGeneratorType: {{ .Values.stellar.app_config.sep31.depositInfoGeneratorType | default "self" }} paymentType: {{ .Values.stellar.app_config.sep31.paymentType | default "STRICT_SEND" }} {{- else }} enabled: "true" feeIntegrationEndPoint: {{ .Values.stellar.app_config.app.backendUrl }} + uniqueAddressIntegrationEndPoint: {{ .Values.stellar.app_config.app.backendUrl }} paymentType: STRICT_SEND {{- end }} # sep-38 diff --git a/integration-tests/src/test/resources/integration-test.anchor-config.yaml b/integration-tests/src/test/resources/integration-test.anchor-config.yaml index 03bb616cad..d4823170de 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -63,6 +63,7 @@ app-config: sep31: enabled: true feeIntegrationEndPoint: http://localhost:8081 + uniqueAddressIntegrationEndPoint: http://localhost:8081 # # paymentType: used to determine how amount_in is calculated from amount in the POST /transaction call # Possible values: STRICT_SEND or STRICT_RECEIVE. default=STRICT_SEND diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 1481da110a..9f6047966d 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -179,6 +179,9 @@ app-config: # The callback API endpoint. feeIntegrationEndPoint: http://host.docker.internal:8081 + # The /unique_address callback API endpoint. + uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 + # # paymentType: used to determine how amount_in is calculated from amount in the POST /transaction call # Possible values: STRICT_SEND or STRICT_RECEIVE. default=STRICT_SEND From 6ff94aaf33b3def4b9c0020c0fe5e7db4967ac6b Mon Sep 17 00:00:00 2001 From: reecexlm <98427531+reecexlm@users.noreply.github.com> Date: Wed, 3 Aug 2022 14:19:55 -0700 Subject: [PATCH 10/65] preview-assets (#462) --- helm-charts/sep-service/templates/configmap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm-charts/sep-service/templates/configmap.yaml b/helm-charts/sep-service/templates/configmap.yaml index d110bd1276..f4f49b48c6 100644 --- a/helm-charts/sep-service/templates/configmap.yaml +++ b/helm-charts/sep-service/templates/configmap.yaml @@ -2,7 +2,7 @@ kind: ConfigMap apiVersion: v1 metadata: - name: assets + name: {{ .Values.fullName }}-assets data: assets.json: | { From bcd856126c1199d804cedd713fe540d1989ddf01 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 4 Aug 2022 08:43:00 -0700 Subject: [PATCH 11/65] issue-457: Update distributionWallet fields. (#458) * add distributionWallet to reference server and make the default distribution wallet consistent with the assets.json * empty #2 * empty #2 * bump helm chart Co-authored-by: Reece Markowsky --- .../src/main/resources/anchor-reference-server.yaml | 2 +- .../anchor-platform-dev/reference-server-config-map.yaml | 3 +++ .../example-k8s/anchor-platform-dev/sep-server-config-map.yaml | 2 ++ helm-charts/sep-service/Chart.yaml | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml index effe6bbc57..7012efb9a8 100644 --- a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml +++ b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml @@ -14,7 +14,7 @@ anchor.settings: hostUrl: http://localhost:8081 # The Stellar wallet to which the customer will send the Stellar assets. - distributionWallet: GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR + distributionWallet: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF distributionWalletMemo: distributionWalletMemoType: diff --git a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/reference-server-config-map.yaml b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/reference-server-config-map.yaml index 80244b7112..893f3ed994 100644 --- a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/reference-server-config-map.yaml +++ b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/reference-server-config-map.yaml @@ -17,6 +17,9 @@ data: anchor.settings: version: 0.0.1 platformApiEndpoint: https://anchor-sep-server-dev.stellar.org + distributionWallet: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF + distributionWalletMemo: + distributionWalletMemoType: integration-auth: authType: JWT_TOKEN diff --git a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/sep-server-config-map.yaml b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/sep-server-config-map.yaml index 59cb9a9c90..e93717d4cb 100644 --- a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/sep-server-config-map.yaml +++ b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/sep-server-config-map.yaml @@ -78,12 +78,14 @@ data: # STRICT_SEND: amount_in = amount # STRICT_RECEIVE: amount_in = amount + fee paymentType: STRICT_SEND + # # depositInfoGeneratorType: used to choose how the SEP-31 deposit information will be generated, which includes the # deposit address, memo and memo type. # Possible values: # self: the memo and memo type are generated in the local code, and the distribution account is used for the deposit address. # circle: the memo and memo type are generated through Circle API, as well as the deposit address. + # api: the memo and memo type are generated through calling the anchor's GET /unique_address endpoint. depositInfoGeneratorType: self # self or circle # sep-38 diff --git a/helm-charts/sep-service/Chart.yaml b/helm-charts/sep-service/Chart.yaml index 412fbd85ba..01a958336c 100644 --- a/helm-charts/sep-service/Chart.yaml +++ b/helm-charts/sep-service/Chart.yaml @@ -7,4 +7,4 @@ maintainers: sources: - https://github.com/stellar/java-stellar-anchor-sdk name: sep -version: 0.3.85 +version: 0.3.86 From 41548e1d44493bafa92ad249de6fc9cbf158744d Mon Sep 17 00:00:00 2001 From: reecexlm <98427531+reecexlm@users.noreply.github.com> Date: Thu, 4 Aug 2022 17:25:43 -0700 Subject: [PATCH 12/65] codebuild_source_version (#468) --- .../example-fargate/terraform/codebuild_config.tf | 2 +- .../example-fargate/terraform/variables.tf | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf b/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf index f9d47c7165..c9dc792449 100644 --- a/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf +++ b/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf @@ -238,7 +238,7 @@ resource "aws_codebuild_project" "codebuild_config" { } } - source_version = "refs/heads/fargate" + source_version = var.codebuild_source_version vpc_config { vpc_id = module.vpc.vpc_id diff --git a/docs/resources/deployment-examples/example-fargate/terraform/variables.tf b/docs/resources/deployment-examples/example-fargate/terraform/variables.tf index fc4d45e8ce..aa94fe3c28 100644 --- a/docs/resources/deployment-examples/example-fargate/terraform/variables.tf +++ b/docs/resources/deployment-examples/example-fargate/terraform/variables.tf @@ -47,6 +47,12 @@ variable "anchor_config_build_spec" { default = "docs/resources/deployment-examples/aws-fargate-ecs/buildspec-dev.yml" } +variable "codebuild_source_version" { + description = "deployment environment" + type = string + default = "main" +} + variable "anchor_config_repository" { type = string default = "https://github.com/reecexlm/java-stellar-anchor-sdk" From 61fbd5e03f14fbd5fe8fe812fa5e889f12c25690 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 5 Aug 2022 08:30:12 -0700 Subject: [PATCH 13/65] core: Fix empty memo cause complaints in the SEP tests (#466) * When memo is empty, remove the field in the response * rename getRate to getUniqueAddress * return none when the memoType is empty * remove the problematic sep-38 exchanges * add exchange pairs --- .../controller/UniqueAddressController.java | 2 +- .../service/UniqueAddressService.java | 59 +++++++++++++------ .../stellar/anchor/sep31/Sep31Service.java | 9 ++- .../org/stellar/anchor/util/MemoHelper.java | 11 +++- .../org/stellar/anchor/util/StringHelper.java | 9 +++ platform/src/main/resources/assets-test.json | 9 ++- .../sep31/Sep31DepositInfoGeneratorTest.kt | 3 +- 7 files changed, 72 insertions(+), 30 deletions(-) create mode 100644 core/src/main/java/org/stellar/anchor/util/StringHelper.java diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java index 6d38d5ae9e..d77f072ef7 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/UniqueAddressController.java @@ -18,7 +18,7 @@ public UniqueAddressController(UniqueAddressService uniqueAddressService) { value = "/unique_address", method = {RequestMethod.GET}) @ResponseBody - public GetUniqueAddressResponse getRate( + public GetUniqueAddressResponse getUniqueAddress( @RequestParam(name = "transaction_id") String transactionId) { return uniqueAddressService.getUniqueAddress(transactionId); } diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java index 7e3717d699..53fe5b2fff 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/UniqueAddressService.java @@ -1,54 +1,77 @@ package org.stellar.anchor.reference.service; +import static org.stellar.anchor.util.MemoHelper.memoTypeAsString; +import static org.stellar.anchor.util.StringHelper.isEmpty; +import static org.stellar.sdk.xdr.MemoType.MEMO_HASH; + +import java.util.Base64; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.stellar.anchor.api.callback.GetUniqueAddressResponse; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.reference.config.AppSettings; import org.stellar.anchor.util.ConditionalOnPropertyNotEmpty; +import org.stellar.anchor.util.Log; import org.stellar.anchor.util.MemoHelper; import org.stellar.sdk.KeyPair; @Service @ConditionalOnPropertyNotEmpty("anchor.settings.distributionWallet") public class UniqueAddressService { - String distributionWallet; - String distributionWalletMemo; - String distributionWalletMemoType; + AppSettings appSettings; UniqueAddressService(AppSettings appSettings) throws SepException { - this.distributionWallet = appSettings.getDistributionWallet(); + this.appSettings = appSettings; + if (Objects.toString(appSettings.getDistributionWallet(), "").isEmpty()) { throw new SepException("distributionWallet is empty"); } try { - KeyPair.fromAccountId(this.distributionWallet); + KeyPair.fromAccountId(appSettings.getDistributionWallet()); } catch (Exception ex) { throw new SepException( - String.format("Invalid distributionWallet: [%s]", this.distributionWallet)); + String.format("Invalid distributionWallet: [%s]", appSettings.getDistributionWallet())); } - if (!Objects.toString(appSettings.getDistributionWalletMemo(), "").isEmpty() - && !Objects.toString(appSettings.getDistributionWalletMemoType(), "").isEmpty()) { + if (!isEmpty(appSettings.getDistributionWalletMemo()) + && !isEmpty(appSettings.getDistributionWalletMemoType())) { // check if memo and memoType are valid MemoHelper.makeMemo( appSettings.getDistributionWalletMemo(), appSettings.getDistributionWalletMemoType()); } - this.distributionWalletMemo = appSettings.getDistributionWalletMemo(); - this.distributionWalletMemoType = appSettings.getDistributionWalletMemoType(); } public GetUniqueAddressResponse getUniqueAddress(String transactionId) { // transactionId may be used to query the transaction information if the anchor would like to // return a transaction-dependent unique address. - return GetUniqueAddressResponse.builder() - .uniqueAddress( - GetUniqueAddressResponse.UniqueAddress.builder() - .stellarAddress(distributionWallet) - .memo(distributionWalletMemo) - .memoType(distributionWalletMemoType) - .build()) - .build(); + + Log.debugF("Getting a unique address for transaction[id={}]", transactionId); + GetUniqueAddressResponse.UniqueAddress.UniqueAddressBuilder uniqueAddressBuilder = + GetUniqueAddressResponse.UniqueAddress.builder() + .stellarAddress(appSettings.getDistributionWallet()); + + if (isEmpty(appSettings.getDistributionWalletMemo()) + || isEmpty(appSettings.getDistributionWalletMemoType())) { + String memo = StringUtils.truncate(transactionId, 32); + memo = StringUtils.leftPad(memo, 32, '0'); + memo = new String(Base64.getEncoder().encode(memo.getBytes())); + uniqueAddressBuilder.memo(memo).memoType(memoTypeAsString(MEMO_HASH)); + } else { + uniqueAddressBuilder + .memo(appSettings.getDistributionWalletMemo()) + .memoType(appSettings.getDistributionWalletMemoType()); + } + + GetUniqueAddressResponse resp = + GetUniqueAddressResponse.builder().uniqueAddress(uniqueAddressBuilder.build()).build(); + Log.infoF( + "Got the unique address for transaction[id={}]. memo={}, memoType={}", + transactionId, + resp.getUniqueAddress().getMemo(), + resp.getUniqueAddress().getMemoType()); + + return resp; } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index a6df823470..1338e67624 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -7,6 +7,8 @@ import static org.stellar.anchor.util.MathHelper.formatAmount; import static org.stellar.anchor.util.SepHelper.*; import static org.stellar.anchor.util.SepLanguageHelper.validateLanguage; +import static org.stellar.anchor.util.StringHelper.isEmpty; +import static org.stellar.sdk.xdr.MemoType.MEMO_NONE; import java.math.BigDecimal; import java.time.Instant; @@ -214,8 +216,9 @@ public Sep31PostTransactionResponse postTransaction( return Sep31PostTransactionResponse.builder() .id(txn.getId()) .stellarAccountId(txn.getStellarAccountId()) - .stellarMemo(txn.getStellarMemo()) - .stellarMemoType(txn.getStellarMemoType()) + .stellarMemo(isEmpty(txn.getStellarMemo()) ? "" : txn.getStellarMemo()) + .stellarMemoType( + isEmpty(txn.getStellarMemoType()) ? MEMO_NONE.name() : txn.getStellarMemoType()) .build(); } @@ -311,7 +314,7 @@ void updateDepositInfo() throws AnchorException { infoF("Updating transaction ({}) with depositInfo ({})", txn.getId(), depositInfo); txn.setStellarAccountId(depositInfo.getStellarAddress()); txn.setStellarMemo(depositInfo.getMemo()); - txn.setStellarMemoType(depositInfo.getMemoType()); + txn.setStellarMemoType(isEmpty(depositInfo.getMemoType()) ? "none" : depositInfo.getMemoType()); } public Sep31GetTransactionResponse getTransaction(String id) throws AnchorException { diff --git a/core/src/main/java/org/stellar/anchor/util/MemoHelper.java b/core/src/main/java/org/stellar/anchor/util/MemoHelper.java index 2e1cc5d152..0134ce57cf 100644 --- a/core/src/main/java/org/stellar/anchor/util/MemoHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/MemoHelper.java @@ -1,5 +1,6 @@ package org.stellar.anchor.util; +import static org.stellar.anchor.util.StringHelper.isEmpty; import static org.stellar.sdk.xdr.MemoType.*; import java.util.Base64; @@ -47,8 +48,8 @@ public static MemoType memoType(Memo memo) { } public static Memo makeMemo(String memo, String memoType) throws SepException { - if (memo == null || memoType == null) { - return null; + if (isEmpty(memoType)) { + memoType = "none"; } switch (memoType) { @@ -59,6 +60,7 @@ public static Memo makeMemo(String memo, String memoType) throws SepException { case "hash": return makeMemo(memo, MemoType.MEMO_HASH); case "none": + return makeMemo(memo, MemoType.MEMO_NONE); case "return": throw new SepException("Unsupported value: " + memoType); default: @@ -75,6 +77,11 @@ public static Memo makeMemo(String memo, MemoType memoType) throws SepException return new MemoText(memo); case MEMO_HASH: return new MemoHash(convertBase64ToHex(memo)); + case MEMO_NONE: + if (isEmpty(memo)) { + return new MemoNone(); + } + throw new SepException("memo must be empty when memoType is none"); default: throw new SepException("Unsupported value: " + memoType); } diff --git a/core/src/main/java/org/stellar/anchor/util/StringHelper.java b/core/src/main/java/org/stellar/anchor/util/StringHelper.java new file mode 100644 index 0000000000..28fe68e0a4 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/util/StringHelper.java @@ -0,0 +1,9 @@ +package org.stellar.anchor.util; + +import java.util.Objects; + +public class StringHelper { + public static boolean isEmpty(String value) { + return Objects.toString(value, "").isEmpty(); + } +} diff --git a/platform/src/main/resources/assets-test.json b/platform/src/main/resources/assets-test.json index d5aefe598a..b24db2d6ca 100644 --- a/platform/src/main/resources/assets-test.json +++ b/platform/src/main/resources/assets-test.json @@ -74,8 +74,7 @@ }, "sep38": { "exchangeable_assets": [ - "iso4217:USD", - "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + "iso4217:USD" ] }, "sep24_enabled": true, @@ -156,8 +155,7 @@ }, "sep38": { "exchangeable_assets": [ - "iso4217:USD", - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + "iso4217:USD" ] }, "sep24_enabled": false, @@ -189,7 +187,8 @@ }, "sep38": { "exchangeable_assets": [ - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" ], "country_codes": ["USA"], "decimals": 4, diff --git a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt index fea8854357..2a379f2ac9 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -208,7 +208,8 @@ class Sep31DepositInfoGeneratorTest { @ParameterizedTest @CsvSource( - value = [",", "YTIzOTJhZGQtODdjOS00MmYwLWE1YzEtNWYxNzI4MDM=,hash", "123,id", "John Doe,text"] + value = + [",none", "YTIzOTJhZGQtODdjOS00MmYwLWE1YzEtNWYxNzI4MDM=,hash", "123,id", "John Doe,text"] ) fun test_updateDepositInfo_api(memo: String?, memoType: String?) { val nonEmptyMemo = Objects.toString(memo, "") From 9cd384c840092db57618fe004045e82c66890f97 Mon Sep 17 00:00:00 2001 From: reecexlm <98427531+reecexlm@users.noreply.github.com> Date: Fri, 5 Aug 2022 09:30:50 -0700 Subject: [PATCH 14/65] Bump Helm Chart to 0.3.87 (#470) --- helm-charts/sep-service/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm-charts/sep-service/Chart.yaml b/helm-charts/sep-service/Chart.yaml index 01a958336c..936c1cfea4 100644 --- a/helm-charts/sep-service/Chart.yaml +++ b/helm-charts/sep-service/Chart.yaml @@ -7,4 +7,4 @@ maintainers: sources: - https://github.com/stellar/java-stellar-anchor-sdk name: sep -version: 0.3.86 +version: 0.3.87 From fe5467e7fcdbf51154b04e065518df90d9df44c3 Mon Sep 17 00:00:00 2001 From: reecexlm <98427531+reecexlm@users.noreply.github.com> Date: Mon, 8 Aug 2022 12:34:14 -0700 Subject: [PATCH 15/65] fixing paths for buildspec after documentation refactor (#474) --- docs/resources/deployment-examples/example-fargate/Dockerfile | 2 +- .../deployment-examples/example-fargate/buildspec-dev.yml | 2 +- .../deployment-examples/example-fargate/terraform/variables.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/resources/deployment-examples/example-fargate/Dockerfile b/docs/resources/deployment-examples/example-fargate/Dockerfile index fade9c3d98..0470ae4918 100644 --- a/docs/resources/deployment-examples/example-fargate/Dockerfile +++ b/docs/resources/deployment-examples/example-fargate/Dockerfile @@ -12,5 +12,5 @@ ADD ./reference_config.yaml /config_files/reference_config.yaml ADD ./stellar_wks.toml /config_files/stellar_wks.toml RUN echo $(ls -1 /config_files) RUN echo $(ls -1 /) -WORKDIR "/docs/deployment-examples/aws-fargate-ecs" +WORKDIR "/docs/resources/deployment-examples/example-fargate" ENTRYPOINT ["/copy_config.sh"] diff --git a/docs/resources/deployment-examples/example-fargate/buildspec-dev.yml b/docs/resources/deployment-examples/example-fargate/buildspec-dev.yml index 8d48e6436b..d4a50a560a 100644 --- a/docs/resources/deployment-examples/example-fargate/buildspec-dev.yml +++ b/docs/resources/deployment-examples/example-fargate/buildspec-dev.yml @@ -9,7 +9,7 @@ env: phases: build: commands: - - cd docs/resources/deployment-examples/aws-fargate-ecs + - cd docs/resources/deployment-examples/example-fargate - aws s3 ls s3://$ANCHOR_CONFIG_S3_BUCKET - aws s3 cp s3://$ANCHOR_CONFIG_S3_BUCKET/anchor_config.yaml . - aws s3 cp s3://$ANCHOR_CONFIG_S3_BUCKET/stellar.toml ./stellar_wks.toml diff --git a/docs/resources/deployment-examples/example-fargate/terraform/variables.tf b/docs/resources/deployment-examples/example-fargate/terraform/variables.tf index aa94fe3c28..8dfb5776e1 100644 --- a/docs/resources/deployment-examples/example-fargate/terraform/variables.tf +++ b/docs/resources/deployment-examples/example-fargate/terraform/variables.tf @@ -44,7 +44,7 @@ variable "anchor_to_platform_secret" { variable "anchor_config_build_spec" { type = string - default = "docs/resources/deployment-examples/aws-fargate-ecs/buildspec-dev.yml" + default = "docs/resources/deployment-examples/example-fargate/buildspec-dev.yml" } variable "codebuild_source_version" { From f40b84faad97f160b5aba40d7b8fc289190f2ca5 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 9 Aug 2022 11:31:16 -0700 Subject: [PATCH 16/65] upgrade Gradle from 7.3 to 7.5.1 (#476) --- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102e09..ae04661ee7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787337..c53aefaa5f 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». +# * expansions $var, ${var}, ${var:-default}, ${var+SET}, +# ${var#prefix}, ${var%suffix}, and $( cmd ); +# * compound commands having a testable exit status, especially case; +# * various built-in commands including command, set, and ulimit. # # Important for patching: # From 0a775b06b35b95e3b4928f6a336f8d2b0585fca8 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 9 Aug 2022 16:34:30 -0300 Subject: [PATCH 17/65] bugfix: fix issue where the incoming payment was not populating the event correctly (#475) ### What Fix issue where the incoming payment was not populating the event correctly. ### Why The field `Transaction.stellarTransactions[0].createdAt` was empty because it relied on the transaction object update and it was happening later in time due to a mistake in the code. I also added tests to make sure this kind of problem would be catch. --- end-to-end-tests/end_to_end_tests.py | 5 +- .../platform/data/JdbcSep31Transaction.java | 6 +- .../PaymentOperationToEventListener.java | 87 ++++++++++++------- .../PaymentOperationToEventListenerTest.kt | 40 ++++++--- 4 files changed, 91 insertions(+), 47 deletions(-) diff --git a/end-to-end-tests/end_to_end_tests.py b/end-to-end-tests/end_to_end_tests.py index 8f71346334..07d90bc279 100644 --- a/end-to-end-tests/end_to_end_tests.py +++ b/end-to-end-tests/end_to_end_tests.py @@ -82,7 +82,10 @@ def get_transaction(endpoints, headers, transaction_id): "address": "123 Washington Street", "city": "San Francisco", "state_or_province": "CA", - "address_country_code": "US" + "address_country_code": "US", + "clabe_number": "1234", + "bank_number": "abcd", + "bank_account_number": "1234" } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java index 8faf1e293e..34ce94dc48 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java @@ -7,8 +7,7 @@ import java.util.Map; import java.util.Set; import javax.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.reference.model.StellarIdConverter; @@ -16,8 +15,7 @@ import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.util.GsonUtils; -@Getter -@Setter +@Data @Entity @Access(AccessType.FIELD) @Table(name = "sep31_transaction") diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index 32c62ebc1e..ac42458267 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -1,12 +1,15 @@ package org.stellar.anchor.platform.service; import static org.stellar.anchor.api.sep.SepTransactionStatus.ERROR; +import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_RECEIVER; +import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_SENDER; import static org.stellar.anchor.util.MathHelper.*; import io.micrometer.core.instrument.Metrics; import java.math.BigDecimal; import java.time.Instant; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -16,8 +19,6 @@ import org.stellar.anchor.api.exception.SepException; 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.StellarId; import org.stellar.anchor.event.EventPublishService; import org.stellar.anchor.event.models.*; import org.stellar.anchor.platform.payment.observer.PaymentListener; @@ -104,32 +105,21 @@ public void onReceived(ObservedPayment payment) { String.format( "Payment amount %s is smaller than the expected amount %s", payment.getAmount(), txn.getAmountIn())); - txn.setStatus(ERROR.getName()); - saveTransaction(txn); - Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", ERROR.getName()) - .increment(); + updateTransactionStatusTo(ERROR, txn, payment); return; } // Set the transaction status. - TransactionEvent event = receivedPaymentToEvent(txn, payment); - if (txn.getStatus().equals(SepTransactionStatus.PENDING_SENDER.toString())) { - txn.setStatus(SepTransactionStatus.PENDING_RECEIVER.toString()); - txn.setStellarTransactionId(payment.getTransactionHash()); - Instant paymentTime = - DateTimeFormatter.ISO_INSTANT.parse(payment.getCreatedAt(), Instant::from); - txn.setUpdatedAt(paymentTime); - txn.setTransferReceivedAt(paymentTime); - try { - transactionStore.save(txn); - Metrics.counter( - "sep31.transaction", "status", SepTransactionStatus.PENDING_RECEIVER.toString()) - .increment(); - } catch (SepException ex) { - Log.errorEx(ex); - } + TransactionEvent.Status oldStatus = TransactionEvent.Status.from(txn.getStatus()); + TransactionEvent.Status newStatus = TransactionEvent.Status.PENDING_RECEIVER; + TransactionEvent.StatusChange statusChange = + new TransactionEvent.StatusChange(oldStatus, newStatus); + if (txn.getStatus().equals(PENDING_SENDER.toString())) { + updateTransactionStatusTo(PENDING_RECEIVER, txn, payment); } + // send to the event queue + TransactionEvent event = receivedPaymentToEvent(txn, payment, statusChange); sendToQueue(event); Metrics.counter(AnchorMetrics.PAYMENT_RECEIVED.toString(), "asset", payment.getAssetName()) .increment(Double.parseDouble(payment.getAmount())); @@ -145,14 +135,8 @@ private void sendToQueue(TransactionEvent event) { Log.info("Sent to event queue" + GsonUtils.getInstance().toJson(event)); } - TransactionEvent receivedPaymentToEvent(Sep31Transaction txn, ObservedPayment payment) { - TransactionEvent.Status oldStatus = TransactionEvent.Status.from(txn.getStatus()); - TransactionEvent.Status newStatus = TransactionEvent.Status.PENDING_RECEIVER; - TransactionEvent.StatusChange statusChange = - new TransactionEvent.StatusChange(oldStatus, newStatus); - - StellarId senderStellarId = StellarId.builder().id(txn.getSenderId()).build(); - StellarId receiverStellarId = StellarId.builder().id(txn.getReceiverId()).build(); + TransactionEvent receivedPaymentToEvent( + Sep31Transaction txn, ObservedPayment payment, TransactionEvent.StatusChange statusChange) { TransactionEvent event = TransactionEvent.builder() .eventId(UUID.randomUUID().toString()) @@ -160,7 +144,7 @@ TransactionEvent receivedPaymentToEvent(Sep31Transaction txn, ObservedPayment pa .id(txn.getId()) .sep(TransactionEvent.Sep.SEP_31) .kind(TransactionEvent.Kind.RECEIVE) - .status(newStatus) + .status(statusChange.getTo()) .statusChange(statusChange) .amountExpected(new Amount(txn.getAmountExpected(), txn.getAmountInAsset())) .amountIn(new Amount(payment.getAmount(), txn.getAmountInAsset())) @@ -197,12 +181,51 @@ TransactionEvent receivedPaymentToEvent(Sep31Transaction txn, ObservedPayment pa .custodialTransactionId(null) .sourceAccount(payment.getFrom()) .destinationAccount(payment.getTo()) - .customers(new Customers(senderStellarId, receiverStellarId)) + .customers(txn.getCustomers()) .creator(txn.getCreator()) .build(); return event; } + void updateTransactionStatusTo( + SepTransactionStatus newStatus, Sep31Transaction txn, ObservedPayment payment) { + // parse payment creation time + Instant paymentTime = null; + try { + paymentTime = DateTimeFormatter.ISO_INSTANT.parse(payment.getCreatedAt(), Instant::from); + } catch (DateTimeParseException | NullPointerException ex) { + Log.error( + String.format("error parsing payment.getCreatedAt() (%s).", payment.getCreatedAt())); + ex.printStackTrace(); + } + + // update the transaction differently based on the new status + switch (newStatus) { + case ERROR: + if (paymentTime != null) { + txn.setUpdatedAt(paymentTime); + } + txn.setStatus(ERROR.getName()); + saveTransaction(txn); + Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", ERROR.getName()) + .increment(); + break; + + case PENDING_RECEIVER: + if (paymentTime != null) { + txn.setUpdatedAt(paymentTime); + txn.setTransferReceivedAt(paymentTime); + } + txn.setStatus(PENDING_RECEIVER.toString()); + txn.setStellarTransactionId(payment.getTransactionHash()); + saveTransaction(txn); + Metrics.counter("sep31.transaction", "status", PENDING_RECEIVER.toString()).increment(); + + default: + Log.errorF("Unsupported new status {}.", newStatus); + } + } + void saveTransaction(Sep31Transaction txn) { try { transactionStore.save(txn); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt index d2a21d1dce..928615fb79 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt @@ -17,11 +17,14 @@ import org.stellar.anchor.event.models.* import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.data.JdbcSep31TransactionStore import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment +import org.stellar.anchor.sep31.Sep31Transaction +import org.stellar.anchor.util.GsonUtils class PaymentOperationToEventListenerTest { @MockK(relaxed = true) private lateinit var transactionStore: JdbcSep31TransactionStore @MockK(relaxed = true) private lateinit var eventPublishService: EventPublishService private lateinit var paymentOperationToEventListener: PaymentOperationToEventListener + private val gson = GsonUtils.getInstance() @BeforeEach fun setup() { @@ -95,9 +98,12 @@ class PaymentOperationToEventListenerTest { slotMemo = slot() p.transactionMemo = "my_memo_5" p.assetCode = "FOO" + p.assetName = "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" p.amount = "9.9999999" + p.createdAt = "2022-08-09T17:32:44Z" sep31TxMock = JdbcSep31Transaction() - sep31TxMock.amountInAsset = "FOO" + sep31TxMock.amountInAsset = + "stellar:FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" sep31TxMock.amountIn = "10" every { transactionStore.findByStellarMemo(capture(slotMemo)) } returns sep31TxMock paymentOperationToEventListener.onReceived(p) @@ -109,8 +115,8 @@ class PaymentOperationToEventListenerTest { @Test fun test_onReceiver_success() { val startedAtMock = Instant.now().minusSeconds(120) - val createdAt = Instant.now() - val createdAtStr = DateTimeFormatter.ISO_INSTANT.format(createdAt) + val transferReceivedAt = Instant.now() + val transferReceivedAtStr = DateTimeFormatter.ISO_INSTANT.format(transferReceivedAt) val fooAsset = "stellar:FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" val barAsset = "stellar:BAR:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" @@ -126,7 +132,7 @@ class PaymentOperationToEventListenerTest { p.sourceAccount = "GCJKWN7ELKOXLDHJTOU4TZOEJQL7TYVVTQFR676MPHHUIUDAHUA7QGJ4" p.from = "GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO" p.to = "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" - p.createdAt = createdAtStr + p.createdAt = transferReceivedAtStr p.transactionEnvelope = "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" p.id = "755914248193" @@ -146,8 +152,8 @@ class PaymentOperationToEventListenerTest { sep31TxMock.amountFeeAsset = fooAsset sep31TxMock.quoteId = "cef1fc13-3f65-4612-b1f2-502d698c816b" sep31TxMock.startedAt = startedAtMock - sep31TxMock.updatedAt = createdAt - sep31TxMock.transferReceivedAt = createdAt + sep31TxMock.updatedAt = startedAtMock + sep31TxMock.transferReceivedAt = null // the event should have a valid `transferReceivedAt` sep31TxMock.stellarMemo = "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=" sep31TxMock.stellarMemoType = "hash" sep31TxMock.status = SepTransactionStatus.PENDING_SENDER.toString() @@ -158,7 +164,11 @@ class PaymentOperationToEventListenerTest { .account("GBE4B7KE62NUBFLYT3BIG4OP5DAXBQX2GSZZOVAYXQKJKIU7P6V2R2N4") .build() - every { transactionStore.findByStellarMemo(capture(slotMemo)) } returns sep31TxMock + val sep31TxCopy = gson.fromJson(gson.toJson(sep31TxMock), JdbcSep31Transaction::class.java) + every { transactionStore.findByStellarMemo(capture(slotMemo)) } returns sep31TxCopy + + val slotTx = slot() + every { transactionStore.save(capture(slotTx)) } returns sep31TxMock val wantEvent = TransactionEvent.builder() @@ -179,8 +189,8 @@ class PaymentOperationToEventListenerTest { .amountFee(Amount("0.5", fooAsset)) .quoteId("cef1fc13-3f65-4612-b1f2-502d698c816b") .startedAt(startedAtMock) - .updatedAt(createdAt) - .transferReceivedAt(createdAt) + .updatedAt(transferReceivedAt) + .transferReceivedAt(transferReceivedAt) .message("Incoming payment for SEP-31 transaction") .sourceAccount("GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO") .destinationAccount("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") @@ -201,7 +211,7 @@ class PaymentOperationToEventListenerTest { .id("1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30") .memo("OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=") .memoType("hash") - .createdAt(createdAt) + .createdAt(transferReceivedAt) .envelope( "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" ) @@ -236,5 +246,15 @@ class PaymentOperationToEventListenerTest { wantEvent.eventId = slotEvent.captured.eventId assertEquals(wantEvent, slotEvent.captured) + + // wantSep31Tx + val wantSep31Tx = gson.fromJson(gson.toJson(sep31TxMock), JdbcSep31Transaction::class.java) + wantSep31Tx.status = TransactionEvent.Status.PENDING_RECEIVER.status + wantSep31Tx.stellarTransactionId = + "1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30" + wantSep31Tx.transferReceivedAt = transferReceivedAt + wantSep31Tx.updatedAt = transferReceivedAt + + assertEquals(wantSep31Tx, slotTx.captured) } } From 3ddb1818853b68063e32248813cdfd1d3af38847 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 10 Aug 2022 16:40:20 -0700 Subject: [PATCH 18/65] Gradle clean up and define dependency aliases in `libs.versions.toml` file. (#477) * Alias non-spring dependencies * use aliases in build gradle files * use alias in plugin scope * upgraded kotlin to 1.6.20 * fixed the kotlin plugin loaded multiple time issue * add the missing alias org.springframework.cloud:spring-cloud-aws-messaging --- anchor-reference-server/build.gradle.kts | 17 ++-- build.gradle.kts | 50 ++++------ core/build.gradle.kts | 16 +++- gradle/libs.versions.toml | 111 +++++++++++++++++++++++ integration-tests/build.gradle.kts | 15 ++- platform/build.gradle.kts | 20 ++-- service-runner/build.gradle.kts | 10 +- settings.gradle.kts | 37 +------- 8 files changed, 181 insertions(+), 95 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/anchor-reference-server/build.gradle.kts b/anchor-reference-server/build.gradle.kts index 7bf6aecc51..cbbf7bcf34 100644 --- a/anchor-reference-server/build.gradle.kts +++ b/anchor-reference-server/build.gradle.kts @@ -1,8 +1,10 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") plugins { application - id("org.springframework.boot") version "2.6.3" - id("io.spring.dependency-management") version "1.0.11.RELEASE" - id("org.jetbrains.kotlin.jvm") version "1.6.10" + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.kotlin.jvm) } dependencies { @@ -12,7 +14,10 @@ dependencies { implementation("org.springframework.boot:spring-boot-autoconfigure") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") - implementation("com.h2database:h2") + + implementation(libs.spring.aws.messaging) + implementation(libs.spring.kafka) + implementation(libs.h2database) implementation(libs.sqlite.jdbc) implementation(libs.google.gson) implementation(libs.java.stellar.sdk) @@ -22,10 +27,6 @@ dependencies { implementation(project(":core")) annotationProcessor(libs.lombok) - annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") - - testImplementation("org.springframework.boot:spring-boot-starter-test") } application { mainClass.set("org.stellar.anchor.reference.AnchorReferenceServer") } - diff --git a/build.gradle.kts b/build.gradle.kts index 020fea431a..4fc01ee446 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,25 +1,19 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") plugins { java - id("com.diffplug.spotless") version "6.2.1" + alias(libs.plugins.spotless) } tasks { register("installLocalGitHook") { - from("scripts/pre-commit.sh") { - rename { it.removeSuffix(".sh") } - } + from("scripts/pre-commit.sh") { rename { it.removeSuffix(".sh") } } into(".git/hooks") - doLast { - project.exec { - commandLine("chmod", "+x", ".git/hooks/pre-commit") - } - } + doLast { project.exec { commandLine("chmod", "+x", ".git/hooks/pre-commit") } } } - "build" { - dependsOn("installLocalGitHook") - } + "build" { dependsOn("installLocalGitHook") } } subprojects { @@ -70,15 +64,13 @@ subprojects { dependencies { // This is to fix the missing implementation in JSR305 that causes "unknown enum constant // When.MAYBE" warning. - implementation("com.google.code.findbugs:jsr305:3.0.2") - implementation("com.amazonaws:aws-java-sdk-sqs:1.12.200") - implementation("org.apache.kafka:kafka-clients:3.1.0") - implementation("org.apache.kafka:connect:3.1.0") - implementation("io.confluent:kafka-json-schema-serializer:7.0.1") - implementation("org.springframework.kafka:spring-kafka:2.8.4") - implementation("org.springframework.cloud:spring-cloud-aws-messaging:2.2.6.RELEASE") - implementation("org.postgresql:postgresql:42.3.5") - implementation("org.apache.logging.log4j:log4j-layout-template-json:2.14.1") + implementation(rootProject.libs.findbugs.jsr305) + implementation(rootProject.libs.aws.sqs) + implementation(rootProject.libs.postgresql) + implementation(rootProject.libs.bundles.kafka) + + // TODO: we should use log4j2 + implementation(rootProject.libs.log4j.template.json) // The common dependencies are declared here because we would like to have a uniform unit // testing across all subprojects. @@ -86,16 +78,12 @@ subprojects { // We need to use the dependency string because the VERSION_CATEGORY feature isn't supported in // subproject task as of today. // - testImplementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.10") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.6.10") - testImplementation("io.mockk:mockk:1.12.2") - testImplementation("org.junit.platform:junit-platform-suite-engine:1.8.2") - testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2") - testImplementation("org.skyscreamer:jsonassert:1.5.0") - - testAnnotationProcessor("org.projectlombok:lombok:1.18.22") + testImplementation(rootProject.libs.bundles.junit) + testImplementation(rootProject.libs.bundles.kotlin) + testImplementation(rootProject.libs.jsonassert) + testImplementation(rootProject.libs.mockk) + + testAnnotationProcessor(rootProject.libs.lombok) } /** diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 95bfb0ff4d..2546355b79 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,8 +1,9 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") plugins { `java-library` `maven-publish` signing - id("org.jetbrains.kotlin.jvm") version "1.6.10" } version = "1.0.2" @@ -13,7 +14,16 @@ dependencies { compileOnly(libs.slf4j.api) api(libs.lombok) - implementation("commons-beanutils:commons-beanutils:1.9.4") + // TODO: To be removed or simplified. Spring has its own way of dependency management. + implementation(libs.spring.kafka) + + implementation(libs.bundles.kafka) + + // TODO: Consider to simplify + implementation(libs.micrometer.prometheus) + implementation(libs.javax.transaction.api) + + implementation(libs.commons.beanutils) implementation(libs.apache.commons.lang3) implementation(libs.log4j2.core) implementation(libs.httpclient) @@ -25,8 +35,6 @@ dependencies { implementation(libs.reactor.core) implementation(libs.javax.jaxb.api) implementation(libs.java.stellar.sdk) - implementation("io.micrometer:micrometer-registry-prometheus:1.9.0") - implementation("javax.transaction:javax.transaction-api:1.3") implementation(project(":api-schema")) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..c07637733c --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,111 @@ +[versions] +# Library versions +apache-commons-lang3 = "3.12.0" +aws-iam-auth = "1.1.4" +aws-rds = "1.12.248" +aws-sqs = "1.12.200" +commons-beanutils = "1.9.4" +commons-cli = "1.5.0" +commons-codec = "1.15" +commons-io = "2.11.0" +commons-validator = "1.7" +findbugs-jsr305 = "3.0.2" +flyway-core = "8.5.13" +google-gson = "2.8.9" +httpclient = "4.5.13" +h2database = "1.4.200" +jackson-dataformat-yaml = "2.13.2" +java-stellar-sdk = "0.34.1" +javax-jaxb-api = "2.3.1" +javax-transaction-api = "1.3" +jjwt = "0.9.1" +jsonassert = "1.5.0" +junit = "5.8.2" +junit-suite-engine = "1.8.2" +kafka = "3.1.0" +kafka-json-schema = "7.0.1" +kotlin = "1.6.20" +log4j = "2.17.1" +log4j-template-json = "2.14.1" +lombok = "1.18.22" +mockk = "1.12.2" +micrometer-prometheus = "1.9.0" +okhttp3 = "4.9.3" +postgresql = "42.3.5" +reactor-core = "3.4.14" +reactor-netty = "1.0.15" +servlet-api = "2.5" +spring-kafka = "2.8.4" +spring-aws-messaging = "2.2.6.RELEASE" +sqlite-jdbc = "3.34.0" +slf4j = "1.7.35" +toml4j = "0.7.2" + + +# Plugin versions +spotless = "6.2.1" +spring-boot = "2.6.3" +spring-dependency-management = "1.0.11.RELEASE" + +[libraries] +apache-commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "apache-commons-lang3" } +aws-rds = { module = "com.amazonaws:aws-java-sdk-rds", version.ref = "aws-rds" } +aws-sqs = { module = "com.amazonaws:aws-java-sdk-sqs", version.ref = "aws-sqs" } +aws-iam-auth = { module = "software.amazon.msk:aws-msk-iam-auth", version.ref = "aws-iam-auth" } +commons-beanutils = { module = "commons-beanutils:commons-beanutils", version.ref = "commons-beanutils" } +commons-cli = { module = "commons-cli:commons-cli", version.ref = "commons-cli" } +commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" } +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } +commons-validator = { module = "commons-validator:commons-validator", version.ref = "commons-validator" } +findbugs-jsr305 = { module = "com.google.code.findbugs:jsr305", version.ref = "findbugs-jsr305" } +flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway-core" } +google-gson = { module = "com.google.code.gson:gson", version.ref = "google-gson" } +httpclient = { module = "org.apache.httpcomponents:httpclient", version.ref = "httpclient" } +h2database = { module = "com.h2database:h2", version.ref = "h2database" } +jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson-dataformat-yaml" } +java-stellar-sdk = { module = "com.github.stellar:java-stellar-sdk", version.ref = "java-stellar-sdk" } +javax-jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "javax-jaxb-api" } +javax-transaction-api = { module = "javax.transaction:javax.transaction-api", version.ref = "javax-transaction-api" } +jjwt = { module = "io.jsonwebtoken:jjwt", version.ref = "jjwt" } +jsonassert = { module = "org.skyscreamer:jsonassert", version.ref = "jsonassert" } +junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } +junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" } +junit-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit" } +junit-suite-engine = { module = "org.junit.platform:junit-platform-suite-engine", version.ref = "junit-suite-engine" } +kafka-clients = { module = "org.apache.kafka:kafka-clients", version.ref = "kafka" } +kafka-connect = { module = "org.apache.kafka:connect", version.ref = "kafka" } +kafka-json-schema = { module = "io.confluent:kafka-json-schema-serializer", version.ref = "kafka-json-schema" } +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlin-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" } +log4j2-slf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } +log4j2-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } +log4j2-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } +# TODO: upgrade to log4j2 +log4j-template-json = { module = "org.apache.logging.log4j:log4j-layout-template-json", version.ref = "log4j-template-json" } +lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } +micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus", version.ref = "micrometer-prometheus" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } +okhttp3-mockserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp3" } +postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } +reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "reactor-core" } +reactor-netty = { module = "io.projectreactor.netty:reactor-netty", version.ref = "reactor-netty" } +servlet-api = { module = "javax.servlet:servlet-api", version.ref = "servlet-api" } +sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" } +slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } +slf4j-log4j12 = { module = "org.slf4j:slf4j-log4j12", version.ref = "slf4j" } +toml4j = { module = "com.moandjiezana.toml:toml4j", version.ref = "toml4j" } +spring-kafka = { module = "org.springframework.kafka:spring-kafka", version.ref = "spring-kafka" } +spring-aws-messaging = { module = "org.springframework.cloud:spring-cloud-aws-messaging", version.ref = "spring-aws-messaging" } + +[bundles] +slf4j = ["slf4j-api", "slf4j-log4j12"] +junit = ["junit-api", "junit-engine", "junit-params", "junit-suite-engine"] +kafka = ["kafka-clients", "kafka-connect", "kafka-json-schema"] +kotlin = ["kotlin-stdlib", "kotlin-junit"] + +[plugins] +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" } +spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } \ No newline at end of file diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index fcb1a89728..0aa9b10984 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -1,11 +1,15 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") plugins { `java-library` - id("org.springframework.boot") version "2.6.3" - id("io.spring.dependency-management") version "1.0.11.RELEASE" - id("org.jetbrains.kotlin.jvm") version "1.6.10" + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) } dependencies { + implementation("org.springframework.boot:spring-boot") + implementation("org.springframework.boot:spring-boot-autoconfigure") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") implementation(libs.commons.cli) @@ -22,9 +26,10 @@ dependencies { implementation(project(":anchor-reference-server")) implementation(project(":service-runner")) + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") + testImplementation(libs.okhttp3.mockserver) + testImplementation("org.springframework.boot:spring-boot-starter-test") } tasks { bootJar { enabled = false } } - - diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index 0fdf2dab48..51ecd6d708 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -1,8 +1,10 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") plugins { application - id("org.springframework.boot") version "2.6.3" - id("io.spring.dependency-management") version "1.0.11.RELEASE" - id("org.jetbrains.kotlin.jvm") version "1.6.10" + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.kotlin.jvm) } dependencies { @@ -16,15 +18,15 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-reactor-netty") - implementation("com.amazonaws:aws-java-sdk-rds:1.12.248") - implementation("software.amazon.msk:aws-msk-iam-auth:1.1.4") - implementation("org.springframework.boot:spring-boot-starter-actuator:2.7.0") - implementation("org.springframework.boot:spring-boot-starter-aop:2.7.0") - implementation("org.flywaydb:flyway-core:8.5.13") - + implementation("org.springframework.boot:spring-boot-starter-actuator") + implementation("org.springframework.boot:spring-boot-starter-aop") + implementation(libs.spring.aws.messaging) + implementation(libs.spring.kafka) + implementation(libs.aws.rds) implementation(libs.commons.cli) implementation(libs.commons.io) + implementation(libs.flyway.core) implementation(libs.google.gson) implementation(libs.java.stellar.sdk) diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index f16f143848..67f99322c7 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -1,11 +1,17 @@ +@Suppress( + // The alias call in plugins scope produces IntelliJ false error which is suppressed here. + "DSL_SCOPE_VIOLATION" +) + plugins { application - id("org.springframework.boot") version "2.6.3" - id("io.spring.dependency-management") version "1.0.11.RELEASE" + alias(libs.plugins.spring.boot) + alias(libs.plugins.spring.dependency.management) } dependencies { implementation("org.springframework.boot:spring-boot-starter-web") + implementation(libs.commons.cli) implementation(libs.okhttp3) diff --git a/settings.gradle.kts b/settings.gradle.kts index 41a70ec093..7ef23c2ed8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,40 +1,5 @@ rootProject.name = "java-stellar-anchor-sdk" -enableFeaturePreview("VERSION_CATALOGS") - -dependencyResolutionManagement { - versionCatalogs { - create("libs") { - alias("apache.commons.lang3").to("org.apache.commons:commons-lang3:3.12.0") - alias("commons.beanutils").to("commons-beanutils:commons-beanutils:1.9.4") - alias("commons.cli").to("commons-cli:commons-cli:1.5.0") - alias("commons.codec").to("commons-codec:commons-codec:1.15") - alias("commons.io").to("commons-io:commons-io:2.11.0") - alias("commons.validator").to("commons-validator:commons-validator:1.7") - alias("google.gson").to("com.google.code.gson:gson:2.8.9") - alias("httpclient").to("org.apache.httpcomponents:httpclient:4.5.13") - alias("jackson.dataformat.yaml") - .to("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.2") - alias("java.stellar.sdk").to("com.github.stellar:java-stellar-sdk:0.34.1") - alias("javax.jaxb.api").to("javax.xml.bind:jaxb-api:2.3.1") - alias("jjwt").to("io.jsonwebtoken:jjwt:0.9.1") - alias("log4j2.api").to("org.apache.logging.log4j:log4j-api:.17.1") - alias("log4j2.core").to("org.apache.logging.log4j:log4j-core:2.17.1") - alias("log4j2.slf4j").to("org.apache.logging.log4j:log4j-slf4j-impl:2.17.1") - alias("lombok").to("org.projectlombok:lombok:1.18.22") - alias("okhttp3").to("com.squareup.okhttp3:okhttp:4.9.3") - alias("okhttp3.mockserver").to("com.squareup.okhttp3:mockwebserver:4.9.3") - alias("reactor.core").to("io.projectreactor:reactor-core:3.4.14") - alias("reactor.netty").to("io.projectreactor.netty:reactor-netty:1.0.15") - alias("servlet.api").to("javax.servlet:servlet-api:2.5") - alias("sqlite.jdbc").to("org.xerial:sqlite-jdbc:3.34.0") - alias("slf4j.api").to("org.slf4j:slf4j-api:1.7.35") - alias("slf4j.log4j12").to("org.slf4j:slf4j-log4j12:1.7.33") - alias("toml4j").to("com.moandjiezana.toml:toml4j:0.7.2") - } - } -} - /** APIs and Schemas */ include("api-schema") @@ -46,8 +11,8 @@ include("platform") /** Anchor Reference Server */ include("anchor-reference-server") - /** Integration tests */ include("integration-tests") +/** Service runners */ include("service-runner") From 4e2e46ed1627c3d0448f31cad29bcf6102284a05 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Wed, 10 Aug 2022 22:04:21 -0300 Subject: [PATCH 19/65] Support SEP-31 non-happy-path where the Transaction is marked as `PENDING_CUSTOMER_INFO_UPDATE` (#448) ### What Support SEP-31 non-happy-path where the Transaction is marked as `PENDING_CUSTOMER_INFO_UPDATE`. The changes include: * Update the sequence diagram to better detail the error handling. * Write integration tests for this use case. * Write further unit tests for the PlatformAPI `TransactionService`. * Improve the object schema conversion between database Sep31Transaction and Platform Api objects (also, including the appropriate tests) ### Why We were not testing the case where the SEP-31 transaction failed to be completed by the Receiving Anchor backend and the Sending Anchor had to update the customer information. This flow was not clear in the Platform sequence diagram either. Close #183. ### Known limitations - [ ] #473 - [ ] #484 --- .../controller/CustomerController.java | 13 + .../reference/service/CustomerService.java | 20 ++ .../stellar/anchor/sep31/RefundPayment.java | 55 ++++ .../org/stellar/anchor/sep31/Refunds.java | 69 +++++ .../anchor/sep31/Sep31Transaction.java | 84 ++++-- .../org/stellar/anchor/util/MathHelper.java | 9 + .../org/stellar/anchor/util/SepHelper.java | 5 +- .../anchor/dto/sep38/InfoResponseTest.kt | 9 + .../stellar/anchor/sep31/RefundPaymentTest.kt | 101 +++++++ .../anchor/sep31/RefundsBuilderTest.kt | 56 ++++ .../org/stellar/anchor/sep31/RefundsTest.kt | 179 ++++++++++++ .../anchor/sep31/Sep31TransactionTest.kt | 233 +++++++++++++++ .../org/stellar/anchor/util/MathHelperTest.kt | 36 +++ docs/00 - Stellar Anchor Platform.md | 91 +++--- .../stellar/anchor/platform/Sep12Client.kt | 18 +- .../stellar/anchor/platform/Sep31Client.kt | 10 + .../platform/AnchorPlatformIntegrationTest.kt | 6 + .../stellar/anchor/platform/PlatformTests.kt | 115 +++++++- .../org/stellar/anchor/platform/Sep31Tests.kt | 22 +- .../platform/data/JdbcSep31Transaction.java | 1 - .../platform/service/TransactionService.java | 276 +++++++++--------- .../service/TransactionServiceTest.kt | 223 +++++++++++++- 22 files changed, 1414 insertions(+), 217 deletions(-) create mode 100644 core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt create mode 100644 core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt create mode 100644 core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt create mode 100644 core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt create mode 100644 core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java index 61d3e431c9..efc57be564 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java @@ -41,4 +41,17 @@ public PutCustomerResponse putCustomer(@RequestBody PutCustomerRequest request) public void deleteCustomer(@PathVariable String id) { customerService.delete(id); } + + /** + * ATTENTION: this function is used for testing purposes only. + * + *

This endpoint is used to delete a customer's `clabe_number`, which would make its state + * change to NEEDS_INFO if it's a receiving customer. + */ + @RequestMapping( + value = "/invalidate_clabe/{id}", + method = {RequestMethod.GET}) + public void invalidateCustomerClabe(@PathVariable String id) throws NotFoundException { + customerService.invalidateCustomerClabe(id); + } } diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java index a71d8f672a..537d9bab2a 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/CustomerService.java @@ -72,6 +72,26 @@ public PutCustomerResponse upsertCustomer(PutCustomerRequest request) throws Not return response; } + /** + * ATTENTION: this function is used for testing purposes only. + * + *

This method is used to delete a customer's `clabe_number`, which would make its state change + * to NEEDS_INFO if it's a receiving customer. + * + * @param customerId is the id of the customer whose `clabe_number` will be deleted. + * @throws NotFoundException if the user was not found. + */ + public void invalidateCustomerClabe(String customerId) throws NotFoundException { + Optional maybeCustomer = customerRepo.findById(customerId); + if (maybeCustomer.isEmpty()) { + throw new NotFoundException(String.format("customer for 'id' '%s' not found", customerId)); + } + + Customer customer = maybeCustomer.get(); + customer.setClabeNumber(null); + customerRepo.save(customer); + } + public void delete(String customerId) { customerRepo.deleteById(customerId); } diff --git a/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java b/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java index ff410774b4..1b60503834 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java +++ b/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java @@ -1,5 +1,8 @@ package org.stellar.anchor.sep31; +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse; +import org.stellar.anchor.api.shared.Amount; + public interface RefundPayment { String getId(); @@ -12,4 +15,56 @@ public interface RefundPayment { String getFee(); void setFee(String fee); + + /** + * Will create a Sep31GetTransactionResponse.Sep31RefundPayment object out of this SEP-31 + * RefundPayment object. + * + * @return a Sep31GetTransactionResponse.Sep31RefundPayment. + */ + default Sep31GetTransactionResponse.Sep31RefundPayment toSep31RefundPayment() { + return Sep31GetTransactionResponse.Sep31RefundPayment.builder() + .id(getId()) + .amount(getAmount()) + .fee(getFee()) + .build(); + } + + /** + * Will create a PlatformApi RefundPayment object out of this SEP-31 RefundPayment object. + * + * @param assetName is the full asset name in the {schema}:{code}:{issuer} format. + * @return a PlatformApi RefundPayment object. + */ + default org.stellar.anchor.api.shared.RefundPayment toPlatformApiRefundPayment(String assetName) { + return org.stellar.anchor.api.shared.RefundPayment.builder() + .id(getId()) + .idType(org.stellar.anchor.api.shared.RefundPayment.IdType.STELLAR) + .amount(new Amount(getAmount(), assetName)) + .fee(new Amount(getFee(), assetName)) + .requestedAt(null) + .refundedAt(null) + .build(); + } + + /** + * Will create a SEP-31 RefundPayment object out of a PlatformApi RefundPayment object. + * + * @param platformApiRefundPayment is the platformApi's RefundPayment object. + * @param factory is a Sep31TransactionStore instance used to build the object. + * @return a SEP-31 RefundPayment object. + */ + static RefundPayment of( + org.stellar.anchor.api.shared.RefundPayment platformApiRefundPayment, + Sep31TransactionStore factory) { + if (platformApiRefundPayment == null) { + return null; + } + + RefundPayment refundPayment = factory.newRefundPayment(); + refundPayment.setId(platformApiRefundPayment.getId()); + refundPayment.setAmount(platformApiRefundPayment.getAmount().getAmount()); + refundPayment.setFee(platformApiRefundPayment.getFee().getAmount()); + return refundPayment; + } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Refunds.java b/core/src/main/java/org/stellar/anchor/sep31/Refunds.java index a24b3382b7..e83edc812e 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Refunds.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Refunds.java @@ -1,6 +1,11 @@ package org.stellar.anchor.sep31; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse; +import org.stellar.anchor.api.shared.Amount; public interface Refunds { @@ -15,4 +20,68 @@ public interface Refunds { List getRefundPayments(); void setRefundPayments(List refundPayments); + + /** + * Will create a Sep31GetTransactionResponse.Refunds object out of this SEP-31 Refunds object. + * + * @return a Sep31GetTransactionResponse.Refunds object. + */ + default Sep31GetTransactionResponse.Refunds toSep31TransactionResponseRefunds() { + List payments = + getRefundPayments().stream() + .map(RefundPayment::toSep31RefundPayment) + .collect(Collectors.toList()); + + return Sep31GetTransactionResponse.Refunds.builder() + .amountRefunded(getAmountRefunded()) + .amountFee(getAmountFee()) + .payments(payments) + .build(); + } + + /** + * Will create a PlatformApi Refund object out of this SEP-31 Refunds object. + * + * @param assetName is the full asset name in the {schema}:{code}:{issuer} format. + * @return a PlatformApi Refund object. + */ + default org.stellar.anchor.api.shared.Refund toPlatformApiRefund(String assetName) { + // build payments + org.stellar.anchor.api.shared.RefundPayment[] payments = + getRefundPayments().stream() + .map(refundPayment -> refundPayment.toPlatformApiRefundPayment(assetName)) + .toArray(org.stellar.anchor.api.shared.RefundPayment[]::new); + + return org.stellar.anchor.api.shared.Refund.builder() + .amountRefunded(new Amount(getAmountRefunded(), assetName)) + .amountFee(new Amount(getAmountFee(), assetName)) + .payments(payments) + .build(); + } + + /** + * Will create a SEP-31 Refunds object out of a PlatformApi Refund object. + * + * @param platformApiRefunds is the platformApi's Refund object. + * @param factory is a Sep31TransactionStore instance used to build the object. + * @return a SEP-31 Refunds object. + */ + static Refunds of( + org.stellar.anchor.api.shared.Refund platformApiRefunds, Sep31TransactionStore factory) { + if (platformApiRefunds == null) { + return null; + } + + Refunds refunds = factory.newRefunds(); + refunds.setAmountRefunded(platformApiRefunds.getAmountRefunded().getAmount()); + refunds.setAmountFee(platformApiRefunds.getAmountFee().getAmount()); + + ArrayList payments = + Arrays.stream(platformApiRefunds.getPayments()) + .map(refundPayment -> RefundPayment.of(refundPayment, factory)) + .collect(Collectors.toCollection(ArrayList::new)); + refunds.setRefundPayments(payments); + + return refunds; + } } 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 c2f8efb6db..db201cb510 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -1,13 +1,17 @@ package org.stellar.anchor.sep31; import java.time.Instant; -import java.util.ArrayList; import java.util.List; import java.util.Map; +import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse; +import org.stellar.anchor.api.shared.Amount; import org.stellar.anchor.api.shared.Customers; +import org.stellar.anchor.api.shared.Refund; import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.event.models.TransactionEvent; +import org.stellar.anchor.util.StringHelper; public interface Sep31Transaction { String getId(); @@ -96,12 +100,12 @@ public interface Sep31Transaction { String getRequiredInfoMessage(); + void setRequiredInfoMessage(String requiredInfoMessage); + Map getFields(); void setFields(Map fields); - void setRequiredInfoMessage(String requiredInfoMessage); - AssetInfo.Sep31TxnFieldSpecs getRequiredInfoUpdates(); void setRequiredInfoUpdates(AssetInfo.Sep31TxnFieldSpecs requiredInfoUpdates); @@ -132,31 +136,15 @@ default Customers getCustomers() { StellarId.builder().id(getReceiverId()).build()); } + /** + * Create a Sep31GetTransactionResponse object out of this SEP-31 Transaction object. + * + * @return a Sep31GetTransactionResponse object. + */ default Sep31GetTransactionResponse toSep31GetTransactionResponse() { Sep31GetTransactionResponse.Refunds refunds = null; - if (this.getRefunds() != null) { - List payments = null; - if (this.getRefunds().getRefundPayments() != null) { - for (RefundPayment refundPayment : this.getRefunds().getRefundPayments()) { - if (payments == null) { - payments = new ArrayList<>(); - } - - payments.add( - Sep31GetTransactionResponse.Sep31RefundPayment.builder() - .id(refundPayment.getId()) - .amount(refundPayment.getAmount()) - .fee(refundPayment.getFee()) - .build()); - } - } - - refunds = - Sep31GetTransactionResponse.Refunds.builder() - .amountRefunded(this.getRefunds().getAmountRefunded()) - .amountFee(this.getRefunds().getAmountFee()) - .payments(payments) - .build(); + if (getRefunds() != null) { + refunds = getRefunds().toSep31TransactionResponseRefunds(); } return Sep31GetTransactionResponse.builder() @@ -185,4 +173,48 @@ default Sep31GetTransactionResponse toSep31GetTransactionResponse() { .build()) .build(); } + + /** + * Create a PlatformApi GetTransactionResponse object out of this SEP-31 Transaction object. + * + * @return a PlatformApi GetTransactionResponse object. + */ + default org.stellar.anchor.api.platform.GetTransactionResponse + toPlatformApiGetTransactionResponse() { + Refund refunds = null; + if (getRefunds() != null) { + refunds = getRefunds().toPlatformApiRefund(getAmountInAsset()); + } + + List stellarTransactions = null; + if (!StringHelper.isEmpty(getStellarTransactionId())) { + GetTransactionResponse.StellarTransaction stellarTxn = + new GetTransactionResponse.StellarTransaction(); + stellarTxn.setId(getStellarTransactionId()); + stellarTransactions = List.of(stellarTxn); + } + + return org.stellar.anchor.api.platform.GetTransactionResponse.builder() + .id(getId()) + .sep(31) + .kind(TransactionEvent.Kind.RECEIVE.getKind()) + .status(getStatus()) + .amountExpected(new Amount(getAmountExpected(), getAmountInAsset())) + .amountIn(new Amount(getAmountIn(), getAmountInAsset())) + .amountOut(new Amount(getAmountOut(), getAmountOutAsset())) + .amountFee(new Amount(getAmountFee(), getAmountFeeAsset())) + .quoteId(getQuoteId()) + .startedAt(getStartedAt()) + .updatedAt(getUpdatedAt()) + .completedAt(getCompletedAt()) + .transferReceivedAt(getTransferReceivedAt()) + .message(getRequiredInfoMessage()) // Assuming these are meant to be the same. + .refunds(refunds) + .stellarTransactions(stellarTransactions) + .externalTransactionId(getExternalTransactionId()) + // TODO .custodialTransactionId(txn.get) + .customers(getCustomers()) + .creator(getCreator()) + .build(); + } } diff --git a/core/src/main/java/org/stellar/anchor/util/MathHelper.java b/core/src/main/java/org/stellar/anchor/util/MathHelper.java index 2d37cf15c6..2fe371661d 100644 --- a/core/src/main/java/org/stellar/anchor/util/MathHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/MathHelper.java @@ -26,6 +26,15 @@ public static BigDecimal decimal(Long value) { return BigDecimal.valueOf(value); } + public static boolean equalsAsDecimals(String valueA, String valueB) { + if (valueA == null && valueB == null) { + return true; + } else if (valueA == null || valueB == null) { + return false; + } + return decimal(valueA).compareTo(decimal(valueB)) == 0; + } + public static String formatAmount(BigDecimal amount, Integer decimals) { BigDecimal newAmount = amount.setScale(decimals, RoundingMode.HALF_DOWN); diff --git a/core/src/main/java/org/stellar/anchor/util/SepHelper.java b/core/src/main/java/org/stellar/anchor/util/SepHelper.java index 1d73bd465e..dd3755d9a8 100644 --- a/core/src/main/java/org/stellar/anchor/util/SepHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/SepHelper.java @@ -6,7 +6,6 @@ import java.math.BigDecimal; import java.util.List; -import java.util.Objects; import java.util.UUID; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.BadRequestException; @@ -56,9 +55,9 @@ public static void validateAmount(String amount) throws AnchorException { } public static BigDecimal validateAmount(String messagePrefix, String amount) - throws AnchorException { + throws BadRequestException { // assetName - if (Objects.toString(amount, "").isEmpty()) { + if (StringHelper.isEmpty(amount)) { throw new BadRequestException(messagePrefix + "amount cannot be empty"); } diff --git a/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt b/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt index 6607cabdb0..79d727ceb8 100644 --- a/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt @@ -1,5 +1,8 @@ package org.stellar.anchor.dto.sep38 +import io.mockk.clearAllMocks +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -17,6 +20,12 @@ class InfoResponseTest { assertEquals(3, assets.size) } + @AfterEach + fun teardown() { + clearAllMocks() + unmockkAll() + } + @Test fun test_constructor() { val infoResponse = InfoResponse(assets) diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt new file mode 100644 index 0000000000..6b7c76f25f --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt @@ -0,0 +1,101 @@ +package org.stellar.anchor.sep31 + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import java.time.Instant +import kotlin.test.assertEquals +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse +import org.stellar.anchor.api.shared.Amount + +class RefundPaymentTest { + companion object { + private const val stellarUSDC = + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + + @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + every { sep31TransactionStore.newRefundPayment() } answers { PojoSep31RefundPayment() } + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun test_toSep31RefundPayment() { + // mock the SEP-31 RefundPayment object + val mockRefundPayment = PojoSep31RefundPayment() + mockRefundPayment.id = "A" + mockRefundPayment.amount = "50" + mockRefundPayment.fee = "4" + + // mock the Sep31GetTransactionResponse.Sep31RefundPayment object we want + val wantSep31RefundPayment = + Sep31GetTransactionResponse.Sep31RefundPayment.builder().id("A").amount("50").fee("4").build() + + // build the Sep31GetTransactionResponse.Sep31RefundPayment object + val gotSep31RefundPayment = mockRefundPayment.toSep31RefundPayment() + assertEquals(wantSep31RefundPayment, gotSep31RefundPayment) + } + + @Test + fun test_toPlatformApiRefundPayment() { + // mock the SEP-31 RefundPayment object + val mockRefundPayment = PojoSep31RefundPayment() + mockRefundPayment.id = "A" + mockRefundPayment.amount = "50" + mockRefundPayment.fee = "4" + + // mock the PlatformApi RefundPayment object we want + val wantPlatformApiRefundPayment = + org.stellar.anchor.api.shared.RefundPayment.builder() + .id("A") + .idType(org.stellar.anchor.api.shared.RefundPayment.IdType.STELLAR) + .amount(Amount("50", stellarUSDC)) + .fee(Amount("4", stellarUSDC)) + .refundedAt(null) + .refundedAt(null) + .build() + + // build the SEP-31 RefundPayment object + val gotPlatformApiRefundPayment = mockRefundPayment.toPlatformApiRefundPayment(stellarUSDC) + assertEquals(wantPlatformApiRefundPayment, gotPlatformApiRefundPayment) + } + + @Test + fun test_of() { + // mock the PlatformApi RefundPayment object + val mockPlatformApiRefundPayment = + org.stellar.anchor.api.shared.RefundPayment.builder() + .id("A") + .idType(org.stellar.anchor.api.shared.RefundPayment.IdType.STELLAR) + .amount(Amount("50", stellarUSDC)) + .fee(Amount("4", stellarUSDC)) + .refundedAt(Instant.now()) + .refundedAt(Instant.now()) + .build() + + // mock the SEP-31 RefundPayment object we want + val wantRefundPayment = PojoSep31RefundPayment() + wantRefundPayment.id = "A" + wantRefundPayment.amount = "50" + wantRefundPayment.fee = "4" + + // build the SEP-31 RefundPayment object + val gotSep31RefundPayment = + RefundPayment.of(mockPlatformApiRefundPayment, sep31TransactionStore) + assertEquals(wantRefundPayment, gotSep31RefundPayment) + } +} diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt new file mode 100644 index 0000000000..ff56cb2201 --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt @@ -0,0 +1,56 @@ +package org.stellar.anchor.sep31 + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import kotlin.test.assertEquals +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class RefundsBuilderTest { + @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + every { sep31TransactionStore.newRefunds() } returns PojoSep31Refunds() + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun test_allBuilderFields() { + // mock the refundPaymentsList + val refundPayment1 = PojoSep31RefundPayment() + refundPayment1.id = "111" + refundPayment1.amount = "5" + refundPayment1.fee = "1" + val refundPayment2 = PojoSep31RefundPayment() + refundPayment2.id = "222" + refundPayment2.amount = "5" + refundPayment2.fee = "1" + val refundPaymentList = listOf(refundPayment1, refundPayment2) + + // mock the Refunds object we want + val wantRefunds = PojoSep31Refunds() + wantRefunds.amountRefunded = "10" + wantRefunds.amountFee = "2" + wantRefunds.refundPayments = refundPaymentList + + // build the Refunds object. + val gotRefunds = + RefundsBuilder(sep31TransactionStore) + .amountRefunded("10") + .amountFee("2") + .payments(refundPaymentList) + .build() + assertEquals(wantRefunds, gotRefunds) + } +} diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt new file mode 100644 index 0000000000..922a7dc000 --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt @@ -0,0 +1,179 @@ +package org.stellar.anchor.sep31 + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import java.time.Instant +import kotlin.test.assertEquals +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse +import org.stellar.anchor.api.shared.Amount +import org.stellar.anchor.api.shared.RefundPayment + +class RefundsTest { + companion object { + private const val fiatUSD = "iso4217:USD" + private const val stellarUSDC = + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + + @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + every { sep31TransactionStore.newRefunds() } answers { PojoSep31Refunds() } + every { sep31TransactionStore.newRefundPayment() } answers { PojoSep31RefundPayment() } + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun test_toSep31TransactionResponseRefunds() { + // mock the SEP-31 Refunds object + val mockRefundPayment1 = PojoSep31RefundPayment() + mockRefundPayment1.id = "A" + mockRefundPayment1.amount = "50" + mockRefundPayment1.fee = "4" + val mockRefundPayment2 = PojoSep31RefundPayment() + mockRefundPayment2.id = "B" + mockRefundPayment2.amount = "50" + mockRefundPayment2.fee = "4" + val mockRefundPaymentList = listOf(mockRefundPayment1, mockRefundPayment2) + val mockSep31Refunds = PojoSep31Refunds() + mockSep31Refunds.amountRefunded = "100" + mockSep31Refunds.amountFee = "8" + mockSep31Refunds.refundPayments = mockRefundPaymentList + + // mock the Sep31GetTransactionResponse.Sep31RefundPayment we want + val wantSep31GetTransactionResponseRefunds = + Sep31GetTransactionResponse.Refunds.builder() + .amountRefunded("100") + .amountFee("8") + .payments( + listOf( + Sep31GetTransactionResponse.Sep31RefundPayment.builder() + .id("A") + .amount("50") + .fee("4") + .build(), + Sep31GetTransactionResponse.Sep31RefundPayment.builder() + .id("B") + .amount("50") + .fee("4") + .build() + ) + ) + .build() + + // build the Sep31GetTransactionResponse.Sep31RefundPayment object + val gotSep31GetTransactionResponseRefunds = mockSep31Refunds.toSep31TransactionResponseRefunds() + assertEquals(wantSep31GetTransactionResponseRefunds, gotSep31GetTransactionResponseRefunds) + } + + @Test + fun test_toPlatformApiRefund() { + // mock the SEP-31 Refunds object + val mockRefundPayment1 = PojoSep31RefundPayment() + mockRefundPayment1.id = "A" + mockRefundPayment1.amount = "50" + mockRefundPayment1.fee = "4" + val mockRefundPayment2 = PojoSep31RefundPayment() + mockRefundPayment2.id = "B" + mockRefundPayment2.amount = "50" + mockRefundPayment2.fee = "4" + val mockRefundPaymentList = listOf(mockRefundPayment1, mockRefundPayment2) + val mockSep31Refunds = PojoSep31Refunds() + mockSep31Refunds.amountRefunded = "100" + mockSep31Refunds.amountFee = "8" + mockSep31Refunds.refundPayments = mockRefundPaymentList + + // mock the PlatformApi Refund we want + val wantPlatformApiRefund = + org.stellar.anchor.api.shared.Refund.builder() + .amountRefunded(Amount("100", stellarUSDC)) + .amountFee(Amount("8", stellarUSDC)) + .payments( + arrayOf( + RefundPayment.builder() + .id("A") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50", stellarUSDC)) + .fee(Amount("4", stellarUSDC)) + .refundedAt(null) + .refundedAt(null) + .build(), + RefundPayment.builder() + .id("B") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50", stellarUSDC)) + .fee(Amount("4", stellarUSDC)) + .refundedAt(null) + .refundedAt(null) + .build() + ) + ) + .build() + + // build the SEP-31 Refunds object + val gotProtocolApiRefund = mockSep31Refunds.toPlatformApiRefund(stellarUSDC) + assertEquals(wantPlatformApiRefund, gotProtocolApiRefund) + } + + @Test + fun test_of() { + // mock the PlatformApi Refund + val mockPlatformApiRefund = + org.stellar.anchor.api.shared.Refund.builder() + .amountRefunded(Amount("100", fiatUSD)) + .amountFee(Amount("8", stellarUSDC)) + .payments( + arrayOf( + RefundPayment.builder() + .id("A") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50", stellarUSDC)) + .fee(Amount("4", stellarUSDC)) + .refundedAt(Instant.now()) + .refundedAt(Instant.now()) + .build(), + RefundPayment.builder() + .id("B") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50", stellarUSDC)) + .fee(Amount("4", stellarUSDC)) + .refundedAt(Instant.now()) + .refundedAt(Instant.now()) + .build() + ) + ) + .build() + + // mock the SEP-31 Refunds object we want + val wantRefundPayment1 = PojoSep31RefundPayment() + wantRefundPayment1.id = "A" + wantRefundPayment1.amount = "50" + wantRefundPayment1.fee = "4" + val wantRefundPayment2 = PojoSep31RefundPayment() + wantRefundPayment2.id = "B" + wantRefundPayment2.amount = "50" + wantRefundPayment2.fee = "4" + val wantRefundPaymentList = listOf(wantRefundPayment1, wantRefundPayment2) + val wantSep31Refunds = PojoSep31Refunds() + wantSep31Refunds.amountRefunded = "100" + wantSep31Refunds.amountFee = "8" + wantSep31Refunds.refundPayments = wantRefundPaymentList + + // build the SEP-31 Refunds object + val gotSep31Refunds = Refunds.of(mockPlatformApiRefund, sep31TransactionStore) + assertEquals(wantSep31Refunds, gotSep31Refunds) + } +} diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt new file mode 100644 index 0000000000..4dcbe1a841 --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -0,0 +1,233 @@ +package org.stellar.anchor.sep31 + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import java.time.Instant +import kotlin.test.assertEquals +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.anchor.api.platform.GetTransactionResponse +import org.stellar.anchor.api.sep.AssetInfo +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse.Sep31RefundPayment +import org.stellar.anchor.api.shared.* +import org.stellar.anchor.api.shared.RefundPayment +import org.stellar.anchor.event.models.TransactionEvent + +class Sep31TransactionTest { + companion object { + private const val fiatUSD = "iso4217:USD" + private const val stellarUSDC = + "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" + private const val TEST_ACCOUNT = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + private const val TEST_MEMO = "test memo" + } + + @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore + private lateinit var sep31Transaction: Sep31Transaction + + private val txId = "a4baff5f-778c-43d6-bbef-3e9fb41d096e" + + // mock time + private val mockStartedAt = Instant.now().minusSeconds(180) + private val mockUpdatedAt = mockStartedAt.plusSeconds(60) + private val mockTransferReceivedAt = mockUpdatedAt.plusSeconds(60) + private val mockCompletedAt = mockTransferReceivedAt.plusSeconds(60) + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + + // Mock the store + every { sep31TransactionStore.newTransaction() } returns PojoSep31Transaction() + every { sep31TransactionStore.newRefunds() } returns PojoSep31Refunds() + every { sep31TransactionStore.newRefundPayment() } answers { PojoSep31RefundPayment() } + + // Mock the store + every { sep31TransactionStore.newTransaction() } returns PojoSep31Transaction() + every { sep31TransactionStore.newRefunds() } returns PojoSep31Refunds() + every { sep31TransactionStore.newRefundPayment() } answers { PojoSep31RefundPayment() } + + // mock refunds + val mockRefundPayment1 = + RefundPaymentBuilder(sep31TransactionStore).id("1111").amount("50.0000").fee("4.0000").build() + val mockRefundPayment2 = + RefundPaymentBuilder(sep31TransactionStore).id("2222").amount("40.0000").fee("4.0000").build() + val mockRefunds = + RefundsBuilder(sep31TransactionStore) + .amountRefunded("90.0000") + .amountFee("8.0000") + .payments(listOf(mockRefundPayment1, mockRefundPayment2)) + .build() + + // mock missing SEP-31 "transaction.fields" + val mockMissingFields = AssetInfo.Sep31TxnFieldSpecs() + mockMissingFields.transaction = + mapOf( + "receiver_account_number" to + AssetInfo.Sep31TxnFieldSpec("bank account number of the destination", null, false), + ) + + // mock the database SEP-31 transaction + sep31Transaction = + Sep31TransactionBuilder(sep31TransactionStore) + .id(txId) + .status(TransactionEvent.Status.PENDING_RECEIVER.status) + .statusEta(120) + .amountExpected("100") + .amountIn("100.0000") + .amountInAsset(fiatUSD) + .amountOut("98.0000000") + .amountOutAsset(stellarUSDC) + .amountFee("2.0000") + .amountFeeAsset(fiatUSD) + .stellarAccountId(TEST_ACCOUNT) + .stellarMemo(TEST_MEMO) + .stellarMemoType("text") + .startedAt(mockStartedAt) + .updatedAt(mockUpdatedAt) + .transferReceivedAt(mockTransferReceivedAt) + .completedAt(mockCompletedAt) + .stellarTransactionId("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .externalTransactionId("external-tx-id") + .refunded(true) + .refunds(mockRefunds) + .requiredInfoMessage("Please don't forget to foo bar") + .requiredInfoUpdates(mockMissingFields) + .quoteId("quote-id") + .clientDomain("test.com") + .senderId("6c1770b0-0ea4-11ed-861d-0242ac120002") + .receiverId("31212353-f265-4dba-9eb4-0bbeda3ba7f2") + .creator(StellarId("141ee445-f32c-4c38-9d25-f4475d6c5558", null)) + .build() + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun test_toPlatformApiGetTransactionResponse() { + val wantRefunds: Refund = + Refund.builder() + .amountRefunded(Amount("90.0000", fiatUSD)) + .amountFee(Amount("8.0000", fiatUSD)) + .payments( + arrayOf( + RefundPayment.builder() + .id("1111") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50.0000", fiatUSD)) + .fee(Amount("4.0000", fiatUSD)) + .requestedAt(null) + .refundedAt(null) + .build(), + RefundPayment.builder() + .id("2222") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("40.0000", fiatUSD)) + .fee(Amount("4.0000", fiatUSD)) + .requestedAt(null) + .refundedAt(null) + .build() + ) + ) + .build() + + val wantStellarTransaction = GetTransactionResponse.StellarTransaction() + wantStellarTransaction.id = "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300" + + val wantGetTransactionResponse: GetTransactionResponse = + GetTransactionResponse.builder() + .id(txId) + .sep(31) + .kind("receive") + .status(TransactionEvent.Status.PENDING_RECEIVER.status) + .amountExpected(Amount("100", fiatUSD)) + .amountIn(Amount("100.0000", fiatUSD)) + .amountOut(Amount("98.0000000", stellarUSDC)) + .amountFee(Amount("2.0000", fiatUSD)) + .quoteId("quote-id") + .startedAt(mockStartedAt) + .updatedAt(mockUpdatedAt) + .completedAt(mockCompletedAt) + .transferReceivedAt(mockTransferReceivedAt) + .message("Please don't forget to foo bar") + .refunds(wantRefunds) + .stellarTransactions(listOf(wantStellarTransaction)) + .externalTransactionId("external-tx-id") + .customers( + Customers( + StellarId("6c1770b0-0ea4-11ed-861d-0242ac120002", null), + StellarId("31212353-f265-4dba-9eb4-0bbeda3ba7f2", null) + ) + ) + .creator(StellarId("141ee445-f32c-4c38-9d25-f4475d6c5558", null)) + .build() + + val gotGetTransactionResponse = sep31Transaction.toPlatformApiGetTransactionResponse() + Assertions.assertEquals(wantGetTransactionResponse, gotGetTransactionResponse) + } + + @Test + fun test_toSep31GetTransactionResponse() { + val refunds = + Sep31GetTransactionResponse.Refunds.builder() + .amountRefunded("90.0000") + .amountFee("8.0000") + .payments( + listOf( + Sep31RefundPayment.builder().id("1111").amount("50.0000").fee("4.0000").build(), + Sep31RefundPayment.builder().id("2222").amount("40.0000").fee("4.0000").build() + ) + ) + .build() + + val requiredInfoUpdates = AssetInfo.Sep31TxnFieldSpecs() + requiredInfoUpdates.transaction = + mapOf( + "receiver_account_number" to + AssetInfo.Sep31TxnFieldSpec("bank account number of the destination", null, false) + ) + + val wantSep31GetTransactionResponse = + Sep31GetTransactionResponse.builder() + .transaction( + Sep31GetTransactionResponse.TransactionResponse.builder() + .id(txId) + .status(TransactionEvent.Status.PENDING_RECEIVER.status) + .statusEta(120) + .amountIn("100.0000") + .amountInAsset(fiatUSD) + .amountOut("98.0000000") + .amountOutAsset(stellarUSDC) + .amountFee("2.0000") + .amountFeeAsset(fiatUSD) + .stellarAccountId(TEST_ACCOUNT) + .stellarMemo(TEST_MEMO) + .stellarMemoType("text") + .startedAt(mockStartedAt) + .completedAt(mockCompletedAt) + .stellarTransactionId( + "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300" + ) + .externalTransactionId("external-tx-id") + .refunded(true) + .refunds(refunds) + .requiredInfoMessage("Please don't forget to foo bar") + .requiredInfoUpdates(requiredInfoUpdates) + .build() + ) + .build() + + val gotSep31GetTransactionResponse = sep31Transaction.toSep31GetTransactionResponse() + assertEquals(wantSep31GetTransactionResponse, gotSep31GetTransactionResponse) + } +} diff --git a/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt new file mode 100644 index 0000000000..bc46a6b545 --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt @@ -0,0 +1,36 @@ +package org.stellar.anchor.util + +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource + +class MathHelperTest { + @ParameterizedTest + @CsvSource( + value = [",", "0,0", "0.1,0.1000000", "1,1", "1,1.000000", "-1,-1", "-1,-1.0000000", "500,5e2"] + ) + fun test_equalsAsDecimals_true(valueA: String?, valueB: String?) { + assertTrue(MathHelper.equalsAsDecimals(valueA, valueB)) + } + + @ParameterizedTest + @CsvSource(value = ["0,-0.0000001", "-0.1,0.1000000", "0,1.000000", "0,", ",0"]) + fun test_equalsAsDecimals_false(valueA: String?, valueB: String?) { + assertFalse(MathHelper.equalsAsDecimals(valueA, valueB)) + } + + @ParameterizedTest + @CsvSource(value = ["a,a", "1,a", "a,1"]) + fun test_equalsAsDecimals_throws(valueA: String, valueB: String) { + val ex: Exception = assertThrows { MathHelper.equalsAsDecimals(valueA, valueB) } + assertInstanceOf(NumberFormatException::class.java, ex) + assertEquals( + "Character a is neither a decimal digit number, decimal point, nor \"e\" notation exponential mark.", + ex.message + ) + } +} diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index 15f4c86977..966a83a0af 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -112,7 +112,8 @@ sequenceDiagram Platform-->>Platform: verifies challenge Platform-->>-Client: authentication token - loop Sending and Receiving Customer + rect rgb(243,249,255) + loop Sending and Receiving Customer Client->>+Platform: GET [SEP-12]/customer?type= Platform->>+Anchor: forwards request Anchor-->>-Platform: fields required @@ -125,29 +126,34 @@ sequenceDiagram Anchor-->>-Platform: id, ACCEPTED Platform-->>Platform: updates customer status (ACCEPTED) Platform-->>-Client: forwards response + end end - opt Get a Quote if Quotes are Supported or Required - Client->>+Platform: GET [SEP-38]/price - Platform->>+Anchor: GET /rate?type=indicative_price - Anchor-->>-Platform: exchange rate - Platform-->>-Client: exchange rate - Client-->>Client: confirms rate - Client->>+Platform: POST [SEP-38]/quote - Platform->>+Anchor: GET /rate?type=firm - Anchor-->>-Platform: exchange rate, expiration - Platform-->>Client: quote id, rate, expiration - Platform->>+Anchor: POST [webhookURL]/quote created - Anchor-->>-Platform: 204 No Content + rect rgb(249,255,243) + opt Get a Quote if Quotes are Supported or Required + Client->>+Platform: GET [SEP-38]/price + Platform->>+Anchor: GET /rate?type=indicative_price + Anchor-->>-Platform: exchange rate + Platform-->>-Client: exchange rate + Client-->>Client: confirms rate + Client->>+Platform: POST [SEP-38]/quote + Platform->>+Anchor: GET /rate?type=firm + Anchor-->>-Platform: exchange rate, expiration + Platform-->>Client: quote id, rate, expiration + Platform->>+Anchor: POST [webhookURL]/quote created + Anchor-->>-Platform: 204 No Content + end end Client->>+Platform: POST [SEP-31]/transactions Platform-->>Platform: checks customer statuses, links quote - opt Get the Fee if Quotes Were Not Used - Platform->>+Anchor: GET /fee - Anchor-->>Anchor: calculates fee - Anchor-->>-Platform: fee + rect rgb(249,255,243) + opt Get the Fee if Quotes Were Not Used + Platform->>+Anchor: GET /fee + Anchor-->>Anchor: calculates fee + Anchor-->>-Platform: fee + end end Platform-->>Platform: Sets fee on transaction @@ -164,30 +170,39 @@ sequenceDiagram Anchor-->>-Platform: 204 No Content Anchor->>Recipient: Sends off-chain payment to recipient - opt Mismatched info when delivering value to the Recipient (or a similar error) - Recipient-->>Anchor: error: Recipient info was wrong - Anchor-->>Anchor: Updates the receiver customer info from ACCEPTED to NEEDS_INFO - Anchor->>Platform: PATCH /transactions to mark the status as `pending_customer_info_update` - Platform-->>Platform: updates transaction to `pending_customer_info_update` - Client->>+Platform: GET /transactions?id= - Platform-->>-Client: transaction `pending_customer_info_update` - - Client->>+Platform: PUT [SEP-12]/customer?type= - Platform->>+Anchor: forwards request - Anchor-->>Anchor: validates KYC values - Anchor-->>-Platform: id, ACCEPTED - Platform-->>-Client: forwards response - - Platform-->>Platform: updates transaction status to `pending_receiver` - Platform->>+Anchor: POST [webhookURL]/transactions with the status change - Anchor-->>Anchor: queues off-chain payment - Anchor-->>-Platform: 204 No Content - Anchor->>Recipient: Sends off-chain payment to recipient + rect rgb(255,243,249) + opt Mismatched info when delivering value to the Recipient (or a similar error) + Recipient-->>Anchor: error: Recipient info was wrong + Anchor-->>Anchor: Updates the receiver customer info from ACCEPTED to NEEDS_INFO + Anchor->>+Platform: PATCH /transactions to mark the status as `pending_customer_info_update` + Platform-->>Platform: updates transaction to `pending_customer_info_update` + Platform-->>-Anchor: updated transaction + Client->>+Platform: GET /transactions?id= + Platform-->>-Client: transaction `pending_customer_info_update` + + Client->>+Platform: GET [SEP-12]/customer?id=&type= + Platform->>+Anchor: forwards request + Anchor-->>Anchor: validates KYC values + Anchor-->>-Platform: id, NEEDS_INFO, fields + Platform-->>-Client: forwards response + + Client->>+Platform: PUT [SEP-12]/customer?id=&type= + Platform->>+Anchor: forwards request + Anchor-->>Anchor: validates KYC values + Anchor-->>-Platform: id, ACCEPTED + Platform-->>-Client: forwards response + + Anchor->>+Platform: PATCH /transactions to mark the status as `pending_receiver` + Platform-->>Platform: updates transaction to `pending_receiver` + Platform-->>-Anchor: updated transaction + Anchor-->>Anchor: queues off-chain payment + Anchor->>Recipient: Sends off-chain payment to recipient + end end Recipient-->>Anchor: successfully delivered funds to Recipient - Anchor->>+Platform: PATCH /transactions - Platform-->>Platform: updates transaction to complete + Anchor->>+Platform: PATCH /transactions to mark the status as `completed` + Platform-->>Platform: updates transaction to `completed` Platform-->>-Anchor: updated transaction Client->>+Platform: GET /transactions?id= Platform-->>-Client: transaction complete diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep12Client.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep12Client.kt index 4225bee913..b8c9dbc199 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep12Client.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep12Client.kt @@ -20,8 +20,11 @@ const val MULTIPART_FORM_DATA_CHARSET_UTF_8 = "multipart/form-data; charset=utf- val TYPE_MULTIPART_FORM_DATA = MULTIPART_FORM_DATA_CHARSET_UTF_8.toMediaType() class Sep12Client(private val endpoint: String, private val jwt: String) : SepClient() { - fun getCustomer(id: String): Sep12GetCustomerResponse? { - val url = String.format(this.endpoint + "/customer?id=%s", id) + fun getCustomer(id: String, type: String? = null): Sep12GetCustomerResponse? { + var url = String.format(this.endpoint + "/customer?id=%s", id) + if (type != null) { + url += "&type=$type" + } val responseBody = httpGet(url, jwt) return gson.fromJson(responseBody, Sep12GetCustomerResponse::class.java) } @@ -82,4 +85,15 @@ class Sep12Client(private val endpoint: String, private val jwt: String) : SepCl } return response.code } + + /** + * ATTENTION: this function is used for testing purposes only. + * + *

This endpoint is used to delete a customer's `clabe_number`, which would make its state + * change to NEEDS_INFO if it's a receiving customer. + */ + fun invalidateCustomerClabe(id: String) { + val url = String.format("http://localhost:8081/invalidate_clabe/%s", id) + httpGet(url, jwt) + } } diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep31Client.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep31Client.kt index 0a427f4416..a129ae0e85 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep31Client.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep31Client.kt @@ -1,6 +1,7 @@ package org.stellar.anchor.platform import com.google.gson.reflect.TypeToken +import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse import org.stellar.anchor.api.sep.sep31.Sep31InfoResponse import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionResponse @@ -12,6 +13,15 @@ class Sep31Client(private val endpoint: String, private val jwt: String) : SepCl return gson.fromJson(responseBody, Sep31InfoResponse::class.java) } + fun getTransaction(txId: String): Sep31GetTransactionResponse { + // build URL + val url = "$endpoint/transactions/$txId" + println("GET $url") + + val responseBody = httpGet(url, jwt) + return gson.fromJson(responseBody, Sep31GetTransactionResponse::class.java) + } + fun postTransaction(txnRequest: Sep31PostTransactionRequest): Sep31PostTransactionResponse { val url = "$endpoint/transactions" println("POST $url") diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt index 45ce9a4506..9ac8016554 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt @@ -139,6 +139,12 @@ class AnchorPlatformIntegrationTest { platformTestAll(toml, jwt) } + @Test + @Order(8) + fun runSep31UnhappyPath() { + testSep31UnhappyPath() + } + @Test fun testCustomerIntegration() { assertThrows { diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt index 07fce868f2..678836753b 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assertions.* import org.stellar.anchor.api.platform.PatchTransactionRequest import org.stellar.anchor.api.platform.PatchTransactionsRequest import org.stellar.anchor.api.sep.sep12.Sep12PutCustomerRequest +import org.stellar.anchor.api.sep.sep12.Sep12Status import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.auth.AuthHelper @@ -47,13 +48,14 @@ fun testHappyPath() { "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", ) - // Post Sep31 transaction. + // POST Sep31 transaction val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) txnRequest.senderId = senderCustomer!!.id txnRequest.receiverId = receiverCustomer!!.id txnRequest.quoteId = quote.id val postTxResponse = sep31Client.postTransaction(txnRequest) + // GET platformAPI transaction val getTxResponse = platformApiClient.getTransaction(postTxResponse.id) assertEquals(postTxResponse.id, getTxResponse.id) assertEquals(TransactionEvent.Status.PENDING_SENDER.status, getTxResponse.status) @@ -61,6 +63,7 @@ fun testHappyPath() { assertTrue(getTxResponse.amountIn.asset.contains(txnRequest.assetCode)) assertEquals(31, getTxResponse.sep) + // PATCH transaction status to COMPLETED through platformAPI val patchTxRequest = PatchTransactionRequest.builder() .id(getTxResponse.id) @@ -92,3 +95,113 @@ fun testHealth() { assertEquals(streams[0]["thread_terminated"], false) assertEquals(streams[0]["stopped"], false) } + +fun testSep31UnhappyPath() { + // Create sender customer + val senderCustomerRequest = + GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + val senderCustomer = sep12Client.putCustomer(senderCustomerRequest, TYPE_MULTIPART_FORM_DATA) + + // Create receiver customer + val receiverCustomerRequest = + GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) + val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) + val quote = + sep38.postQuote( + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "10", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + ) + + // POST SEP-31 transaction + val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) + txnRequest.senderId = senderCustomer!!.id + txnRequest.receiverId = receiverCustomer!!.id + txnRequest.quoteId = quote.id + val postTxResponse = sep31Client.postTransaction(txnRequest) + + // GET platformAPI transaction + val getTxResponse = platformApiClient.getTransaction(postTxResponse.id) + assertEquals(postTxResponse.id, getTxResponse.id) + assertEquals(TransactionEvent.Status.PENDING_SENDER.status, getTxResponse.status) + assertEquals(txnRequest.amount, getTxResponse.amountIn.amount) + assertTrue(getTxResponse.amountIn.asset.contains(txnRequest.assetCode)) + assertEquals(31, getTxResponse.sep) + assertNull(getTxResponse.completedAt) + assertTrue(getTxResponse.updatedAt > getTxResponse.startedAt) + + // Modify the customer by erasing its clabe_number to simulate an invalid clabe_number + sep12Client.invalidateCustomerClabe(receiverCustomer.id) + var updatedReceiverCustomer = sep12Client.getCustomer(receiverCustomer.id, "sep31-receiver") + assertEquals(Sep12Status.NEEDS_INFO, updatedReceiverCustomer?.status) + assertNotNull(updatedReceiverCustomer?.fields?.get("clabe_number")) + assertNull(updatedReceiverCustomer?.providedFields?.get("clabe_number")) + + // PATCH {platformAPI}/transaction status to PENDING_CUSTOMER_INFO_UPDATE, since the clabe_number + // was invalidated. + var patchTxRequest = + PatchTransactionRequest.builder() + .id(getTxResponse.id) + .status(TransactionEvent.Status.PENDING_CUSTOMER_INFO_UPDATE.status) + .message("The receiving customer clabe_number is invalid!") + .build() + var patchTxResponse = + platformApiClient.patchTransaction( + PatchTransactionsRequest.builder().records(listOf(patchTxRequest)).build() + ) + assertEquals(1, patchTxResponse.records.size) + var patchedTx = patchTxResponse.records[0] + assertEquals(getTxResponse.id, patchedTx.id) + assertEquals(TransactionEvent.Status.PENDING_CUSTOMER_INFO_UPDATE.status, patchedTx.status) + assertEquals(31, patchedTx.sep) + assertEquals("The receiving customer clabe_number is invalid!", patchedTx.message) + + // GET SEP-31 transaction should return PENDING_CUSTOMER_INFO_UPDATE with a message + var gotSep31TxResponse = sep31Client.getTransaction(postTxResponse.id) + assertEquals(postTxResponse.id, gotSep31TxResponse.transaction.id) + assertEquals( + TransactionEvent.Status.PENDING_CUSTOMER_INFO_UPDATE.status, + gotSep31TxResponse.transaction.status + ) + assertEquals( + "The receiving customer clabe_number is invalid!", + gotSep31TxResponse.transaction.requiredInfoMessage + ) + assertNull(gotSep31TxResponse.transaction.completedAt) + + // PUT sep12/customer with the correct clabe_number + sep12Client.putCustomer( + Sep12PutCustomerRequest.builder().id(receiverCustomer.id).clabeNumber("5678").build() + ) + updatedReceiverCustomer = sep12Client.getCustomer(receiverCustomer.id, "sep31-receiver") + assertEquals(Sep12Status.ACCEPTED, updatedReceiverCustomer?.status) + assertNull(updatedReceiverCustomer?.fields?.get("clabe_number")) + assertNotNull(updatedReceiverCustomer?.providedFields?.get("clabe_number")) + + // PATCH {platformAPI}/transaction status to COMPLETED, since the clabe_number was updated + // correctly. + patchTxRequest = + PatchTransactionRequest.builder() + .id(getTxResponse.id) + .status(TransactionEvent.Status.COMPLETED.status) + .build() + patchTxResponse = + platformApiClient.patchTransaction( + PatchTransactionsRequest.builder().records(listOf(patchTxRequest)).build() + ) + assertEquals(1, patchTxResponse.records.size) + patchedTx = patchTxResponse.records[0] + assertEquals(getTxResponse.id, patchedTx.id) + assertEquals(TransactionEvent.Status.COMPLETED.status, patchedTx.status) + assertEquals(31, patchedTx.sep) + assertNull(patchedTx.message) + assertTrue(patchedTx.startedAt < patchedTx.updatedAt) + assertEquals(patchedTx.updatedAt, patchedTx.completedAt) + + // GET SEP-31 transaction should return COMPLETED with no message + gotSep31TxResponse = sep31Client.getTransaction(postTxResponse.id) + assertEquals(postTxResponse.id, gotSep31TxResponse.transaction.id) + assertEquals(TransactionEvent.Status.COMPLETED.status, gotSep31TxResponse.transaction.status) + assertNull(gotSep31TxResponse.transaction.requiredInfoMessage) + assertEquals(patchedTx.completedAt, gotSep31TxResponse.transaction.completedAt) +} diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep31Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep31Tests.kt index 703626a303..77cdd997f9 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep31Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep31Tests.kt @@ -1,10 +1,12 @@ package org.stellar.anchor.platform +import kotlin.test.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.sep.sep12.Sep12PutCustomerRequest import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest +import org.stellar.anchor.event.models.TransactionEvent import org.stellar.anchor.util.GsonUtils import org.stellar.anchor.util.Sep1Helper @@ -33,7 +35,7 @@ fun sep31TestAll(toml: Sep1Helper.TomlContent, jwt: String) { sep38 = Sep38Client(toml.getString("ANCHOR_QUOTE_SERVER"), jwt) testSep31TestInfo() - testSep31PostTransaction() + testSep31PostAndGetTransaction() testBadAsset() } @@ -44,7 +46,7 @@ fun testSep31TestInfo() { assertTrue(info.receive.isNotEmpty()) } -fun testSep31PostTransaction() { +fun testSep31PostAndGetTransaction() { // Create sender customer val senderCustomerRequest = GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) @@ -63,12 +65,24 @@ fun testSep31PostTransaction() { "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", ) - // Post Sep31 transaction. + // POST Sep31 transaction val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) txnRequest.senderId = senderCustomer!!.id txnRequest.receiverId = receiverCustomer!!.id txnRequest.quoteId = quote.id - sep31Client.postTransaction(txnRequest) + val postTxResponse = sep31Client.postTransaction(txnRequest) + assertEquals( + "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + postTxResponse.stellarAccountId + ) + + // GET Sep31 transaction + val getTxResponse = sep31Client.getTransaction(postTxResponse.id) + assertEquals(postTxResponse.id, getTxResponse.transaction.id) + assertEquals(postTxResponse.stellarAccountId, getTxResponse.transaction.stellarAccountId) + assertEquals(postTxResponse.stellarMemo, getTxResponse.transaction.stellarMemo) + assertEquals(postTxResponse.stellarMemoType, getTxResponse.transaction.stellarMemoType) + assertEquals(TransactionEvent.Status.PENDING_SENDER.status, getTxResponse.transaction.status) } fun testBadAsset() { diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java index 34ce94dc48..6043d2a17c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java @@ -140,7 +140,6 @@ public void setRefundsJson(String refundsJson) { Instant updatedAt; Instant transferReceivedAt; - String message; String amountExpected; @OneToMany(fetch = FetchType.EAGER, mappedBy = "sep31Transaction") diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java index 32c0accb7e..897cd9b4cb 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java @@ -2,10 +2,11 @@ import static org.stellar.anchor.api.sep.SepTransactionStatus.*; import static org.stellar.anchor.sep31.Sep31Helper.allAmountAvailable; -import static org.stellar.anchor.sep31.Sep31Helper.validateStatus; import static org.stellar.anchor.util.MathHelper.decimal; +import static org.stellar.anchor.util.MathHelper.equalsAsDecimals; import io.micrometer.core.instrument.Metrics; +import java.time.Instant; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -20,16 +21,15 @@ import org.stellar.anchor.api.platform.PatchTransactionsResponse; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.Amount; -import org.stellar.anchor.api.shared.Refund; -import org.stellar.anchor.api.shared.RefundPayment; import org.stellar.anchor.asset.AssetService; -import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.sep31.Refunds; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep38.Sep38Quote; import org.stellar.anchor.sep38.Sep38QuoteStore; import org.stellar.anchor.util.Log; +import org.stellar.anchor.util.SepHelper; +import org.stellar.anchor.util.StringHelper; @Service public class TransactionService { @@ -46,6 +46,11 @@ public class TransactionService { EXPIRED.getName(), ERROR.getName()); + static boolean isStatusError(String status) { + return List.of(PENDING_CUSTOMER_INFO_UPDATE.getName(), EXPIRED.getName(), ERROR.getName()) + .contains(status); + } + TransactionService( Sep38QuoteStore quoteStore, Sep31TransactionStore txnStore, AssetService assetService) { this.quoteStore = quoteStore; @@ -64,7 +69,7 @@ public GetTransactionResponse getTransaction(String txnId) throws AnchorExceptio throw new NotFoundException(String.format("transaction (id=%s) is not found", txnId)); } - return fromTransactionToResponse(txn); + return txn.toPlatformApiGetTransactionResponse(); } public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest request) @@ -92,7 +97,7 @@ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest requ if (!txnOriginalStatus.equals(txn.getStatus())) { statusUpdatedTxns.add(txn); } - responses.add(fromTransactionToResponse(txn)); + responses.add(txn.toPlatformApiGetTransactionResponse()); } else { throw new BadRequestException(String.format("transaction(id=%s) not found", patch.getId())); } @@ -108,128 +113,151 @@ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest requ return new PatchTransactionsResponse(responses); } - GetTransactionResponse fromTransactionToResponse(Sep31Transaction txn) { - Refunds txnRefunds = txn.getRefunds(); - Refund refunds = null; - if (txnRefunds != null) { - String amountInAsset = txn.getAmountInAsset(); - RefundPayment[] payments = null; - Refund.RefundBuilder refundsBuilder = - Refund.builder() - .amountRefunded(new Amount(txnRefunds.getAmountRefunded(), amountInAsset)) - .amountFee(new Amount(txnRefunds.getAmountFee(), amountInAsset)); - - // populate refunds payments - for (int i = 0; i < txnRefunds.getRefundPayments().size(); i++) { - org.stellar.anchor.sep31.RefundPayment refundPayment = - txnRefunds.getRefundPayments().get(i); - RefundPayment platformRefundPayment = - RefundPayment.builder() - .id(refundPayment.getId()) - .idType(RefundPayment.IdType.STELLAR) - .amount(new Amount(refundPayment.getAmount(), amountInAsset)) - .fee(new Amount(refundPayment.getFee(), amountInAsset)) - .requestedAt(null) - .refundedAt(null) - .build(); - - if (payments == null) { - payments = new RefundPayment[txnRefunds.getRefundPayments().size()]; - } - payments[i] = platformRefundPayment; - } - - refunds = refundsBuilder.payments(payments).build(); - } - - List stellarTransactions = null; - if (!Objects.toString(txn.getStellarTransactionId(), "").isEmpty()) { - GetTransactionResponse.StellarTransaction stellarTxn = - new GetTransactionResponse.StellarTransaction(); - stellarTxn.setId(txn.getStellarTransactionId()); - stellarTransactions = List.of(stellarTxn); - } - - return GetTransactionResponse.builder() - .id(txn.getId()) - .sep(31) - .kind(TransactionEvent.Kind.RECEIVE.getKind()) - .status(txn.getStatus()) - .amountExpected(new Amount(txn.getAmountExpected(), txn.getAmountInAsset())) - .amountIn(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) - .amountOut(new Amount(txn.getAmountOut(), txn.getAmountOutAsset())) - .amountFee(new Amount(txn.getAmountFee(), txn.getAmountFeeAsset())) - .quoteId(txn.getQuoteId()) - .startedAt(txn.getStartedAt()) - .updatedAt(txn.getUpdatedAt()) - .completedAt(txn.getCompletedAt()) - .transferReceivedAt(txn.getTransferReceivedAt()) - .message(txn.getRequiredInfoMessage()) // Assuming these meant to be the same. - .refunds(refunds) - .stellarTransactions(stellarTransactions) - .externalTransactionId(txn.getExternalTransactionId()) - // TODO .custodialTransactionId(txn.get) - .customers(txn.getCustomers()) - .creator(txn.getCreator()) - .build(); - } - + /** + * updateSep31Transaction will inject the new values from the PatchTransactionRequest into the + * database Sep31Transaction and validate them to make sure they are compliant. + * + * @param ptr is the PatchTransactionRequest containing the updated values. + * @param txn is the Sep31Transaction stored in the database that needs to be updated. + * @throws AnchorException if any of the new values is invalid or non-compliant. + */ void updateSep31Transaction(PatchTransactionRequest ptr, Sep31Transaction txn) throws AnchorException { - if (ptr.getStatus() != null) { - validatePlatformApiStatus(ptr.getStatus()); + boolean txWasUpdated = false; + boolean txWasCompleted = false; + boolean shouldClearMessageStatus = + !StringHelper.isEmpty(ptr.getStatus()) + && !isStatusError(ptr.getStatus()) + && !StringHelper.isEmpty(txn.getStatus()) + && isStatusError(txn.getStatus()); + + if (ptr.getStatus() != null && !Objects.equals(txn.getStatus(), ptr.getStatus())) { + validateIfStatusIsSupported(ptr.getStatus()); + txWasCompleted = + !Objects.equals(txn.getStatus(), COMPLETED.getName()) + && Objects.equals(ptr.getStatus(), COMPLETED.getName()); txn.setStatus(ptr.getStatus()); + txWasUpdated = true; } - if (ptr.getAmountIn() != null) { - validateAsset(ptr.getAmountIn()); + + if (ptr.getAmountIn() != null + && (!Objects.equals(txn.getAmountIn(), ptr.getAmountIn().getAmount()) + || !Objects.equals(txn.getAmountInAsset(), ptr.getAmountIn().getAsset()))) { + validateAsset("amount_in", ptr.getAmountIn()); txn.setAmountIn(ptr.getAmountIn().getAmount()); txn.setAmountInAsset(ptr.getAmountIn().getAsset()); + txWasUpdated = true; } - if (ptr.getAmountOut() != null) { - validateAsset(ptr.getAmountOut()); + + if (ptr.getAmountOut() != null + && (!Objects.equals(txn.getAmountOut(), ptr.getAmountOut().getAmount()) + || !Objects.equals(txn.getAmountOutAsset(), ptr.getAmountOut().getAsset()))) { + validateAsset("amount_out", ptr.getAmountOut()); txn.setAmountOut(ptr.getAmountOut().getAmount()); txn.setAmountOutAsset(ptr.getAmountOut().getAsset()); + txWasUpdated = true; } - if (ptr.getAmountFee() != null) { - validateAsset(ptr.getAmountFee()); + + if (ptr.getAmountFee() != null + && (!Objects.equals(txn.getAmountFee(), ptr.getAmountFee().getAmount()) + || !Objects.equals(txn.getAmountFeeAsset(), ptr.getAmountFee().getAsset()))) { + validateAsset("amount_fee", ptr.getAmountFee()); txn.setAmountFee(ptr.getAmountFee().getAmount()); txn.setAmountFeeAsset(ptr.getAmountFee().getAsset()); + txWasUpdated = true; } - if (ptr.getTransferReceivedAt() != null) { + + if (ptr.getTransferReceivedAt() != null + && ptr.getTransferReceivedAt().compareTo(txn.getStartedAt()) != 0) { + if (ptr.getTransferReceivedAt().compareTo(txn.getStartedAt()) < 0) { + throw new BadRequestException( + String.format( + "the `transfer_received_at(%s)` cannot be earlier than 'started_at(%s)'", + ptr.getTransferReceivedAt().toString(), txn.getStartedAt().toString())); + } txn.setTransferReceivedAt(ptr.getTransferReceivedAt()); + txWasUpdated = true; } + if (ptr.getMessage() != null) { - txn.setRequiredInfoMessage(ptr.getMessage()); + if (!Objects.equals(txn.getRequiredInfoMessage(), ptr.getMessage())) { + txn.setRequiredInfoMessage(ptr.getMessage()); + txWasUpdated = true; + } + } else if (shouldClearMessageStatus) { + txn.setRequiredInfoMessage(null); + } + + if (ptr.getRefunds() != null) { + Refunds updatedRefunds = Refunds.of(ptr.getRefunds(), txnStore); + // TODO: validate refunds + if (!Objects.equals(txn.getRefunds(), updatedRefunds)) { + txn.setRefunds(updatedRefunds); + txWasUpdated = true; + } } - if (ptr.getExternalTransactionId() != null) { + + if (ptr.getExternalTransactionId() != null + && !Objects.equals(txn.getExternalTransactionId(), ptr.getExternalTransactionId())) { txn.setExternalTransactionId(ptr.getExternalTransactionId()); + txWasUpdated = true; } - // TODO: Update [refunds] field - validateStatus(txn); validateQuoteAndAmounts(txn); - validateTimestamps(txn); + + Instant now = Instant.now(); + if (txWasUpdated) { + txn.setUpdatedAt(now); + } + if (txWasCompleted) { + txn.setCompletedAt(now); + } } - private void validatePlatformApiStatus(String status) throws BadRequestException { + /** + * validateIfStatusIsSupported will check if the provided string is a SepTransactionStatus + * supported by the PlatformAPI + * + * @param status a String representing the SepTransactionStatus + * @throws BadRequestException if the provided status is not supported + */ + void validateIfStatusIsSupported(String status) throws BadRequestException { if (!validStatuses.contains(status)) { throw new BadRequestException(String.format("invalid status(%s)", status)); } } - void validateAsset(Amount amount) throws BadRequestException { - if (amount != null) { - if (assets.stream() - .noneMatch(assetInfo -> assetInfo.getAssetName().equals(amount.getAsset()))) { - throw new BadRequestException( - String.format("'%s' is not a supported asset.", amount.getAsset())); - } + /** + * validateAsset will validate if the provided amount has valid values and if its asset is + * supported. + * + * @param amount is the object containing the asset full name and the amount. + * @throws BadRequestException if the provided asset is not supported + */ + void validateAsset(String fieldName, Amount amount) throws BadRequestException { + if (amount == null) { + return; + } + + // asset amount needs to be non-empty and valid + SepHelper.validateAmount(fieldName + ".", amount.getAmount()); + + // asset name cannot be empty + if (StringHelper.isEmpty(amount.getAsset())) { + throw new BadRequestException(fieldName + ".asset cannot be empty"); + } + + // asset name needs to be supported + if (assets.stream() + .noneMatch(assetInfo -> assetInfo.getAssetName().equals(amount.getAsset()))) { + throw new BadRequestException( + String.format("'%s' is not a supported asset.", amount.getAsset())); } } void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { // amount_in = amount_out + amount_fee - if (txn.getQuoteId() == null) { + if (StringHelper.isEmpty(txn.getQuoteId())) { // without exchange if (allAmountAvailable(txn)) if (decimal(txn.getAmountIn()) @@ -244,44 +272,30 @@ void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { "invalid quote_id(id=%s) found in transaction(id=%s)", txn.getQuoteId(), txn.getId())); } - // TODO: Commenting out for now to get SEP38 working, Jamie will update SEP31 fee handling - // logic - // if (!decimal(quote.getSellAmount()).equals(decimal(txn.getAmountIn()))) { - // throw new BadRequestException("quote.sell_amount != amount_in"); - // } - - if (txn.getAmountFeeAsset().equals(quote.getBuyAsset())) { - // fee calculated in buying asset - // buy_asset = amount_out + amount_fee - if (decimal(quote.getBuyAmount()) - .compareTo(decimal(txn.getAmountOut()).add(decimal(txn.getAmountFee()))) - != 0) { - throw new BadRequestException("quote.buy_amount != amount_fee + amount_out"); - } else if (txn.getAmountFeeAsset().equals(quote.getSellAsset())) { - // fee calculated in selling asset - // sell_asset = amount_in + amount_fee - if (decimal(quote.getSellAmount()) - .compareTo(decimal(txn.getAmountIn()).add(decimal(txn.getAmountFee()))) - != 0) { - throw new BadRequestException("quote.sell_amount != amount_fee + amount_in"); - } - } else { - throw new BadRequestException( - String.format( - "amount_in_asset(%s) must equal to one of sell_asset(%s) and buy_asset(%s", - txn.getAmountInAsset(), quote.getSellAsset(), quote.getBuyAsset())); - } + + if (!Objects.equals(txn.getAmountInAsset(), quote.getSellAsset())) { + throw new BadRequestException("transaction.amount_in_asset != quote.sell_asset"); } - } - } - void validateTimestamps(Sep31Transaction txn) throws BadRequestException { - if (txn.getTransferReceivedAt() != null - && txn.getTransferReceivedAt().compareTo(txn.getStartedAt()) < 0) { - throw new BadRequestException( - String.format( - "the `transfer_receved_at(%s)` cannot be earlier than 'started_at(%s)'", - txn.getTransferReceivedAt().toString(), txn.getStartedAt().toString())); + if (!equalsAsDecimals(txn.getAmountIn(), quote.getSellAmount())) { + throw new BadRequestException("transaction.amount_in != quote.sell_amount"); + } + + if (!Objects.equals(txn.getAmountOutAsset(), quote.getBuyAsset())) { + throw new BadRequestException("transaction.amount_out_asset != quote.buy_asset"); + } + + if (!equalsAsDecimals(txn.getAmountOut(), quote.getBuyAmount())) { + throw new BadRequestException("transaction.amount_out != quote.buy_amount"); + } + + if (!Objects.equals(txn.getAmountFeeAsset(), quote.getFee().getAsset())) { + throw new BadRequestException("transaction.amount_fee_asset != quote.fee.asset"); + } + + if (!equalsAsDecimals(txn.getAmountFee(), quote.getFee().getTotal())) { + throw new BadRequestException("amount_fee != sum(quote.fee.total)"); + } } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt index a7b5ee1cec..27e7f2d82f 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt @@ -1,37 +1,36 @@ package org.stellar.anchor.platform.service -import io.mockk.MockKAnnotations -import io.mockk.clearAllMocks -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.unmockkAll import java.time.Instant -import org.junit.jupiter.api.AfterEach -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.assertThrows +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource import org.stellar.anchor.api.exception.AnchorException import org.stellar.anchor.api.exception.BadRequestException import org.stellar.anchor.api.exception.NotFoundException import org.stellar.anchor.api.platform.GetTransactionResponse +import org.stellar.anchor.api.platform.PatchTransactionRequest import org.stellar.anchor.api.sep.AssetInfo +import org.stellar.anchor.api.sep.SepTransactionStatus import org.stellar.anchor.api.shared.* import org.stellar.anchor.api.shared.RefundPayment import org.stellar.anchor.asset.AssetService +import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.event.models.TransactionEvent import org.stellar.anchor.platform.data.JdbcSep31RefundPayment.JdbcRefundPayment import org.stellar.anchor.platform.data.JdbcSep31Refunds import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.sep31.* +import org.stellar.anchor.sep38.Sep38Quote import org.stellar.anchor.sep38.Sep38QuoteStore class TransactionServiceTest { companion object { private const val fiatUSD = "iso4217:USD" private const val stellarUSDC = - "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" + "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" private const val TEST_ACCOUNT = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" private const val TEST_MEMO = "test memo" } @@ -201,4 +200,206 @@ class TransactionServiceTest { .build() assertEquals(wantGetTransactionResponse, gotGetTransactionResponse) } + + @Test + fun test_validateAsset_failure() { + // fails if amount_in.amount is null + var assetAmount = Amount(null, null) + var ex = + assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.amount cannot be empty", ex.message) + + // fails if amount_in.amount is empty + assetAmount = Amount("", null) + ex = assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.amount cannot be empty", ex.message) + + // fails if amount_in.amount is invalid + assetAmount = Amount("abc", null) + ex = assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.amount is invalid", ex.message) + + // fails if amount_in.amount is negative + assetAmount = Amount("-1", null) + ex = assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.amount should be positive", ex.message) + + // fails if amount_in.amount is zero + assetAmount = Amount("0", null) + ex = assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.amount should be positive", ex.message) + + // fails if amount_in.asset is null + assetAmount = Amount("10", null) + ex = assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.asset cannot be empty", ex.message) + + // fails if amount_in.asset is empty + assetAmount = Amount("10", "") + ex = assertThrows { transactionService.validateAsset("amount_in", assetAmount) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("amount_in.asset cannot be empty", ex.message) + + // fails if listAllAssets is empty + every { assetService.listAllAssets() } returns listOf() + val mockAsset = Amount("10", fiatUSD) + ex = assertThrows { transactionService.validateAsset("amount_in", mockAsset) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("'$fiatUSD' is not a supported asset.", ex.message) + + // fails if listAllAssets does not contain the desired asset + this.assetService = ResourceJsonAssetService("test_assets.json") + ex = assertThrows { transactionService.validateAsset("amount_in", mockAsset) } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("'$fiatUSD' is not a supported asset.", ex.message) + } + + @Test + fun test_validateAsset() { + this.assetService = ResourceJsonAssetService("test_assets.json") + transactionService = TransactionService(sep38QuoteStore, sep31TransactionStore, assetService) + val mockAsset = Amount("10", fiatUSD) + assertDoesNotThrow { transactionService.validateAsset("amount_in", mockAsset) } + } + + @ParameterizedTest + @EnumSource( + value = SepTransactionStatus::class, + mode = EnumSource.Mode.EXCLUDE, + names = + [ + "PENDING_STELLAR", + "PENDING_CUSTOMER_INFO_UPDATE", + "PENDING_RECEIVER", + "PENDING_EXTERNAL", + "COMPLETED", + "EXPIRED", + "ERROR"] + ) + fun test_validateIfStatusIsSupported_failure(sepTxnStatus: SepTransactionStatus) { + val ex: Exception = assertThrows { + transactionService.validateIfStatusIsSupported(sepTxnStatus.getName()) + } + assertInstanceOf(BadRequestException::class.java, ex) + assertEquals("invalid status(${sepTxnStatus.getName()})", ex.message) + } + + @ParameterizedTest + @EnumSource( + value = SepTransactionStatus::class, + mode = EnumSource.Mode.INCLUDE, + names = + [ + "PENDING_STELLAR", + "PENDING_CUSTOMER_INFO_UPDATE", + "PENDING_RECEIVER", + "PENDING_EXTERNAL", + "COMPLETED", + "EXPIRED", + "ERROR"] + ) + fun test_validateIfStatusIsSupported(sepTxnStatus: SepTransactionStatus) { + assertDoesNotThrow { transactionService.validateIfStatusIsSupported(sepTxnStatus.getName()) } + } + + @Test + fun test_updateSep31Transaction() { + val txId = "my-tx-id" + val quoteId = "my-quote-id" + + // mock times + val mockStartedAt = Instant.now().minusSeconds(180) + val mockTransferReceivedAt = mockStartedAt.plusSeconds(60) + + val mockRefunds: Refund = + Refund.builder() + .amountRefunded(Amount("90.0000", fiatUSD)) + .amountFee(Amount("8.0000", fiatUSD)) + .payments( + arrayOf( + RefundPayment.builder() + .id("1111") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("50.0000", fiatUSD)) + .fee(Amount("4.0000", fiatUSD)) + .requestedAt(null) + .refundedAt(null) + .build(), + RefundPayment.builder() + .id("2222") + .idType(RefundPayment.IdType.STELLAR) + .amount(Amount("40.0000", fiatUSD)) + .fee(Amount("4.0000", fiatUSD)) + .requestedAt(null) + .refundedAt(null) + .build() + ) + ) + .build() + + val mockPatchTransactionRequest = + PatchTransactionRequest.builder() + .id(txId) + .status("completed") + .amountIn(Amount("100", fiatUSD)) + .amountOut(Amount("98", stellarUSDC)) + .amountFee(Amount("2", fiatUSD)) + .transferReceivedAt(mockTransferReceivedAt) + .message("Remittance was successfully completed.") + .refunds(mockRefunds) + .externalTransactionId("external-id") + .build() + + val mockSep31Transaction = JdbcSep31Transaction() + mockSep31Transaction.id = txId + mockSep31Transaction.quoteId = quoteId + mockSep31Transaction.startedAt = mockStartedAt + mockSep31Transaction.updatedAt = mockStartedAt + + val mockSep38Quote = mockk(relaxed = true) + every { mockSep38Quote.id } returns quoteId + every { mockSep38Quote.sellAmount } returns "100" + every { mockSep38Quote.sellAsset } returns fiatUSD + every { mockSep38Quote.buyAmount } returns "98" + every { mockSep38Quote.buyAsset } returns stellarUSDC + every { mockSep38Quote.fee.total } returns "2" + every { mockSep38Quote.fee.asset } returns fiatUSD + every { sep38QuoteStore.findByQuoteId(quoteId) } returns mockSep38Quote + + this.assetService = ResourceJsonAssetService("test_assets.json") + transactionService = TransactionService(sep38QuoteStore, sep31TransactionStore, assetService) + + assertEquals(mockSep31Transaction.startedAt, mockSep31Transaction.updatedAt) + assertNull(mockSep31Transaction.completedAt) + assertDoesNotThrow { + transactionService.updateSep31Transaction(mockPatchTransactionRequest, mockSep31Transaction) + } + assertTrue(mockSep31Transaction.updatedAt > mockSep31Transaction.startedAt) + assertTrue(mockSep31Transaction.updatedAt == mockSep31Transaction.completedAt) + + val wantSep31TransactionUpdated = JdbcSep31Transaction() + wantSep31TransactionUpdated.id = txId + wantSep31TransactionUpdated.status = "completed" + wantSep31TransactionUpdated.quoteId = quoteId + wantSep31TransactionUpdated.startedAt = mockStartedAt + wantSep31TransactionUpdated.updatedAt = mockSep31Transaction.updatedAt + wantSep31TransactionUpdated.completedAt = mockSep31Transaction.completedAt + wantSep31TransactionUpdated.amountIn = "100" + wantSep31TransactionUpdated.amountInAsset = fiatUSD + wantSep31TransactionUpdated.amountOut = "98" + wantSep31TransactionUpdated.amountOutAsset = stellarUSDC + wantSep31TransactionUpdated.amountFee = "2" + wantSep31TransactionUpdated.amountFeeAsset = fiatUSD + wantSep31TransactionUpdated.requiredInfoMessage = "Remittance was successfully completed." + wantSep31TransactionUpdated.externalTransactionId = "external-id" + wantSep31TransactionUpdated.transferReceivedAt = mockTransferReceivedAt + wantSep31TransactionUpdated.refunds = Refunds.of(mockRefunds, sep31TransactionStore) + assertEquals(wantSep31TransactionUpdated, mockSep31Transaction) + } } From 31823521144b9e77e0d50a77e923186d73aaed40 Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 12 Aug 2022 14:56:21 -0400 Subject: [PATCH 20/65] Add timeout to end to end test/ fix helm prometheus annotations (#464) Add timeout to end to end test/ fix helm prometheus annotations --- end-to-end-tests/end_to_end_tests.py | 35 ++++++++++++++----- end-to-end-tests/requirements.txt | 1 + .../sep-service/templates/deployment.yaml | 12 +++---- 3 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 end-to-end-tests/requirements.txt diff --git a/end-to-end-tests/end_to_end_tests.py b/end-to-end-tests/end_to_end_tests.py index 07d90bc279..753f4bf3c0 100644 --- a/end-to-end-tests/end_to_end_tests.py +++ b/end-to-end-tests/end_to_end_tests.py @@ -21,6 +21,8 @@ HORIZON_URI = "https://horizon-testnet.stellar.org" FRIENDBOT_URI = "https://friendbot.stellar.org" +TRANSACTION_STATUS_COMPLETE = "completed" + class Endpoints: def __init__(self, domain, use_http=True): @@ -139,14 +141,28 @@ def send_asset(asset, source_secret_key, receiver_public_key, memo_hash): response = server.submit_transaction(transaction) -def poll_transaction_status(endpoints, headers, transaction_id): +def poll_transaction_status(endpoints, headers, transaction_id, status=TRANSACTION_STATUS_COMPLETE, timeout=120, + poll_interval=2): + """ + :param endpoints: Anchor Platform endpoints + :param headers: Request headers + :param transaction_id: Transaction ID to poll for completion + :param status: The desired status to poll for + :param timeout: Time (in seconds) to poll for the desired status + :param poll_interval: Time (in seconds) between each poll attempt + :return: + """ + attempt = 1 print("============= Polling Transaction Status from Anchor Platform ===========") - while True: - status = get_transaction(endpoints, headers, transaction_id)["transaction"]["status"] - print(f"transaction - {transaction_id} status is {status}") - if status == "completed": + while True and attempt*poll_interval <= timeout: + transaction_status = get_transaction(endpoints, headers, transaction_id)["transaction"]["status"] + print(f"attempt #{attempt} - transaction - {transaction_id} status is {transaction_status}") + if transaction_status == status: break - time.sleep(3) + attempt += 1 + time.sleep(poll_interval) + else: + raise Exception("error: timed out while polling transaction status") print("=========================================================================") def test_sep_31_flow(endpoints, keypair, transaction_payload, sep38_payload=None): @@ -182,8 +198,11 @@ def test_sep_31_flow(endpoints, keypair, transaction_payload, sep38_payload=None asset = Asset(transaction_payload["asset_code"], transaction_payload["asset_issuer"]) send_asset(asset, secret_key, transaction["stellar_account_id"], memo_hash) - poll_transaction_status(endpoints, headers, transaction["id"]) - + try: + poll_transaction_status(endpoints, headers, transaction["id"]) + except Exception as e: + print(e) + exit(1) def test_sep38_create_quote(endpoints, keypair, payload): token = get_anchor_platform_token(endpoints, keypair.public_key, keypair.secret) diff --git a/end-to-end-tests/requirements.txt b/end-to-end-tests/requirements.txt new file mode 100644 index 0000000000..e716c45b8d --- /dev/null +++ b/end-to-end-tests/requirements.txt @@ -0,0 +1 @@ +stellar-sdk \ No newline at end of file diff --git a/helm-charts/sep-service/templates/deployment.yaml b/helm-charts/sep-service/templates/deployment.yaml index 60b5f8c752..da0e920161 100644 --- a/helm-charts/sep-service/templates/deployment.yaml +++ b/helm-charts/sep-service/templates/deployment.yaml @@ -2,12 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Values.fullName }} - {{- if (.Values.deployment).annotations }} - annotations: - {{- range $key, $value := .Values.deployment.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} - {{- end }} labels: app.kubernetes.io/name: {{ .Values.fullName }} helm.sh/chart: {{ include "common.chart" . }} @@ -23,6 +17,12 @@ spec: labels: app.kubernetes.io/name: {{ .Values.fullName }} app.kubernetes.io/instance: {{ .Release.Name }} + {{- if (.Values.deployment).annotations }} + annotations: + {{- range $key, $value := .Values.deployment.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} spec: {{- if (.Values.deployment).serviceAccountName }} serviceAccountName: {{ .Values.deployment.serviceAccountName | default "default" }} From 60af84d3ef1df0b697ad8a7376ace15d8bc6c047 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 12 Aug 2022 17:51:39 -0700 Subject: [PATCH 21/65] platform: Dynamic account list handling in StellarPaymentObserver (#454) * add ObservingAccounts * integrate the dynamic account handler to stellar payment observer * moved data model classes to org.stellar.anchor.platform.model package * Add store to PaymentObservingAccountManager * mock store rather than mock the manager * changed from startAt to lastObserved * add scheduled task to evict and persist to database * fixed the Instant.now() mockK verfify count caused by race condition * fixed the eviction test failure caused by another race condition. * respond to pr review * upsert stellar account to payment observing manager when Sep31DepositInfoGeneratorApi receives a new account from the anchor * remove the PaymentObservingAccountsManager.remove function * rename observe to lookupAndUPdate * increase the eviction max idle time to 1 week * add shutdown() that flushes the account list with the @PreDestroy annotation. * fine tune the eviction period and max idle time * change canExpire to RESIDENTIAL and TRANSIENT --- .../exception/ValueValidationException.java | 11 ++ .../platform/RestRateIntegrationTest.kt | 2 +- .../anchor/platform/PaymentConfig.java | 34 +++- .../stellar/anchor/platform/SepConfig.java | 5 +- .../platform/{model => data}/Customer.java | 2 +- .../{model => data}/CustomerStatus.java | 2 +- .../data/PaymentObservingAccount.java | 25 +++ .../data/PaymentObservingAccountRepo.java | 8 + .../platform/data/PaymentStreamerCursor.java | 8 +- .../data/PaymentStreamerCursorRepo.java | 5 +- ...JdbcStellarPaymentStreamerCursorStore.java | 14 +- ...moryStellarPaymentStreamerCursorStore.java | 9 +- .../stellar/PaymentObservingAccountStore.java | 48 +++++ .../PaymentObservingAccountsManager.java | 159 ++++++++++++++++ .../stellar/StellarPaymentObserver.java | 104 +++++----- .../StellarPaymentStreamerCursorStore.java | 4 +- .../service/Sep31DepositInfoGeneratorApi.java | 20 +- .../anchor/platform/PaymentConfigTest.kt | 74 ++++++-- .../PaymentObservingAccountsManagerTest.kt | 178 ++++++++++++++++++ .../Sep31DepositInfoGeneratorApiTest.kt | 31 ++- 20 files changed, 638 insertions(+), 105 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/ValueValidationException.java rename platform/src/main/java/org/stellar/anchor/platform/{model => data}/Customer.java (85%) rename platform/src/main/java/org/stellar/anchor/platform/{model => data}/CustomerStatus.java (80%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccountRepo.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountStore.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManager.java create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManagerTest.kt diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/ValueValidationException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/ValueValidationException.java new file mode 100644 index 0000000000..07d903a52e --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/ValueValidationException.java @@ -0,0 +1,11 @@ +package org.stellar.anchor.api.exception; + +public class ValueValidationException extends AnchorException { + public ValueValidationException(String message, Exception cause) { + super(message, cause); + } + + public ValueValidationException(String message) { + super(message); + } +} diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt index 05c7c85370..23f56412db 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt @@ -557,6 +557,6 @@ class RestRateIntegrationTest { }""".trimMargin(), wantGetRateResponse ) - verify(exactly = 1) { Instant.now() } + verify(atLeast = 1) { Instant.now() } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java b/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java index ae2da586b2..47505943f4 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.stream.Collectors; +import lombok.SneakyThrows; import okhttp3.OkHttpClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,20 +12,24 @@ import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.CirclePaymentObserverConfig; import org.stellar.anchor.horizon.Horizon; +import org.stellar.anchor.platform.data.PaymentObservingAccountRepo; import org.stellar.anchor.platform.payment.observer.PaymentListener; import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService; +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountStore; +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentObserver; import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentStreamerCursorStore; @Configuration public class PaymentConfig { @Bean + @SneakyThrows public StellarPaymentObserver stellarPaymentObserverService( AssetService assetService, List paymentListeners, StellarPaymentStreamerCursorStore stellarPaymentStreamerCursorStore, - AppConfig appConfig) - throws ServerErrorException { + PaymentObservingAccountsManager paymentObservingAccountsManager, + AppConfig appConfig) { // validate assetService if (assetService == null || assetService.listAllAssets() == null) { throw new ServerErrorException("Asset service cannot be empty."); @@ -58,16 +63,33 @@ public StellarPaymentObserver stellarPaymentObserverService( .horizonServer(appConfig.getHorizonUrl()) .paymentTokenStore(stellarPaymentStreamerCursorStore) .observers(paymentListeners) - .accounts( - stellarAssets.stream() - .map(AssetInfo::getDistributionAccount) - .collect(Collectors.toList())) + .paymentObservingAccountManager(paymentObservingAccountsManager) .build(); + // Add distribution wallet to the observing list as type RESIDENTIAL + for (AssetInfo assetInfo : stellarAssets) { + if (!paymentObservingAccountsManager.lookupAndUpdate(assetInfo.getDistributionAccount())) { + paymentObservingAccountsManager.upsert( + assetInfo.getDistributionAccount(), + PaymentObservingAccountsManager.AccountType.RESIDENTIAL); + } + } + stellarPaymentObserverService.start(); return stellarPaymentObserverService; } + @Bean + public PaymentObservingAccountsManager observingAccounts( + PaymentObservingAccountStore paymentObservingAccountStore) { + return new PaymentObservingAccountsManager(paymentObservingAccountStore); + } + + @Bean + public PaymentObservingAccountStore observingAccountStore(PaymentObservingAccountRepo repo) { + return new PaymentObservingAccountStore(repo); + } + @Bean public CirclePaymentObserverService circlePaymentObserverService( OkHttpClient httpClient, diff --git a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java b/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java index 57af887fcb..920fcc4613 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java @@ -20,6 +20,7 @@ import org.stellar.anchor.platform.data.*; import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentService; +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.platform.service.*; import org.stellar.anchor.sep1.Sep1Service; import org.stellar.anchor.sep10.Sep10Service; @@ -160,6 +161,7 @@ CirclePaymentService circlePaymentService( Sep31DepositInfoGenerator sep31DepositInfoGenerator( Sep31Config sep31Config, CirclePaymentService circlePaymentService, + PaymentObservingAccountsManager paymentObservingAccountsManager, UniqueAddressIntegration uniqueAddressIntegration) { switch (sep31Config.getDepositInfoGeneratorType()) { case SELF: @@ -169,7 +171,8 @@ Sep31DepositInfoGenerator sep31DepositInfoGenerator( return new Sep31DepositInfoGeneratorCircle(circlePaymentService); case API: - return new Sep31DepositInfoGeneratorApi(uniqueAddressIntegration); + return new Sep31DepositInfoGeneratorApi( + uniqueAddressIntegration, paymentObservingAccountsManager); default: throw new RuntimeException("Not supported"); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/model/Customer.java b/platform/src/main/java/org/stellar/anchor/platform/data/Customer.java similarity index 85% rename from platform/src/main/java/org/stellar/anchor/platform/model/Customer.java rename to platform/src/main/java/org/stellar/anchor/platform/data/Customer.java index 471c928c3d..d5d6d2f8a5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/model/Customer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/Customer.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.model; +package org.stellar.anchor.platform.data; import java.util.List; import javax.persistence.Entity; diff --git a/platform/src/main/java/org/stellar/anchor/platform/model/CustomerStatus.java b/platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java similarity index 80% rename from platform/src/main/java/org/stellar/anchor/platform/model/CustomerStatus.java rename to platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java index 17551cbae2..6be62a1d97 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/model/CustomerStatus.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.model; +package org.stellar.anchor.platform.data; import javax.persistence.Entity; import javax.persistence.Id; diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java new file mode 100644 index 0000000000..44d1524773 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java @@ -0,0 +1,25 @@ +package org.stellar.anchor.platform.data; + +import java.time.Instant; +import javax.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@Table(name = "stellar_payment_observing_account") +public class PaymentObservingAccount { + public PaymentObservingAccount(String account, Instant lastObserved) { + this.account = account; + this.lastObserved = lastObserved; + } + + @Column(unique = true) + @Id + String account; + + Instant lastObserved; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccountRepo.java b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccountRepo.java new file mode 100644 index 0000000000..7519df7a28 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccountRepo.java @@ -0,0 +1,8 @@ +package org.stellar.anchor.platform.data; + +import org.springframework.data.repository.CrudRepository; + +public interface PaymentObservingAccountRepo + extends CrudRepository { + PaymentObservingAccount findByAccount(String account); +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursor.java b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursor.java index a7c5715107..3bc8492ba1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursor.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursor.java @@ -7,11 +7,13 @@ @Data @Entity @Access(AccessType.FIELD) -@Table(name = "stellar_account_page_token") +@Table(name = "stellar_payment_observer_page_token") public class PaymentStreamerCursor { + public static final String SINGLETON_ID = "SINGLETON_ID"; + @Id - @SerializedName("account_id") - String accountId; + @SerializedName("id") + String id = SINGLETON_ID; String cursor; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursorRepo.java b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursorRepo.java index c8fb786dc9..eb6ad5fb44 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursorRepo.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentStreamerCursorRepo.java @@ -1,10 +1,11 @@ package org.stellar.anchor.platform.data; import java.util.Optional; +import org.jetbrains.annotations.NotNull; import org.springframework.data.repository.CrudRepository; public interface PaymentStreamerCursorRepo extends CrudRepository { - Optional findById(String id); - Optional findByAccountId(String accountId); + @NotNull + Optional findById(String id); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java index 49fbe7b464..6129be480a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java @@ -1,5 +1,7 @@ package org.stellar.anchor.platform.payment.observer.stellar; +import static org.stellar.anchor.platform.data.PaymentStreamerCursor.SINGLETON_ID; + import java.util.Optional; import org.springframework.stereotype.Component; import org.stellar.anchor.platform.data.PaymentStreamerCursor; @@ -14,19 +16,21 @@ public class JdbcStellarPaymentStreamerCursorStore implements StellarPaymentStre } @Override - public void save(String account, String cursor) { - PaymentStreamerCursor paymentStreamerCursor = this.repo.findByAccountId(account).orElse(null); + public void save(String cursor) { + PaymentStreamerCursor paymentStreamerCursor = this.repo.findById(SINGLETON_ID).orElse(null); if (paymentStreamerCursor == null) { paymentStreamerCursor = new PaymentStreamerCursor(); } - paymentStreamerCursor.setAccountId(account); + + paymentStreamerCursor.setId(SINGLETON_ID); paymentStreamerCursor.setCursor(cursor); + this.repo.save(paymentStreamerCursor); } @Override - public String load(String account) { - Optional pageToken = repo.findByAccountId(account); + public String load() { + Optional pageToken = repo.findById(SINGLETON_ID); return pageToken.map(PaymentStreamerCursor::getCursor).orElse(null); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java index 64c413fd27..71e8c736c6 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java @@ -5,14 +5,15 @@ public class MemoryStellarPaymentStreamerCursorStore implements StellarPaymentStreamerCursorStore { Map mapTokens = new HashMap<>(); + String cursor = null; @Override - public void save(String account, String cursor) { - mapTokens.put(account, cursor); + public void save(String cursor) { + this.cursor = cursor; } @Override - public String load(String account) { - return mapTokens.get(account); + public String load() { + return this.cursor; } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountStore.java new file mode 100644 index 0000000000..89d40e3002 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountStore.java @@ -0,0 +1,48 @@ +package org.stellar.anchor.platform.payment.observer.stellar; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import org.stellar.anchor.platform.data.PaymentObservingAccount; +import org.stellar.anchor.platform.data.PaymentObservingAccountRepo; +import org.stellar.anchor.util.Log; + +public class PaymentObservingAccountStore { + PaymentObservingAccountRepo repo; + + public PaymentObservingAccountStore(PaymentObservingAccountRepo repo) { + this.repo = repo; + } + + List list() { + Log.debug("Retrieving the list of observing account from the store."); + List paymentObservingAccounts = new ArrayList<>(); + repo.findAll().forEach(paymentObservingAccounts::add); + return paymentObservingAccounts; + } + + void upsert(String account, Instant lastObserved) { + Log.infoF("Upserting account[{}, {}]", account, lastObserved); + PaymentObservingAccount poa = repo.findByAccount(account); + if (poa == null) { + poa = new PaymentObservingAccount(account, lastObserved); + Log.infoF("Save account[{}, {}] to data store", account, lastObserved); + repo.save(poa); + } else if (lastObserved.isAfter(poa.getLastObserved())) { + // save if newer + poa.setLastObserved(lastObserved); + Log.infoF("Update account[{}, {}] to data store", account, lastObserved); + repo.save(poa); + } + } + + void delete(String account) { + PaymentObservingAccount poa = repo.findByAccount(account); + if (poa != null) { + Log.infoF("Delete account[{}]", account); + repo.delete(poa); + } else { + Log.warnF("Account[{}] cannot be found for deletion.", account); + } + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManager.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManager.java new file mode 100644 index 0000000000..0fa9846a01 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManager.java @@ -0,0 +1,159 @@ +package org.stellar.anchor.platform.payment.observer.stellar; + +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.MINUTES; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import lombok.AllArgsConstructor; +import org.stellar.anchor.api.exception.ValueValidationException; +import org.stellar.anchor.platform.data.PaymentObservingAccount; +import org.stellar.anchor.util.Log; + +public class PaymentObservingAccountsManager { + Map allAccounts; + private final PaymentObservingAccountStore store; + + public PaymentObservingAccountsManager(PaymentObservingAccountStore store) { + this.store = store; + allAccounts = new ConcurrentHashMap<>(); + } + + @PostConstruct + public void initialize() throws ValueValidationException { + List accounts = store.list(); + for (PaymentObservingAccount account : accounts) { + ObservingAccount oa = + new ObservingAccount( + account.getAccount(), account.getLastObserved(), AccountType.TRANSIENT); + upsert(oa); + } + + // Start the eviction task + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate( + this::evictAndPersist, 60, getEvictPeriod().getSeconds(), TimeUnit.SECONDS); + } + + /** + * The shutdown hook that is run when Spring terminates. The allAccounts map will be evicted and + * flushed. + */ + @PreDestroy + public void shutdown() { + evictAndPersist(); + } + + public void evictAndPersist() { + Log.info("Evicting old accounts..."); + this.evict(getEvictMaxIdleTime()); + Log.info("Persisting accounts..."); + for (ObservingAccount account : this.getAccounts()) { + store.upsert(account.account, account.lastObserved); + } + } + + /** + * Adds an account to be observed. If the account is being observed, it will be updated. + * + * @param account The account being observed. + * @param type true The account type. + */ + public void upsert(String account, AccountType type) throws ValueValidationException { + if (account != null && type != null) { + upsert(new ObservingAccount(account, Instant.now(), type)); + } + } + + /** + * Add an account to be observed. If the account is being observed, it will be updated. + * + * @param observingAccount The account being observed. + */ + public void upsert(ObservingAccount observingAccount) { + if (observingAccount != null) { + ObservingAccount existingAccount = allAccounts.get(observingAccount.account); + if (existingAccount == null) { + allAccounts.put(observingAccount.account, observingAccount); + // update the database + store.upsert(observingAccount.account, observingAccount.lastObserved); + } else { + existingAccount.account = observingAccount.account; + existingAccount.lastObserved = observingAccount.lastObserved; + if (existingAccount.type == AccountType.TRANSIENT) { + existingAccount.type = observingAccount.type; + } + } + } + } + + /** + * Gets the list of observed accounts. + * + * @return The list of observed accounts. + */ + public List getAccounts() { + return new ArrayList<>(allAccounts.values()); + } + + /** + * Look up if the account is being observed. If the account is being observed, the lastObserved + * timestamp of the observing account will be updated. + * + * @param account The account to be checked. + * @return true if the account is being observed. false, otherwise. + */ + public boolean lookupAndUpdate(String account) { + ObservingAccount acct = allAccounts.get(account); + if (acct == null) return false; + acct.lastObserved = Instant.now(); + return true; + } + + /** + * Evict expired accounts + * + * @param maxIdleTime evict all accounts that are older than maxAge + */ + public void evict(Duration maxIdleTime) { + for (ObservingAccount acct : getAccounts()) { + if (acct.type == AccountType.RESIDENTIAL) continue; + + Duration idleTime = Duration.between(Instant.now(), acct.lastObserved).abs(); + if (idleTime.compareTo(maxIdleTime) > 0) { + allAccounts.remove(acct.account); + store.delete(acct.account); + } + } + } + + public enum AccountType { + TRANSIENT, // the account is transient and can be flushed out of the list. + RESIDENTIAL // the account is residential and will stay in the list. For example, a + // distribution account + } + + @AllArgsConstructor + public static class ObservingAccount { + String account; + Instant lastObserved; + AccountType type; + } + + Duration getEvictPeriod() { + return Duration.of(5, MINUTES); + } + + Duration getEvictMaxIdleTime() { + return Duration.of(30, DAYS); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java index 089c38f7b7..3396c2ed0e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java @@ -13,6 +13,7 @@ import lombok.Data; import org.jetbrains.annotations.NotNull; import org.stellar.anchor.api.exception.SepException; +import org.stellar.anchor.api.exception.ValueValidationException; import org.stellar.anchor.api.platform.HealthCheckResult; import org.stellar.anchor.api.platform.HealthCheckStatus; import org.stellar.anchor.healthcheck.HealthCheckable; @@ -31,49 +32,37 @@ public class StellarPaymentObserver implements HealthCheckable { final Server server; - final SortedSet accounts; final Set observers; - final List> streams; final StellarPaymentStreamerCursorStore paymentStreamerCursorStore; final Map, String> mapStreamToAccount = new HashMap<>(); + final PaymentObservingAccountsManager paymentObservingAccountsManager; + SSEStream stream; StellarPaymentObserver( String horizonServer, - SortedSet accounts, Set observers, + PaymentObservingAccountsManager paymentObservingAccountsManager, StellarPaymentStreamerCursorStore paymentStreamerCursorStore) { this.server = new Server(horizonServer); this.observers = observers; - this.accounts = accounts; - this.streams = new ArrayList<>(accounts.size()); + this.paymentObservingAccountsManager = paymentObservingAccountsManager; this.paymentStreamerCursorStore = paymentStreamerCursorStore; } /** Start watching the accounts. */ public void start() { - for (String account : accounts) { - SSEStream stream = watch(account); - mapStreamToAccount.put(stream, account); - this.streams.add(stream); - } + this.stream = watch(); } /** Graceful shutdown. */ public void shutdown() { - for (SSEStream stream : streams) { - stream.close(); - } + this.stream.close(); } - public SSEStream watch(String account) { + public SSEStream watch() { PaymentsRequestBuilder paymentsRequest = - server - .payments() - .forAccount(account) - .includeTransactions(true) - .limit(200) - .order(RequestBuilder.Order.ASC); - String lastToken = paymentStreamerCursorStore.load(account); + server.payments().includeTransactions(true).order(RequestBuilder.Order.ASC); + String lastToken = paymentStreamerCursorStore.load(); if (lastToken != null) { paymentsRequest.cursor(lastToken); } @@ -83,7 +72,7 @@ public SSEStream watch(String account) { @Override public void onEvent(OperationResponse operationResponse) { if (!operationResponse.isTransactionSuccessful()) { - paymentStreamerCursorStore.save(account, operationResponse.getPagingToken()); + paymentStreamerCursorStore.save(operationResponse.getPagingToken()); return; } @@ -108,10 +97,12 @@ public void onEvent(OperationResponse operationResponse) { if (observedPayment != null) { try { - if (observedPayment.getTo().equals(account)) { + if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getTo())) { final ObservedPayment finalObservedPayment = observedPayment; observers.forEach(observer -> observer.onReceived(finalObservedPayment)); - } else if (observedPayment.getFrom().equals(account)) { + } + if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getFrom()) + && !observedPayment.getTo().equals(observedPayment.getFrom())) { final ObservedPayment finalObservedPayment = observedPayment; observers.forEach(observer -> observer.onSent(finalObservedPayment)); } @@ -119,8 +110,7 @@ public void onEvent(OperationResponse operationResponse) { Log.errorEx(t); } } - - paymentStreamerCursorStore.save(account, operationResponse.getPagingToken()); + paymentStreamerCursorStore.save(operationResponse.getPagingToken()); } @Override @@ -143,10 +133,10 @@ public int compareTo(@NotNull HealthCheckable other) { public static class Builder { String horizonServer = "https://horizon-testnet.stellar.org"; - SortedSet accounts = new TreeSet<>(); Set observers = new HashSet<>(); StellarPaymentStreamerCursorStore paymentStreamerCursorStore = new MemoryStellarPaymentStreamerCursorStore(); + private PaymentObservingAccountsManager paymentObservingAccountsManager; public Builder() {} @@ -155,11 +145,6 @@ public Builder horizonServer(String horizonServer) { return this; } - public Builder accounts(List accounts) { - this.accounts.addAll(accounts); - return this; - } - public Builder observers(List observers) { this.observers.addAll(observers); return this; @@ -171,9 +156,15 @@ public Builder paymentTokenStore( return this; } - public StellarPaymentObserver build() { + public Builder paymentObservingAccountManager( + PaymentObservingAccountsManager paymentObservingAccountsManager) { + this.paymentObservingAccountsManager = paymentObservingAccountsManager; + return this; + } + + public StellarPaymentObserver build() throws ValueValidationException { return new StellarPaymentObserver( - horizonServer, accounts, observers, paymentStreamerCursorStore); + horizonServer, observers, paymentObservingAccountsManager, paymentStreamerCursorStore); } } @@ -191,35 +182,34 @@ public List getTags() { public HealthCheckResult check() { List results = new ArrayList<>(); HealthCheckStatus status = GREEN; - for (SSEStream stream : streams) { - StreamHealth.StreamHealthBuilder healthBuilder = StreamHealth.builder(); - healthBuilder.account(mapStreamToAccount.get(stream)); - // populate executorService information - ExecutorService executorService = getField(stream, "executorService", null); - if (executorService != null) { - healthBuilder.threadShutdown(executorService.isShutdown()); - healthBuilder.threadTerminated(executorService.isTerminated()); - if (executorService.isShutdown() || executorService.isTerminated()) { - status = RED; - } - } else { - status = RED; - } - boolean isStopped = getField(stream, "isStopped", new AtomicBoolean(false)).get(); - healthBuilder.stopped(isStopped); - if (isStopped) { + StreamHealth.StreamHealthBuilder healthBuilder = StreamHealth.builder(); + healthBuilder.account(mapStreamToAccount.get(stream)); + // populate executorService information + ExecutorService executorService = getField(stream, "executorService", null); + if (executorService != null) { + healthBuilder.threadShutdown(executorService.isShutdown()); + healthBuilder.threadTerminated(executorService.isTerminated()); + if (executorService.isShutdown() || executorService.isTerminated()) { status = RED; } + } else { + status = RED; + } - AtomicReference lastEventId = getField(stream, "lastEventId", null); - if (lastEventId != null) { - healthBuilder.lastEventId(lastEventId.get()); - } + boolean isStopped = getField(stream, "isStopped", new AtomicBoolean(false)).get(); + healthBuilder.stopped(isStopped); + if (isStopped) { + status = RED; + } - results.add(healthBuilder.build()); + AtomicReference lastEventId = getField(stream, "lastEventId", null); + if (lastEventId != null) { + healthBuilder.lastEventId(lastEventId.get()); } + results.add(healthBuilder.build()); + return SPOHealthCheckResult.builder() .name(getName()) .streams(results) @@ -234,7 +224,7 @@ public HealthCheckResult check() { class SPOHealthCheckResult implements HealthCheckResult { transient String name; - List statuses = List.of(GREEN.getName(), RED.getName()); + List statuses; String status; diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java index 9e4ca03c62..841fda73ef 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java @@ -1,7 +1,7 @@ package org.stellar.anchor.platform.payment.observer.stellar; public interface StellarPaymentStreamerCursorStore { - void save(String account, String cursor); + void save(String cursor); - String load(String account); + String load(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java index 1a8fd14c1c..8327dd2427 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java @@ -1,19 +1,27 @@ package org.stellar.anchor.platform.service; +import static org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager.AccountType.TRANSIENT; + import java.util.Objects; import org.stellar.anchor.api.callback.GetUniqueAddressResponse; import org.stellar.anchor.api.callback.UniqueAddressIntegration; import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.api.exception.InternalServerErrorException; import org.stellar.anchor.api.sep.sep31.Sep31DepositInfo; +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.sep31.Sep31DepositInfoGenerator; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.util.MemoHelper; public class Sep31DepositInfoGeneratorApi implements Sep31DepositInfoGenerator { - private UniqueAddressIntegration uniqueAddressIntegration; + private final UniqueAddressIntegration uniqueAddressIntegration; + private final PaymentObservingAccountsManager paymentObservingAccountsManager; - public Sep31DepositInfoGeneratorApi(UniqueAddressIntegration uniqueAddressIntegration) { + public Sep31DepositInfoGeneratorApi( + UniqueAddressIntegration uniqueAddressIntegration, + PaymentObservingAccountsManager paymentObservingAccountsManager) { this.uniqueAddressIntegration = uniqueAddressIntegration; + this.paymentObservingAccountsManager = paymentObservingAccountsManager; } @Override @@ -21,12 +29,20 @@ public Sep31DepositInfo generate(Sep31Transaction txn) throws AnchorException { GetUniqueAddressResponse response = uniqueAddressIntegration.getUniqueAddress(txn.getId()); GetUniqueAddressResponse.UniqueAddress uniqueAddress = response.getUniqueAddress(); + if (uniqueAddress == null) { + throw new InternalServerErrorException( + "The server does not respond with a unique address for SEP31 deposit"); + } + if (!Objects.toString(uniqueAddress.getMemo(), "").isEmpty() && !Objects.toString(uniqueAddress.getMemoType(), "").isEmpty()) { // Check the validity of the returned memo and memo type MemoHelper.makeMemo(uniqueAddress.getMemo(), uniqueAddress.getMemoType()); } + // Add to payment observer manager so that we get events of the stellar address. + paymentObservingAccountsManager.upsert(uniqueAddress.getStellarAddress(), TRANSIENT); + return new Sep31DepositInfo( uniqueAddress.getStellarAddress(), uniqueAddress.getMemo(), uniqueAddress.getMemoType()); } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt index 963505e05b..3004e37517 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt @@ -1,8 +1,10 @@ package org.stellar.anchor.platform -import io.mockk.every -import io.mockk.mockk +import io.mockk.* +import io.mockk.impl.annotations.MockK +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.ServerErrorException @@ -10,17 +12,36 @@ import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.platform.payment.observer.PaymentListener +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountStore +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentStreamerCursorStore class PaymentConfigTest { + @MockK private lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore + @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + } + + @AfterEach + fun teardown() { + clearAllMocks() + unmockkAll() + } + @Test fun test_stellarPaymentObserverService_failure() { + val assetService: AssetService = ResourceJsonAssetService("test_assets.json") val paymentConfig = PaymentConfig() + val mockPaymentListener = mockk() + val mockPaymentListeners = listOf(mockPaymentListener) // assetService is null var ex = assertThrows { - paymentConfig.stellarPaymentObserverService(null, null, null, null) + paymentConfig.stellarPaymentObserverService(null, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) @@ -30,7 +51,7 @@ class PaymentConfigTest { every { mockEmptyAssetService.listAllAssets() } returns null ex = assertThrows { - paymentConfig.stellarPaymentObserverService(mockEmptyAssetService, null, null, null) + paymentConfig.stellarPaymentObserverService(mockEmptyAssetService, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) @@ -40,59 +61,82 @@ class PaymentConfigTest { every { mockStellarLessAssetService.listAllAssets() } returns listOf() ex = assertThrows { - paymentConfig.stellarPaymentObserverService(mockStellarLessAssetService, null, null, null) + paymentConfig.stellarPaymentObserverService( + mockStellarLessAssetService, + null, + null, + null, + null + ) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service should contain at least one Stellar asset.", ex.message) // paymentListeners is null - val assetService: AssetService = ResourceJsonAssetService("test_assets.json") ex = - assertThrows { paymentConfig.stellarPaymentObserverService(assetService, null, null, null) } + assertThrows { + paymentConfig.stellarPaymentObserverService(assetService, null, null, null, null) + } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) // paymentListeners is empty ex = assertThrows { - paymentConfig.stellarPaymentObserverService(assetService, listOf(), null, null) + paymentConfig.stellarPaymentObserverService(assetService, listOf(), null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) // paymentStreamerCursorStore is null - val mockPaymentListener = mockk() - val mockPaymentListeners = listOf(mockPaymentListener) ex = assertThrows { - paymentConfig.stellarPaymentObserverService(assetService, mockPaymentListeners, null, null) + paymentConfig.stellarPaymentObserverService( + assetService, + mockPaymentListeners, + null, + null, + null + ) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Payment streamer cursor store cannot be empty.", ex.message) // appConfig is null - val mockPaymentStreamerCursorStore = mockk() - every { mockPaymentStreamerCursorStore.load(any()) } returns null + every { paymentStreamerCursorStore.load() } returns null ex = assertThrows { paymentConfig.stellarPaymentObserverService( assetService, mockPaymentListeners, - mockPaymentStreamerCursorStore, + paymentStreamerCursorStore, + null, null ) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("App config cannot be empty.", ex.message) + } + @Test + fun test_givenGoodManager_whenConstruct_thenOk() { // success! + val paymentConfig = PaymentConfig() + val assetService: AssetService = ResourceJsonAssetService("test_assets.json") + val mockPaymentListener = mockk() + val mockPaymentListeners = listOf(mockPaymentListener) + + val paymentObservingAccountsManager = + PaymentObservingAccountsManager(paymentObservingAccountStore) + val mockAppConfig = mockk() every { mockAppConfig.horizonUrl } returns "https://horizon-testnet.stellar.org" assertDoesNotThrow { paymentConfig.stellarPaymentObserverService( assetService, mockPaymentListeners, - mockPaymentStreamerCursorStore, + paymentStreamerCursorStore, + paymentObservingAccountsManager, mockAppConfig ) } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManagerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManagerTest.kt new file mode 100644 index 0000000000..770b526403 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManagerTest.kt @@ -0,0 +1,178 @@ +package org.stellar.anchor.platform.payment.observer.stellar + +import io.mockk.* +import io.mockk.impl.annotations.MockK +import java.time.Duration +import java.time.Instant +import java.time.temporal.ChronoUnit.DAYS +import java.time.temporal.ChronoUnit.HOURS +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.anchor.platform.data.PaymentObservingAccount +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager.AccountType.RESIDENTIAL +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager.AccountType.TRANSIENT + +class PaymentObservingAccountsManagerTest { + @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + every { paymentObservingAccountStore.list() } returns + listOf( + PaymentObservingAccount( + "GCK5ECMM67ZN7RWGUSDKQAW6CAF6Q5WYK2VQJ276SJMINU6WVCIQW6BL", + Instant.now().minus(0, HOURS) + ), + PaymentObservingAccount( + "GCIWQDKACLW26UJXY5CTLULVYUOYROZPAPDDYEQKNGIERVOAXSPLABMB", + Instant.now().minus(2, HOURS) + ), + PaymentObservingAccount( + "GAPBFA5ZYG5VVKN7WPMH6K5CBXGU2AM5ED7S54VX27J7S222NKMTWKR6", + Instant.now().minus(12, HOURS) + ) + ) + } + + @AfterEach + fun teardown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun test_addAndRemove_success() { + val obs = PaymentObservingAccountsManager(paymentObservingAccountStore) + obs.initialize() + + obs.upsert("GCIWQDKACLW26UJXY5CTLULVYUOYROZPAPDDYEQKNGIERVOAXSPLABMB", TRANSIENT) + obs.upsert("GCK5ECMM67ZN7RWGUSDKQAW6CAF6Q5WYK2VQJ276SJMINU6WVCIQW6BL", TRANSIENT) + obs.upsert("GAPBFA5ZYG5VVKN7WPMH6K5CBXGU2AM5ED7S54VX27J7S222NKMTWKR6", TRANSIENT) + + assertEquals(3, obs.accounts.size) + + assertTrue(obs.lookupAndUpdate("GCIWQDKACLW26UJXY5CTLULVYUOYROZPAPDDYEQKNGIERVOAXSPLABMB")) + assertTrue(obs.lookupAndUpdate("GCK5ECMM67ZN7RWGUSDKQAW6CAF6Q5WYK2VQJ276SJMINU6WVCIQW6BL")) + assertTrue(obs.lookupAndUpdate("GAPBFA5ZYG5VVKN7WPMH6K5CBXGU2AM5ED7S54VX27J7S222NKMTWKR6")) + } + + @Test + fun test_add_invalid() { + val obs = PaymentObservingAccountsManager(paymentObservingAccountStore) + obs.initialize() + + assertEquals(3, obs.accounts.size) + obs.upsert("GB4DZFFUWC64MZ3BQ433ME7QBODCSFZRBOLWEWEMJIVHABTWGT3W2Q22", TRANSIENT) + assertDoesNotThrow { + obs.upsert("GB4DZFFUWC64MZ3BQ433ME7QBODCSFZRBOLWEWEMJIVHABTWGT3W2Q22", TRANSIENT) + } + assertEquals(4, obs.accounts.size) + } + + @Test + fun test_evict() { + val obs = PaymentObservingAccountsManager(paymentObservingAccountStore) + + obs.initialize() + + // Nothing evict-able + assertEquals(3, obs.accounts.size) + obs.evict(Duration.of(1, DAYS)) + assertEquals(3, obs.accounts.size) + obs.evict(Duration.of(1, DAYS)) + assertEquals(3, obs.accounts.size) + + obs.upsert( + PaymentObservingAccountsManager.ObservingAccount( + "GB4DZFFUWC64MZ3BQ433ME7QBODCSFZRBOLWEWEMJIVHABTWGT3W2Q22", + Instant.now().minus(24, HOURS), + TRANSIENT + ) + ) + + obs.upsert( + PaymentObservingAccountsManager.ObservingAccount( + "GCC2B7LML6UBKBA7NOMLCR57HPKMFHRO2VTZGSIMRLXDMECBXQ44MOYO", + Instant.now().minus(100, DAYS), + RESIDENTIAL + ) + ) + + assertEquals(5, obs.accounts.size) + + // Evict GB4DZFFUWC64MZ3BQ433ME7QBODCSFZRBOLWEWEMJIVHABTWGT3W2Q22 + obs.evict(Duration.of(23, HOURS)) + assertFalse(obs.lookupAndUpdate("GB4DZFFUWC64MZ3BQ433ME7QBODCSFZRBOLWEWEMJIVHABTWGT3W2Q22")) + assertEquals(4, obs.accounts.size) + + // Test idempotency + obs.evict(Duration.of(23, HOURS)) + assertEquals(4, obs.accounts.size) + + // Update the last observed timestamp to avoid being evicted + assertTrue(obs.lookupAndUpdate("GAPBFA5ZYG5VVKN7WPMH6K5CBXGU2AM5ED7S54VX27J7S222NKMTWKR6")) + obs.evict(Duration.of(11, HOURS)) + assertEquals(4, obs.accounts.size) + + // Make sure observing random account return false + assertFalse(obs.lookupAndUpdate("GBXXYA2NZPCS2LHLXBWOQ6UXXRCH3N5YVTYWZ4DEVYTEWVFV7R7MEKSV")) + + // Evict another one + obs.evict(Duration.of(1, HOURS)) + assertEquals(3, obs.accounts.size) + + Thread.sleep(10) + + // Evict all expiring + obs.evict(Duration.ZERO) + assertEquals(1, obs.accounts.size) + } + + @Test + fun test_whenEvictAndPersist_thenSuccessful() { + val obs = spyk(PaymentObservingAccountsManager(paymentObservingAccountStore)) + assertEquals(0, obs.accounts.size) + + obs.initialize() + assertEquals(3, obs.accounts.size) + + obs.evictAndPersist() + assertEquals(3, obs.accounts.size) + + obs.upsert("GBXXYA2NZPCS2LHLXBWOQ6UXXRCH3N5YVTYWZ4DEVYTEWVFV7R7MEKSV", TRANSIENT) + assertEquals(4, obs.accounts.size) + Thread.sleep(10) + + every { obs.evictMaxIdleTime } returns Duration.of(1, DAYS) + obs.evictAndPersist() + assertEquals(4, obs.accounts.size) + + every { obs.evictMaxIdleTime } returns Duration.ZERO + obs.evictAndPersist() + assertEquals(0, obs.accounts.size) + verify(exactly = 1) { + paymentObservingAccountStore.delete( + "GBXXYA2NZPCS2LHLXBWOQ6UXXRCH3N5YVTYWZ4DEVYTEWVFV7R7MEKSV" + ) + } + verify(exactly = 1) { + paymentObservingAccountStore.delete( + "GCK5ECMM67ZN7RWGUSDKQAW6CAF6Q5WYK2VQJ276SJMINU6WVCIQW6BL" + ) + } + verify(exactly = 1) { + paymentObservingAccountStore.delete( + "GCIWQDKACLW26UJXY5CTLULVYUOYROZPAPDDYEQKNGIERVOAXSPLABMB" + ) + } + verify(exactly = 1) { + paymentObservingAccountStore.delete( + "GAPBFA5ZYG5VVKN7WPMH6K5CBXGU2AM5ED7S54VX27J7S222NKMTWKR6" + ) + } + verify(exactly = 4) { paymentObservingAccountStore.delete(any()) } + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt index f112a8e8ab..33c5203489 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApiTest.kt @@ -5,9 +5,8 @@ import io.mockk.clearAllMocks import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.unmockkAll -import java.util.* import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -16,17 +15,27 @@ import org.junit.jupiter.params.provider.ValueSource import org.stellar.anchor.api.callback.GetUniqueAddressResponse import org.stellar.anchor.api.callback.UniqueAddressIntegration import org.stellar.anchor.api.exception.HttpException +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountStore +import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager import org.stellar.anchor.sep31.Sep31Transaction class Sep31DepositInfoGeneratorApiTest { @MockK(relaxed = true) private lateinit var uniqueAddressIntegration: UniqueAddressIntegration + @MockK(relaxed = true) private lateinit var txn: Sep31Transaction + + @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore + + // Do not mock the manager + private lateinit var paymentObservingAccountsManager: PaymentObservingAccountsManager + val txnId = "this_is_a_transaction_id" val stellarAddress = "GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2" @BeforeEach fun setup() { MockKAnnotations.init(this, relaxUnitFun = true) + paymentObservingAccountsManager = PaymentObservingAccountsManager(paymentObservingAccountStore) } @AfterEach @@ -49,14 +58,21 @@ class Sep31DepositInfoGeneratorApiTest { val uniqueAddressResponse = GetUniqueAddressResponse.builder().uniqueAddress(uniqueAddress).build() + // Check the manager is not observing the account + assertFalse(paymentObservingAccountsManager.lookupAndUpdate(uniqueAddress.stellarAddress)) + every { txn.getId() } returns txnId every { uniqueAddressIntegration.getUniqueAddress(any()) } returns uniqueAddressResponse - val generator = Sep31DepositInfoGeneratorApi(uniqueAddressIntegration) + val generator = + Sep31DepositInfoGeneratorApi(uniqueAddressIntegration, paymentObservingAccountsManager) val depositInfo = generator.generate(txn) assertEquals(stellarAddress, depositInfo.stellarAddress) assertEquals(memo, depositInfo.memo) assertEquals(memoType, depositInfo.memoType) + + // Check if the manager is observing the account + assertTrue(paymentObservingAccountsManager.lookupAndUpdate(uniqueAddress.stellarAddress)) } @ParameterizedTest @@ -74,8 +90,12 @@ class Sep31DepositInfoGeneratorApiTest { every { txn.getId() } returns txnId every { uniqueAddressIntegration.getUniqueAddress(any()) } returns uniqueAddressResponse - val generator = Sep31DepositInfoGeneratorApi(uniqueAddressIntegration) + val generator = + Sep31DepositInfoGeneratorApi(uniqueAddressIntegration, paymentObservingAccountsManager) assertThrows { generator.generate(txn) } + + // Make sure the address does not go into manager when exception happens + assertFalse(paymentObservingAccountsManager.lookupAndUpdate(uniqueAddress.stellarAddress)) } @ParameterizedTest @@ -84,7 +104,8 @@ class Sep31DepositInfoGeneratorApiTest { every { txn.getId() } returns txnId every { uniqueAddressIntegration.getUniqueAddress(any()) } throws HttpException(statusCode) - val generator = Sep31DepositInfoGeneratorApi(uniqueAddressIntegration) + val generator = + Sep31DepositInfoGeneratorApi(uniqueAddressIntegration, paymentObservingAccountsManager) val ex = assertThrows { generator.generate(txn) } assertEquals(statusCode, ex.statusCode) } From 9c1f2547b6c624da07b7aca7cb4a781328e1bba4 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 15 Aug 2022 15:47:21 -0700 Subject: [PATCH 22/65] core/service-runner: Fix the tests skipping problem (core and service-runner) (#498) * add kotlin plugin to core and service-runner subproject * Empty commit to trigger CI. Co-authored-by: Marcelo Salloum --- build.gradle.kts | 2 -- core/build.gradle.kts | 10 +++++----- gradle/libs.versions.toml | 3 +-- service-runner/build.gradle.kts | 1 + 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4fc01ee446..0f24caf9dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -79,9 +79,7 @@ subprojects { // subproject task as of today. // testImplementation(rootProject.libs.bundles.junit) - testImplementation(rootProject.libs.bundles.kotlin) testImplementation(rootProject.libs.jsonassert) - testImplementation(rootProject.libs.mockk) testAnnotationProcessor(rootProject.libs.lombok) } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 2546355b79..4d8a103914 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -4,17 +4,20 @@ plugins { `java-library` `maven-publish` signing + alias(libs.plugins.kotlin.jvm) } version = "1.0.2" dependencies { compileOnly(libs.servlet.api) - compileOnly(libs.slf4j.api) + api(libs.lombok) - // TODO: To be removed or simplified. Spring has its own way of dependency management. + // Lombok should be used by all sub-projects to reduce Java verbosity + annotationProcessor(libs.lombok) + implementation(libs.spring.kafka) implementation(libs.bundles.kafka) @@ -38,9 +41,6 @@ dependencies { implementation(project(":api-schema")) - // Lombok should be used by all sub-projects to reduce Java verbosity - annotationProcessor(libs.lombok) - testImplementation(libs.okhttp3.mockserver) testImplementation(libs.servlet.api) testImplementation(libs.slf4j.api) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c07637733c..c660ee38ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -100,9 +100,8 @@ spring-aws-messaging = { module = "org.springframework.cloud:spring-cloud-aws-me [bundles] slf4j = ["slf4j-api", "slf4j-log4j12"] -junit = ["junit-api", "junit-engine", "junit-params", "junit-suite-engine"] +junit = ["junit-api", "junit-engine", "junit-params", "junit-suite-engine", "kotlin-stdlib", "kotlin-junit", "mockk"] kafka = ["kafka-clients", "kafka-connect", "kafka-json-schema"] -kotlin = ["kotlin-stdlib", "kotlin-junit"] [plugins] spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index 67f99322c7..d4147a8540 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -7,6 +7,7 @@ plugins { application alias(libs.plugins.spring.boot) alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.kotlin.jvm) } dependencies { From 77484c6f4a0b1407c3c8278e012d996c14880158 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Wed, 17 Aug 2022 10:56:27 -0700 Subject: [PATCH 23/65] ci: fix complete job (#501) --- .github/workflows/build_and_test.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 883b5bea99..5c0fd7cd0b 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -90,8 +90,9 @@ jobs: stellar-anchor-tests --home-domain $HOME_DOMAIN --seps 1 10 12 31 38 --sep-config platform/src/test/resources/stellar-anchor-tests-sep-config.json complete: + if: always() needs: [build_and_test, sep_validation_suite] runs-on: ubuntu-latest steps: - - if: contains(needs.*.result, 'failure') - run: exit 1 + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 From 4619ad761e1787594da59d525a13d6b9a6f58b4c Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Thu, 18 Aug 2022 14:44:02 -0300 Subject: [PATCH 24/65] hotfix: fix build.gradle.kts from the integration-tests package so the integration tests can be executed (#505) ### What Fix `build.gradle.kts` from the integration-tests package. ### Why So the integration tests can be executed. --- integration-tests/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index 0aa9b10984..eef5a0e1b2 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -4,6 +4,7 @@ plugins { `java-library` alias(libs.plugins.spring.boot) alias(libs.plugins.spring.dependency.management) + alias(libs.plugins.kotlin.jvm) } dependencies { From 22fb3332436882fdedad5eecf28c7f600ce3144a Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Thu, 18 Aug 2022 17:19:26 -0300 Subject: [PATCH 25/65] hotfix: SEP-12's deletion should not require the memo to be in the JWT_TOKEN when it is sent on the request body ### What The SEP-12's deletion should not require the memo to be in the JWT_TOKEN when it is sent on the request body ### Why Close #509 --- .../main/java/org/stellar/anchor/sep12/Sep12Service.java | 8 ++++---- .../kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) 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 77b610d034..a7e4fe3d83 100644 --- a/core/src/main/java/org/stellar/anchor/sep12/Sep12Service.java +++ b/core/src/main/java/org/stellar/anchor/sep12/Sep12Service.java @@ -49,17 +49,17 @@ public void deleteCustomer(JwtToken jwtToken, String account, String memo, Strin .filter(Objects::nonNull) .anyMatch(tokenAccount -> Objects.equals(tokenAccount, account)); - boolean isMemoAuthenticated = memo == null; + boolean isMemoMissingAuthentication = false; String muxedAccountId = Objects.toString(jwtToken.getMuxedAccountId(), null); if (muxedAccountId != null) { if (!Objects.equals(jwtToken.getMuxedAccount(), account)) { - isMemoAuthenticated = Objects.equals(muxedAccountId, memo); + isMemoMissingAuthentication = !Objects.equals(muxedAccountId, memo); } } else if (jwtToken.getAccountMemo() != null) { - isMemoAuthenticated = Objects.equals(jwtToken.getAccountMemo(), memo); + isMemoMissingAuthentication = !Objects.equals(jwtToken.getAccountMemo(), memo); } - if (!isAccountAuthenticated || !isMemoAuthenticated) { + if (!isAccountAuthenticated || isMemoMissingAuthentication) { infoF("Requester ({}) not authorized to delete account ({})", jwtToken.getAccount(), account); throw new SepNotAuthorizedException( String.format("Not authorized to delete account [%s] with memo [%s]", account, memo)); 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 8aa66d8c01..e40e9403cd 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt @@ -303,6 +303,11 @@ class Sep12ServiceTest { // succeeds if request account and memo are equal the token's assertDoesNotThrow { sep12Service.deleteCustomer(jwtToken, TEST_ACCOUNT, TEST_MEMO, null) } + // succeeds if the request account is equals the token's, and the token memo is empty while the + // request's is not + jwtToken = createJwtToken(TEST_ACCOUNT) + assertDoesNotThrow { sep12Service.deleteCustomer(jwtToken, TEST_ACCOUNT, "foo_bar", null) } + // PART 3 - muxed account // throws exception if request is missing the memo jwtToken = createJwtToken(TEST_MUXED_ACCOUNT) @@ -332,7 +337,7 @@ class Sep12ServiceTest { val mockNoCustomerFound = Sep12GetCustomerResponse() every { customerIntegration.getCustomer(any()) } returns mockNoCustomerFound - val jwtToken = createJwtToken("$TEST_ACCOUNT:$TEST_MEMO") + val jwtToken = createJwtToken(TEST_ACCOUNT) val ex: AnchorException = assertThrows { sep12Service.deleteCustomer(jwtToken, TEST_ACCOUNT, TEST_MEMO, null) } From 1427fceeb1206dd26a286b7117d195634419f406 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 19 Aug 2022 00:20:51 -0700 Subject: [PATCH 26/65] core: Use Kotlin backticks to rename tests to descriptive names (#507) * Rename tests to descriptive names * fix test errors * Compare the complatedAt field with truncation to SECONDS * delay the MetricEmitterService to avoid the race condition when Hibernate is not fully initialized --- .../api/sep/sep10/ChallengeRequest.java | 12 +- .../api/sep/sep10/ChallengeRequestTest.kt | 10 +- .../api/sep/sep10/ChallengeResponseTest.kt | 2 +- .../api/sep/sep10/ValidationRequestTest.kt | 2 +- .../api/sep/sep10/ValidationResponseTest.kt | 2 +- .../anchor/api/sep/sep24/Sep24DtoTests.kt | 13 +- .../asset/ResourceJsonAssetServiceTest.kt | 4 +- .../org/stellar/anchor/auth/AuthHelperTest.kt | 2 +- .../org/stellar/anchor/auth/JwtServiceTest.kt | 6 +- .../org/stellar/anchor/auth/JwtTokenTest.kt | 6 +- .../anchor/dto/SepExceptionResponseTest.kt | 8 +- .../sep31/Sep31PostTransactionRequestTest.kt | 2 +- .../anchor/dto/sep38/InfoResponseTest.kt | 2 +- .../stellar/anchor/filter/ApiKeyFilterTest.kt | 12 +- .../anchor/filter/JwtTokenFilterTest.kt | 18 +-- .../stellar/anchor/filter/NoneFilterTest.kt | 2 +- .../org/stellar/anchor/horizon/HorizonTest.kt | 2 +- .../anchor/model/Sep24TransactionTest.kt | 38 +++--- .../stellar/anchor/sep1/Sep1ServiceTest.kt | 2 +- .../stellar/anchor/sep10/Sep10ServiceTest.kt | 123 +++++++++++++----- .../stellar/anchor/sep12/Sep12ServiceTest.kt | 14 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 26 ++-- .../stellar/anchor/sep31/RefundPaymentTest.kt | 6 +- .../org/stellar/anchor/sep31/RefundsTest.kt | 8 +- .../stellar/anchor/sep31/Sep31HelperTest.kt | 6 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 29 +++-- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 34 ++--- .../org/stellar/anchor/util/DateUtilTest.kt | 2 +- .../org/stellar/anchor/util/FileUtilTest.kt | 2 +- .../kotlin/org/stellar/anchor/util/LogTest.kt | 15 ++- .../org/stellar/anchor/util/MathHelperTest.kt | 6 +- .../org/stellar/anchor/util/MemoHelperTest.kt | 4 +- .../org/stellar/anchor/util/NetUtilTest.kt | 6 +- .../org/stellar/anchor/util/OkHttpUtilTest.kt | 10 +- .../stellar/anchor/util/PropertyUtilTest.java | 52 -------- .../stellar/anchor/util/PropertyUtilTest.kt | 48 +++++++ .../org/stellar/anchor/util/SepHelperTest.kt | 2 +- .../anchor/util/SepLanguageHelperTest.kt | 2 +- .../stellar/anchor/platform/PlatformTests.kt | 6 +- .../platform/controller/Sep10Controller.java | 7 +- .../service/MetricEmitterService.java | 2 +- 41 files changed, 311 insertions(+), 244 deletions(-) delete mode 100644 core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.java create mode 100644 core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep10/ChallengeRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep10/ChallengeRequest.java index 77af8a1094..26965b86e6 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep10/ChallengeRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep10/ChallengeRequest.java @@ -1,8 +1,10 @@ package org.stellar.anchor.api.sep.sep10; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; import lombok.Data; +@Builder @Data public class ChallengeRequest { String account; @@ -13,14 +15,4 @@ public class ChallengeRequest { @JsonProperty("client_domain") String clientDomain; - - public static ChallengeRequest of( - String account, String memo, String homeDomain, String clientDomain) { - ChallengeRequest challengeRequest = new ChallengeRequest(); - challengeRequest.account = account; - challengeRequest.memo = memo; - challengeRequest.homeDomain = homeDomain; - challengeRequest.clientDomain = clientDomain; - return challengeRequest; - } } diff --git a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeRequestTest.kt b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeRequestTest.kt index 9765c570f3..ed24a91dd2 100644 --- a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeRequestTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeRequestTest.kt @@ -12,8 +12,14 @@ internal class ChallengeRequestTest { } @Test - fun of() { - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + fun `test ChallanegeRequest creation`() { + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() assertEquals(TEST_ACCOUNT, cr.account) assertEquals(TEST_MEMO, cr.memo) assertEquals(TEST_HOME_DOMAIN, cr.homeDomain) diff --git a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeResponseTest.kt b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeResponseTest.kt index de80d1fb74..716b889fb3 100644 --- a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeResponseTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ChallengeResponseTest.kt @@ -10,7 +10,7 @@ internal class ChallengeResponseTest { } @Test - fun testOf() { + fun `test creation of ChallengeResponse`() { val cr = ChallengeResponse.of(TEST_TRANSACTION, TEST_NETWORKPHRASE) assertEquals(TEST_TRANSACTION, cr.transaction) assertEquals(TEST_NETWORKPHRASE, cr.networkPassphrase) diff --git a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationRequestTest.kt b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationRequestTest.kt index 150a690cbc..55a0818663 100644 --- a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationRequestTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationRequestTest.kt @@ -9,7 +9,7 @@ internal class ValidationRequestTest { } @Test - fun testOf() { + fun `test creation of ValidationRequest`() { val vr = ValidationRequest.of(TEST_TRANSACTION) assertEquals(TEST_TRANSACTION, vr.transaction) } diff --git a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationResponseTest.kt b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationResponseTest.kt index 97ae9deb17..e48111c59d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationResponseTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep10/ValidationResponseTest.kt @@ -9,7 +9,7 @@ internal class ValidationResponseTest { } @Test - fun of() { + fun `test creation of ValidationResponse`() { val vr = ValidationResponse.of(TEST_TOKEN) assertEquals(TEST_TOKEN, vr.token) } diff --git a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep24/Sep24DtoTests.kt b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep24/Sep24DtoTests.kt index 78abf7c0a4..422a87e459 100644 --- a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep24/Sep24DtoTests.kt +++ b/core/src/test/kotlin/org/stellar/anchor/api/sep/sep24/Sep24DtoTests.kt @@ -2,11 +2,10 @@ package org.stellar.anchor.api.sep.sep24 import org.junit.jupiter.api.Test import org.stellar.anchor.api.sep.AssetInfo -import org.stellar.anchor.api.sep.sep24.* internal class Sep24DtoTests { @Test - fun testAssetInfoCoverage() { + fun `test to cover AssetInfo`() { val ar = AssetInfo() ar.getSignificantDecimals() ar.setSignificantDecimals(0) @@ -35,7 +34,7 @@ internal class Sep24DtoTests { } @Test - fun testWithdrawDepositTransactionResponseCoverage() { + fun `test to cover WithdrawDepositTransactionResponse`() { val dtr = DepositTransactionResponse() dtr.setDepositMemo("") dtr.getDepositMemo() @@ -59,13 +58,13 @@ internal class Sep24DtoTests { } @Test - fun testGetTransactionRequestCoverage() { + fun `test to cover GetTransactionRequest`() { val gtr = GetTransactionRequest("", "", "", "") gtr.canEqual(Object()) } @Test - fun testInfoResponseCoverage() { + fun `test to cover InfoResponse`() { val ir = InfoResponse() ir.getFeatureFlags() ir.setFeatureFlags(InfoResponse.FeatureFlagResponse()) @@ -82,7 +81,7 @@ internal class Sep24DtoTests { } @Test - fun testInteractiveTransactionResponseCoverage() { + fun `test to cover InteractiveTransactionResponse`() { val itr = InteractiveTransactionResponse("", "", "") itr.getType() itr.setType("") @@ -94,7 +93,7 @@ internal class Sep24DtoTests { } @Test - fun testTransactionResponseCoverage() { + fun `test to cover TransactionResponse`() { val tr = TransactionResponse() tr.getStatus_eta() tr.setStatus_eta(1) diff --git a/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt index f1c9d1f443..da3c74907b 100644 --- a/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt @@ -11,7 +11,7 @@ import org.stellar.anchor.api.exception.SepNotFoundException internal class ResourceJsonAssetServiceTest { @Test - fun getListAllAssets() { + fun `test assets listing`() { val rjas = ResourceJsonAssetService("test_assets.json") assertEquals(3, rjas.assets.getAssets().size) @@ -28,7 +28,7 @@ internal class ResourceJsonAssetServiceTest { } @Test - fun testJsonNotFound() { + fun `test asset JSON file not found`() { assertThrows { ResourceJsonAssetService("test_assets.json.bad") } assertThrows { ResourceJsonAssetService("not_found.json") } diff --git a/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt index dc32f5ab6b..9f5534d6e2 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt @@ -23,7 +23,7 @@ class AuthHelperTest { @ParameterizedTest @EnumSource(AuthType::class) - fun test_createAuthHeader(authType: AuthType) { + fun `test AuthHeader creation based on the AuthType`(authType: AuthType) { when (authType) { AuthType.JWT_TOKEN -> { // Mock calendar to guarantee the jwt token format diff --git a/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt index e481279d01..40b825cbe4 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt @@ -24,7 +24,7 @@ internal class JwtServiceTest { } @Test - fun testCodec() { + fun `test apply JWT encoding and decoding and make sure the original values are not changed`() { val appConfig = mockk() every { appConfig.jwtSecretKey } returns "jwt_secret" @@ -55,7 +55,7 @@ internal class JwtServiceTest { } @Test - fun testBadCipher() { + fun `make sure decoding bad cipher test throws an error`() { val appConfig = mockk() every { appConfig.jwtSecretKey } returns "jwt_secret" @@ -65,7 +65,7 @@ internal class JwtServiceTest { } @Test - fun testBadTokenAlgorithm() { + fun `make sure JwtService only decodes HS256`() { val appConfig = mockk() every { appConfig.jwtSecretKey } returns "jwt_secret" diff --git a/core/src/test/kotlin/org/stellar/anchor/auth/JwtTokenTest.kt b/core/src/test/kotlin/org/stellar/anchor/auth/JwtTokenTest.kt index d47e4d7cdd..4439886a89 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/JwtTokenTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/JwtTokenTest.kt @@ -11,7 +11,7 @@ class JwtTokenTest { private val expiresAt = issuedAt + 500 @Test - fun of() { + fun `test token creation`() { val token = JwtToken.of("iss", TEST_ACCOUNT, issuedAt, expiresAt, "", TEST_CLIENT_DOMAIN) assertEquals(TEST_ACCOUNT, token.account) assertEquals(TEST_CLIENT_DOMAIN, token.getClientDomain()) @@ -21,7 +21,7 @@ class JwtTokenTest { } @Test - fun of_accountMemo() { + fun `test the mapping of JWT fields`() { val accountMemo = "135689" val token = JwtToken.of("iss", "$TEST_ACCOUNT:$accountMemo", issuedAt, expiresAt, "", TEST_CLIENT_DOMAIN) @@ -34,7 +34,7 @@ class JwtTokenTest { } @Test - fun of_muxedAccount() { + fun `test the mux account mapping`() { val muxedAccount = "MA3X53JGZ5SLT733GNKH3CVV7RKCL4DXWCIZG2Y24HA24L6XNEHSQAAAAAAETFQC2JGGC" val token = JwtToken.of("iss", muxedAccount, issuedAt, expiresAt, "", TEST_CLIENT_DOMAIN) assertEquals("GA3X53JGZ5SLT733GNKH3CVV7RKCL4DXWCIZG2Y24HA24L6XNEHSQXT4", token.account) diff --git a/core/src/test/kotlin/org/stellar/anchor/dto/SepExceptionResponseTest.kt b/core/src/test/kotlin/org/stellar/anchor/dto/SepExceptionResponseTest.kt index c0c59ebc52..982c5a5d64 100644 --- a/core/src/test/kotlin/org/stellar/anchor/dto/SepExceptionResponseTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/dto/SepExceptionResponseTest.kt @@ -1,13 +1,13 @@ package org.stellar.anchor.dto +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.stellar.anchor.api.sep.SepExceptionResponse internal class SepExceptionResponseTest { @Test - fun test() { - val ser = SepExceptionResponse("") - ser.getError() - ser.setError("") + fun `test SepExceptionResponse creation`() { + val ser = SepExceptionResponse("Test Error Message") + assertEquals("Test Error Message", ser.getError()) } } diff --git a/core/src/test/kotlin/org/stellar/anchor/dto/sep31/Sep31PostTransactionRequestTest.kt b/core/src/test/kotlin/org/stellar/anchor/dto/sep31/Sep31PostTransactionRequestTest.kt index 7d6069b356..5ebcb0546d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/dto/sep31/Sep31PostTransactionRequestTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/dto/sep31/Sep31PostTransactionRequestTest.kt @@ -23,7 +23,7 @@ internal class Sep31PostTransactionRequestTest { }""" @Test - fun test() { + fun `test parsing JSON string to the Sep31PostTransactionRequest`() { val request = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) JSONAssert.assertEquals(postTxnJson, gson.toJson(request), false) } diff --git a/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt b/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt index 79d727ceb8..61a58ccb63 100644 --- a/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/dto/sep38/InfoResponseTest.kt @@ -27,7 +27,7 @@ class InfoResponseTest { } @Test - fun test_constructor() { + fun `test the InfoResponse construction`() { val infoResponse = InfoResponse(assets) assertEquals(3, infoResponse.assets.size) diff --git a/core/src/test/kotlin/org/stellar/anchor/filter/ApiKeyFilterTest.kt b/core/src/test/kotlin/org/stellar/anchor/filter/ApiKeyFilterTest.kt index e607001b4f..ed386b171f 100644 --- a/core/src/test/kotlin/org/stellar/anchor/filter/ApiKeyFilterTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/filter/ApiKeyFilterTest.kt @@ -40,7 +40,7 @@ internal class ApiKeyFilterTest { } @Test - fun testOptionsMethodDoesntNeedAuth() { + fun `test the OPTIONS Method does not need AUTH`() { every { request.method } returns "OPTIONS" apiKeyFilter.doFilter(request, response, mockFilterChain) @@ -49,7 +49,7 @@ internal class ApiKeyFilterTest { } @Test - fun testBadServletIsNotAccepted() { + fun `make sure bad servlet is not accepted`() { val mockServletRequest = mockk(relaxed = true) val mockServletResponse = mockk(relaxed = true) @@ -67,7 +67,7 @@ internal class ApiKeyFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testNoApiKeyReturnsForbidden(method: String) { + fun `make sure FORBIDDEN is returned when no X-Api-Key is not specified`(method: String) { every { request.method } returns method every { request.getHeader("X-Api-Key") } returns null @@ -81,7 +81,7 @@ internal class ApiKeyFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testNoBearerInAuthHeaderReturnsForbidden(method: String) { + fun `make sure FORBIDDEN is returned when no BEARER in the auth header`(method: String) { every { request.method } returns method every { request.getHeader("X-Api-Key") } returns "" @@ -95,7 +95,7 @@ internal class ApiKeyFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testMismatchingApiKeyReturnsForbidden(method: String) { + fun `make sure FORBIDDEN is returned when having mismatching api key`(method: String) { every { request.method } returns method every { request.getHeader("X-Api-Key") } returns "123" @@ -109,7 +109,7 @@ internal class ApiKeyFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testMatchingApiKeySucceeds(method: String) { + fun `test matching api key returns OK`(method: String) { every { request.method } returns method every { request.getHeader("X-Api-Key") } returns API_KEY diff --git a/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt b/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt index a2fbba1a0a..e3232240fb 100644 --- a/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt @@ -50,7 +50,7 @@ internal class JwtTokenFilterTest { } @Test - fun testBadServlet() { + fun `make sure bad servlet throws exception`() { val mockServletRequest = mockk(relaxed = true) val mockServletResponse = mockk(relaxed = true) @@ -64,7 +64,7 @@ internal class JwtTokenFilterTest { } @Test - fun testOptions() { + fun `test OPTIONS method works fine without auth header`() { every { request.method } returns "OPTIONS" sep10TokenFilter.doFilter(request, response, mockFilterChain) @@ -74,7 +74,7 @@ internal class JwtTokenFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testNoTokenForbidden(method: String) { + fun `make sure FORBIDDEN is returned when no token exists`(method: String) { every { request.method } returns method every { request.getHeader("Authorization") } returns null @@ -88,7 +88,7 @@ internal class JwtTokenFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testNoBearer(method: String) { + fun `make sure FORBIDDEN is returned when encounter an empty token`(method: String) { every { request.method } returns method every { request.getHeader("Authorization") } returns "" @@ -102,7 +102,7 @@ internal class JwtTokenFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testBearerSplitError(method: String) { + fun `make sure Bearer123 does not cause confusion and return FORBIDDEN`(method: String) { every { request.method } returns method every { request.getHeader("Authorization") } returns "Bearer123" @@ -116,7 +116,7 @@ internal class JwtTokenFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testValidationError(method: String) { + fun `make sure validate() exception returns FORBIDDEN and does not cause 500`(method: String) { every { request.method } returns method val mockFilter = spyk(sep10TokenFilter) every { mockFilter.validate(any()) } answers { throw Exception("Not validate") } @@ -131,7 +131,9 @@ internal class JwtTokenFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testNullToken(method: String) { + fun `make sure FORBIDDEN is returned when null token is decoded and does not cause 500 `( + method: String + ) { every { request.method } returns method val mockJwtService = spyk(jwtService) every { mockJwtService.decode(any()) } returns null @@ -147,7 +149,7 @@ internal class JwtTokenFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE"]) - fun testValidToken(method: String) { + fun `make sure a valid token returns OK`(method: String) { every { request.method } returns method val slot = slot() every { request.setAttribute(JWT_TOKEN, capture(slot)) } answers {} diff --git a/core/src/test/kotlin/org/stellar/anchor/filter/NoneFilterTest.kt b/core/src/test/kotlin/org/stellar/anchor/filter/NoneFilterTest.kt index ea1fcf3cbc..8cce4caabc 100644 --- a/core/src/test/kotlin/org/stellar/anchor/filter/NoneFilterTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/filter/NoneFilterTest.kt @@ -31,7 +31,7 @@ internal class NoneFilterTest { @ParameterizedTest @ValueSource(strings = ["GET", "PUT", "POST", "DELETE", "OPTIONS"]) - fun testAllPass(method: String) { + fun `test NoneFilter works without Authorization header`(method: String) { every { request.method } returns method every { request.getHeader("Authorization") } returns null diff --git a/core/src/test/kotlin/org/stellar/anchor/horizon/HorizonTest.kt b/core/src/test/kotlin/org/stellar/anchor/horizon/HorizonTest.kt index fb6e4a1344..67eca9b486 100644 --- a/core/src/test/kotlin/org/stellar/anchor/horizon/HorizonTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/horizon/HorizonTest.kt @@ -16,7 +16,7 @@ internal class HorizonTest { } @Test - fun testHorizon() { + fun `test the correctness of Horizon creation`() { val appConfig = mockk() every { appConfig.horizonUrl } returns TEST_HORIZON_URI every { appConfig.stellarNetworkPassphrase } returns TEST_HORIZON_PASSPHRASE diff --git a/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt b/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt index 3cc293e096..55d01d6a9d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt @@ -2,31 +2,37 @@ package org.stellar.anchor.model import io.mockk.every import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.stellar.anchor.api.sep.SepTransactionStatus import org.stellar.anchor.sep24.PojoSep24Transaction import org.stellar.anchor.sep24.Sep24TransactionBuilder import org.stellar.anchor.sep24.Sep24TransactionStore internal class Sep24TransactionTest { @Test - fun testStatusCoverage() { - SepTransactionStatus.COMPLETED.description - } - - @Test - fun testBuilder() { + fun `test the Sep24TransactionBuilder builds the Sep24Transaction correctly`() { val store = mockk() every { store.newInstance() } returns PojoSep24Transaction() val builder = Sep24TransactionBuilder(store) - builder - .transactionId("txnId") - .completedAt(10) - .withdrawAnchorAccount("account") - .memo("memo") - .amountFee("20") - .amountInAsset("USDC_In") - .amountOutAsset("USDC_Out") - .amountFeeAsset("USDC_Fee") + var txn = + builder + .transactionId("txnId") + .completedAt(10) + .withdrawAnchorAccount("account") + .memo("memo") + .amountFee("20") + .amountInAsset("USDC_In") + .amountOutAsset("USDC_Out") + .amountFeeAsset("USDC_Fee") + .build() + + assertEquals(txn.transactionId, "txnId") + assertEquals(txn.completedAt, 10) + assertEquals(txn.withdrawAnchorAccount, "account") + assertEquals(txn.memo, "memo") + assertEquals(txn.amountFee, "20") + assertEquals(txn.amountInAsset, "USDC_In") + assertEquals(txn.amountOutAsset, "USDC_Out") + assertEquals(txn.amountFeeAsset, "USDC_Fee") } } diff --git a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt index 765c1485ba..c9a3555e15 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -11,7 +11,7 @@ import org.stellar.anchor.config.Sep1Config @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class Sep1ServiceTest { @Test - fun testToml() { + fun `simple test if the toml file is read`() { val sep1Config = mockk() every { sep1Config.stellarFile } returns "test_stellar.toml" diff --git a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt index a93fcde641..18c69462b1 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -32,7 +32,6 @@ import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.exception.SepNotAuthorizedException import org.stellar.anchor.api.exception.SepValidationException import org.stellar.anchor.api.sep.sep10.ChallengeRequest -import org.stellar.anchor.api.sep.sep10.ChallengeRequestTest import org.stellar.anchor.api.sep.sep10.ValidationRequest import org.stellar.anchor.auth.JwtService import org.stellar.anchor.config.AppConfig @@ -118,7 +117,7 @@ internal class Sep10ServiceTest { } @Test - fun test_challengeWithExistentAccount_multisig_andClientDomain() { + fun `test the challenge with existent account, multisig, and client domain`() { // 1 ------ Create Test Transaction // serverKP does not exist in the network. @@ -209,7 +208,7 @@ internal class Sep10ServiceTest { } @Test - fun test_challengeWithNonExistentAccount_andClientDomain() { + fun `test challenge with non existent account and client domain`() { // 1 ------ Create Test Transaction // serverKP does not exist in the network. @@ -269,7 +268,7 @@ internal class Sep10ServiceTest { } @Test - fun test_challengeWithExistentAccount_multisigWithInvalidEdDSAPublicKey_andClientDomain() { + fun `test challenge with existent account multisig with invalid ed dsa public key and client domain`() { // 1 ------ Mock client account and its response from horizon // The public key of the client that exists thanks to a mockk // GDFWZYGUNUFW4H3PP3DSNGTDFBUHO6NUFPQ6FAPMCKEJ6EHDKX2CV2IM @@ -345,9 +344,15 @@ internal class Sep10ServiceTest { @CsvSource( value = ["true,test.client.stellar.org", "false,test.client.stellar.org", "false,null"] ) - fun testOkCreateChallenge(clientAttributionRequired: String, clientDomain: String) { + fun `test create challenge ok`(clientAttributionRequired: String, clientDomain: String) { every { sep10Config.isClientAttributionRequired } returns clientAttributionRequired.toBoolean() - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() cr.clientDomain = if (clientDomain == "null") null else clientDomain val challengeResponse = sep10Service.createChallenge(cr) @@ -392,7 +397,7 @@ internal class Sep10ServiceTest { } @Test - fun testOkValidateChallengeClientAccountOnNetwork() { + fun `test validate challenge when client account is on Stellar network`() { val vr = ValidationRequest() vr.transaction = createTestChallenge("", false) @@ -410,7 +415,7 @@ internal class Sep10ServiceTest { } @Test - fun testValidateChallengeWithClientDomain() { + fun `test validate challenge with client domain`() { val accountResponse = spyk(AccountResponse(clientKeyPair.accountId, 1)) val signers = arrayOf( @@ -446,7 +451,7 @@ internal class Sep10ServiceTest { } @Test - fun testOkValidateChallengeClientAccountNotOnNetwork() { + fun `test validate challenge when client account is not on network`() { val vr = ValidationRequest() vr.transaction = createTestChallenge("", false) @@ -460,7 +465,7 @@ internal class Sep10ServiceTest { @Suppress("CAST_NEVER_SUCCEEDS") @Test - fun testErrValidateChallengeBadRequest() { + fun `Test validate challenge with bad request`() { assertThrows { sep10Service.validateChallenge(null as? ValidationRequest) } @@ -471,8 +476,14 @@ internal class Sep10ServiceTest { } @Test - fun testErrBadHomeDomainCreateChallenge() { - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + fun `Test bad home domain create challenge failure`() { + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() cr.homeDomain = "bad.homedomain.com" assertThrows { sep10Service.createChallenge(cr) } @@ -480,9 +491,15 @@ internal class Sep10ServiceTest { @ParameterizedTest @MethodSource("homeDomains") - fun testClientDomainFailure(homeDomain: String?) { + fun `test client domain failures`(homeDomain: String?) { every { sep10Config.isClientAttributionRequired } returns true - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() cr.homeDomain = homeDomain cr.clientDomain = null @@ -500,9 +517,15 @@ internal class Sep10ServiceTest { } @Test - fun testBadAccount() { + fun `test createChallenge() with bad account`() { every { sep10Config.isClientAttributionRequired } returns false - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() cr.account = "GXXX" assertThrows { sep10Service.createChallenge(cr) } @@ -510,15 +533,15 @@ internal class Sep10ServiceTest { @ParameterizedTest @ValueSource(strings = ["ABC", "12AB", "-1", "0", Integer.MIN_VALUE.toString()]) - fun testBadMemo(badMemo: String) { + fun `test createChallenge() with bad memo`(badMemo: String) { every { sep10Config.isClientAttributionRequired } returns false val cr = - ChallengeRequest.of( - ChallengeRequestTest.TEST_ACCOUNT, - TEST_MEMO, - TEST_HOME_DOMAIN, - TEST_CLIENT_DOMAIN - ) + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() cr.account = TEST_ACCOUNT cr.memo = badMemo @@ -526,7 +549,7 @@ internal class Sep10ServiceTest { } @Test - fun testGetClientAccountIdFailure() { + fun `test getClientAccountId failure`() { mockkStatic(NetUtil::class) every { NetUtil.fetch(any()) } returns " NETWORK_PASSPHRASE=\"Public Global Stellar Network ; September 2015\"\n" @@ -543,9 +566,15 @@ internal class Sep10ServiceTest { } @Test - fun testAppConfigBadHostURL() { + fun `test appConfig with bad hostUrl`() { every { sep10Config.isClientAttributionRequired } returns false - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() every { appConfig.hostUrl } returns "This is bad URL" @@ -553,9 +582,15 @@ internal class Sep10ServiceTest { } @Test - fun testCreateChallengeSigningError() { + fun `test createChallenge signing error`() { every { sep10Config.isClientAttributionRequired } returns false - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, TEST_CLIENT_DOMAIN) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(TEST_CLIENT_DOMAIN) + .build() every { Sep10Challenge.newChallenge(any(), any(), any(), any(), any(), any(), any(), any(), any()) @@ -565,10 +600,16 @@ internal class Sep10ServiceTest { } @Test - fun testRequireKnownOmnibusAccount() { + fun `test createChallenge() ok when isRequireKnownOmnibusAccount is enabled`() { every { sep10Config.isRequireKnownOmnibusAccount } returns true every { sep10Config.omnibusAccountList } returns listOf(TEST_ACCOUNT) - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, null) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(null) + .build() assertDoesNotThrow { sep10Service.createChallenge(cr) } verify(exactly = 1) { sep10Config.isRequireKnownOmnibusAccount } @@ -576,11 +617,17 @@ internal class Sep10ServiceTest { } @Test - fun testRequireKnownOmnibusAccountDisabled() { + fun `Test createChallenge() when isRequireKnownOmnibusAccount is not enabled`() { every { sep10Config.isRequireKnownOmnibusAccount } returns false every { sep10Config.omnibusAccountList } returns listOf("G321E23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP") - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, null) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(null) + .build() assertDoesNotThrow { sep10Service.createChallenge(cr) } verify(exactly = 1) { sep10Config.isRequireKnownOmnibusAccount } @@ -588,11 +635,17 @@ internal class Sep10ServiceTest { } @Test - fun testRequireKnownOmnibusAccountUnknownAccount() { + fun `test createChallenge() failure when isRequireKnownOmnibusAccount is enabled and account mis-match`() { every { sep10Config.isRequireKnownOmnibusAccount } returns true every { sep10Config.omnibusAccountList } returns listOf("G321E23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP") - val cr = ChallengeRequest.of(TEST_ACCOUNT, TEST_MEMO, TEST_HOME_DOMAIN, null) + val cr = + ChallengeRequest.builder() + .account(TEST_ACCOUNT) + .memo(TEST_MEMO) + .homeDomain(TEST_HOME_DOMAIN) + .clientDomain(null) + .build() val ex = assertThrows { sep10Service.createChallenge(cr) } verify(exactly = 1) { sep10Config.isRequireKnownOmnibusAccount } @@ -611,7 +664,7 @@ internal class Sep10ServiceTest { "http://test.stellar.org:9800,test.stellar.org:9800", ] ) - fun testGetDomainFromURI(testUri: String, compareDomain: String) { + fun `test getDomain from uri`(testUri: String, compareDomain: String) { val domain = sep10Service.getDomainFromURI(testUri) assertEquals(domain, compareDomain) } 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 e40e9403cd..9b1b400fd0 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt @@ -59,7 +59,7 @@ class Sep12ServiceTest { } @Test - fun test_validateRequestAndTokenAccounts() { + fun `test validate request and token accounts`() { val mockRequestBase = mockk(relaxed = true) // request account fails if not the same as token account @@ -91,7 +91,7 @@ class Sep12ServiceTest { } @Test - fun test_validateRequestAndTokenMemos() { + fun `test validate request and token memos`() { val mockRequestBase = mockk(relaxed = true) // If the token doesn't have a memo nor a Muxed account id, does not fail for empty request memo @@ -130,7 +130,7 @@ class Sep12ServiceTest { } @Test - fun test_updateRequestMemoAndMemoType() { + fun `test update request memo and memo type`() { val mockRequestBase = mockk(relaxed = true) // if the request doesn't have any kind of memo, make sure the memo type is empty and return @@ -183,7 +183,7 @@ class Sep12ServiceTest { } @Test - fun test_putCustomer() { + fun `Test put customer request ok`() { // mock `PUT {callbackApi}/customer` response val callbackApiPutRequestSlot = slot() val mockCallbackApiPutCustomerResponse = Sep12PutCustomerResponse() @@ -224,7 +224,7 @@ class Sep12ServiceTest { } @Test - fun test_getCustomer() { + fun `Test get customer request ok`() { // mock `GET {callbackApi}/customer` response val callbackApiGetRequestSlot = slot() val mockCallbackApiGetCustomerResponse = Sep12GetCustomerResponse() @@ -268,7 +268,7 @@ class Sep12ServiceTest { } @Test - fun test_deleteCustomer_validation() { + fun `test delete customer validation`() { every { customerIntegration.deleteCustomer(any()) } just Runs // PART 1 - account without memo @@ -328,7 +328,7 @@ class Sep12ServiceTest { } @Test - fun test_deleteCustomer() { + fun `test delete customer`() { // mock callbackApi customer integration val deleteCustomerIdSlot = slot() every { customerIntegration.deleteCustomer(capture(deleteCustomerIdSlot)) } just Runs 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 c5bc09c4b7..ff57c11251 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -91,7 +91,7 @@ internal class Sep24ServiceTest { } @Test - fun testWithdraw() { + fun `test withdraw ok`() { val slotTxn = slot() every { txnStore.save(capture(slotTxn)) } returns null @@ -156,7 +156,7 @@ internal class Sep24ServiceTest { } @Test - fun testWithdrawalNoTokenNoRequest() { + fun `test withdrawal with no token and no request failure`() { assertThrows { sep24Service.withdraw("/sep24/withdrawal", null, createTestTransactionRequest()) } @@ -167,7 +167,7 @@ internal class Sep24ServiceTest { } @Test - fun testWithdrawalBadRequest() { + fun `test withdraw with bad requests`() { assertThrows { val request = createTestTransactionRequest() request.remove("asset_code") @@ -215,7 +215,7 @@ internal class Sep24ServiceTest { @ParameterizedTest @ValueSource(strings = ["true", "false"]) - fun testDeposit(claimable_balance_supported: String) { + fun `test deposit`(claimable_balance_supported: String) { val slotTxn = slot() every { txnStore.save(capture(slotTxn)) } returns null @@ -257,7 +257,7 @@ internal class Sep24ServiceTest { } @Test - fun testDepositNoTokenNoRequest() { + fun `test deposit with no token and no request`() { assertThrows { sep24Service.deposit("/sep24/deposit", null, createTestTransactionRequest()) } @@ -268,7 +268,7 @@ internal class Sep24ServiceTest { } @Test - fun testDepositBadRequest() { + fun `test deposit with bad requests`() { assertThrows { val request = createTestTransactionRequest() request.remove("asset_code") @@ -316,7 +316,7 @@ internal class Sep24ServiceTest { @ParameterizedTest @ValueSource(strings = ["deposit", "withdrawal"]) - fun testFindTransactions(kind: String) { + fun `test find transactions`(kind: String) { every { txnStore.findTransactions(TEST_ACCOUNT, any(), any()) } returns createTestTransactions(kind) val gtr = @@ -339,7 +339,7 @@ internal class Sep24ServiceTest { @ParameterizedTest @ValueSource(strings = ["deposit", "withdrawal"]) - fun testFindTransactionsValidationError(kind: String) { + fun `test find transactions with validation error`(kind: String) { assertThrows { val gtr = GetTransactionsRequest.of(TEST_ASSET, kind, 10, "2021-12-20T19:30:58+00:00", "1", "en-US") @@ -362,7 +362,7 @@ internal class Sep24ServiceTest { @ParameterizedTest @ValueSource(strings = ["deposit", "withdrawal"]) - fun testFindTransaction(kind: String) { + fun `test find one transaction`(kind: String) { every { txnStore.findByTransactionId(any()) } returns createTestTransaction(kind) var gtr = GetTransactionRequest(TEST_TRANSACTION_ID_0, null, null, "en-US") @@ -392,7 +392,7 @@ internal class Sep24ServiceTest { @ParameterizedTest @ValueSource(strings = ["deposit", "withdrawal"]) - fun testFindTransactionValidationError(kind: String) { + fun `test find transaction validation error`(kind: String) { assertThrows { val gtr = GetTransactionRequest(TEST_TRANSACTION_ID_0, null, null, "en-US") sep24Service.findTransaction(null, gtr) @@ -422,7 +422,7 @@ internal class Sep24ServiceTest { } @Test - fun testGetInfo() { + fun `test GET info`() { val response = sep24Service.info assertEquals(3, response.deposit.size) @@ -433,7 +433,7 @@ internal class Sep24ServiceTest { } @Test - fun testMakeMemo() { + fun `test make memo`() { var memo = makeMemo("this_is_a_test_memo", "text") assertTrue(memo is MemoText) memo = makeMemo("1234", "id") @@ -443,7 +443,7 @@ internal class Sep24ServiceTest { } @Test - fun testLang() { + fun `test lang`() { val slotTxn = slot() every { txnStore.save(capture(slotTxn)) } returns null diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt index 6b7c76f25f..3548a10d3c 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt @@ -34,7 +34,7 @@ class RefundPaymentTest { } @Test - fun test_toSep31RefundPayment() { + fun `test to sep31 refund payment`() { // mock the SEP-31 RefundPayment object val mockRefundPayment = PojoSep31RefundPayment() mockRefundPayment.id = "A" @@ -51,7 +51,7 @@ class RefundPaymentTest { } @Test - fun test_toPlatformApiRefundPayment() { + fun `test to platform api refund payment`() { // mock the SEP-31 RefundPayment object val mockRefundPayment = PojoSep31RefundPayment() mockRefundPayment.id = "A" @@ -75,7 +75,7 @@ class RefundPaymentTest { } @Test - fun test_of() { + fun `test PlatformApi Refund object creation`() { // mock the PlatformApi RefundPayment object val mockPlatformApiRefundPayment = org.stellar.anchor.api.shared.RefundPayment.builder() diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt index 922a7dc000..deee947e50 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt @@ -37,7 +37,7 @@ class RefundsTest { } @Test - fun test_toSep31TransactionResponseRefunds() { + fun `test conversion to sep31 transaction response refunds`() { // mock the SEP-31 Refunds object val mockRefundPayment1 = PojoSep31RefundPayment() mockRefundPayment1.id = "A" @@ -80,7 +80,7 @@ class RefundsTest { } @Test - fun test_toPlatformApiRefund() { + fun `test conversion to CallbackApi Refund`() { // mock the SEP-31 Refunds object val mockRefundPayment1 = PojoSep31RefundPayment() mockRefundPayment1.id = "A" @@ -129,8 +129,8 @@ class RefundsTest { } @Test - fun test_of() { - // mock the PlatformApi Refund + fun `test CallbackApi Refund creation`() { + // mock the CallbackApi Refund val mockPlatformApiRefund = org.stellar.anchor.api.shared.Refund.builder() .amountRefunded(Amount("100", fiatUSD)) diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt index f9dcb61fcd..41e9c63f91 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt @@ -10,7 +10,7 @@ import org.stellar.anchor.api.exception.BadRequestException class Sep31HelperTest { @Test - fun test_allAmountAvailable() { + fun `test all amount available`() { val txn = PojoSep31Transaction() txn.amountIn = "100" assertFalse(Sep31Helper.allAmountAvailable(txn)) @@ -40,7 +40,7 @@ class Sep31HelperTest { "expired", "error"] ) - fun test_validateStatus(status: String) { + fun `test validate status`(status: String) { val txn = PojoSep31Transaction() txn.status = status Sep31Helper.validateStatus(txn) @@ -49,7 +49,7 @@ class Sep31HelperTest { @ParameterizedTest @NullSource @ValueSource(strings = ["Error", "erroR", ""]) - fun test_validateStatus_failure(status: String?) { + fun `test validate status failure`(status: String?) { val txn = PojoSep31Transaction() txn.status = status val ex = assertThrows { Sep31Helper.validateStatus(txn) } 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 d4106e9fe0..9cc364a29e 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -327,7 +327,7 @@ class Sep31ServiceTest { } @Test - fun test_updateTxAmountsWhenNoQuoteWasUsed() { + fun `test update transaction amounts when no quote was used`() { Context.get().setTransaction(txn) Context.get().setRequest(request) Context.get().setFee(fee) @@ -347,7 +347,7 @@ class Sep31ServiceTest { } @Test - fun test_quotesSupportedAndRequiredValidation() { + fun `test quotes supported and required validation`() { val assetServiceQuotesNotSupported: AssetService = ResourceJsonAssetService( "test_assets.json.quotes_required_but_not_supported", @@ -373,7 +373,7 @@ class Sep31ServiceTest { } @Test - fun test_updateTxAmountsBasedOnQuote() { + fun `test update tx amounts based on quote`() { Context.get().setTransaction(txn) Context.get().setRequest(request) Context.get().setFee(fee) @@ -402,7 +402,7 @@ class Sep31ServiceTest { } @Test - fun test_getTransaction() { + fun `test GET transaction`() { assertThrows { sep31Service.getTransaction(null) } assertThrows { sep31Service.getTransaction("") } @@ -474,7 +474,7 @@ class Sep31ServiceTest { } @Test - fun test_patchTransaction() { + fun `test PATCH transaction ok`() { txn.status = "pending_transaction_info_update" every { txnStore.findByTransactionId("a2392add-87c9-42f0-a5c1-5f1728030b68") } returns txn sep31Service.patchTransaction(patchRequest) @@ -482,7 +482,7 @@ class Sep31ServiceTest { } @Test - fun test_patchTransaction_failure() { + fun `test PATCH transaction failure`() { val ex1 = assertThrows { sep31Service.patchTransaction(null) } assertEquals("request cannot be null", ex1.message) @@ -515,7 +515,7 @@ class Sep31ServiceTest { } @Test - fun test_postTransaction_failure() { + fun `test POST transaction failures`() { val jwtToken = TestHelper.createJwtToken() // missing asset code @@ -699,7 +699,7 @@ class Sep31ServiceTest { } @Test - fun test_postTransaction_withQuote() { + fun `test POST transaction with quote`() { val tomorrow = Instant.now().plus(1, ChronoUnit.DAYS) quote.expiresAt = tomorrow quote.id = "my_quote_id" @@ -862,7 +862,7 @@ class Sep31ServiceTest { } @Test - fun test_postTransaction_withoutQuote_quoteRequired() { + fun `test POST transaction without quote and quote is required`() { Context.get().setAsset(asset) val senderId = "d2bd1412-e2f6-4047-ad70-a1a2f133b25c" val receiverId = "137938d4-43a7-4252-a452-842adcee474c" @@ -900,7 +900,7 @@ class Sep31ServiceTest { } @Test - fun test_postTransaction_quoteNotSupported() { + fun `test post transaction when quote is not supported`() { every { sep31DepositInfoGenerator.generate(any()) } returns Sep31DepositInfo("GA7FYRB5VREZKOBIIKHG5AVTPFGWUBPOBF7LTYG4GTMFVIOOD2DWAL7I", "123456", "id") @@ -984,7 +984,7 @@ class Sep31ServiceTest { """.trimIndent() @Test - fun test_info_response() { + fun `test INFO response`() { val info = sep31Service.info val gotJpyc = info.receive.get("JPYC")!! val gotUsdc = info.receive.get("USDC")!! @@ -997,7 +997,8 @@ class Sep31ServiceTest { } @Test - fun test_validateRequiredFields() { + fun `test validate required fields`() { + Context.reset() val ex1 = assertThrows { sep31Service.validateRequiredFields() } assertEquals("Missing asset information.", ex1.message) @@ -1030,7 +1031,7 @@ class Sep31ServiceTest { } @Test - fun test_updateFee() { + fun `Test update fee ok`() { val jwtToken = TestHelper.createJwtToken() Context.get().setRequest(request) Context.get().setJwtToken(jwtToken) @@ -1060,7 +1061,7 @@ class Sep31ServiceTest { } @Test - fun test_updateFee_failure() { + fun `test update fee failure`() { val jwtToken = TestHelper.createJwtToken() Context.get().setRequest(request) Context.get().setJwtToken(jwtToken) diff --git a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt index 70940b952f..6c4b026117 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -82,7 +82,7 @@ class Sep38ServiceTest { } @Test - fun test_getInfo() { + fun `test GET info`() { val infoResponse = sep38Service.getInfo() assertEquals(3, infoResponse.assets.size) @@ -133,7 +133,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrices_failure() { + fun `Test GET prices failure`() { // empty rateIntegration should throw an error var ex: AnchorException = assertThrows { sep38Service.getPrices(null, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) @@ -192,7 +192,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrices_minimumParameters() { + fun `test get prices with minimum parameters`() { // mock rate integration val mockRateIntegration = mockk() val getRateReq1 = @@ -233,7 +233,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrices_allParameters() { + fun `test get prices with all parameters`() { // mock rate integration val mockRateIntegration = mockk() val getRateReq1 = @@ -278,7 +278,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrices_filterWithBuyDeliveryMethod() { + fun `Test get prices filter with buy delivery method`() { // mock rate integration val mockRateIntegration = mockk() val getRateReq1 = @@ -311,7 +311,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrice_failure() { + fun `test GET price failure`() { var getPriceRequestBuilder = Sep38GetPriceRequest.builder() // empty rateIntegration should throw an error @@ -465,7 +465,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrice_minimumParametersWithSellAmount() { + fun `test GET price with minimum parameters and sell amount`() { val mockFee = mockSellAssetFee(fiatUSD) // mock rate integration @@ -511,7 +511,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrice_minimumParametersWithBuyAmount() { + fun `test get price with minimum parameters and buy amount`() { val mockFee = mockSellAssetFee(fiatUSD) // mock rate integration @@ -557,7 +557,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrice_allParametersWithSellAmount() { + fun `test GET price all parameters with sell amount`() { val mockFee = mockSellAssetFee(fiatUSD) // mock rate integration @@ -607,7 +607,7 @@ class Sep38ServiceTest { } @Test - fun test_getPrice_allParametersWithBuyAmount() { + fun `test GET price all parameters with buy amount`() { val mockFee = mockSellAssetFee(fiatUSD) // mock rate integration @@ -658,7 +658,7 @@ class Sep38ServiceTest { } @Test - fun test_postQuote_failure() { + fun `test POST quote failures`() { // empty rateIntegration should throw an error var ex: AnchorException = assertThrows { sep38Service.postQuote(null, Sep38PostQuoteRequest.builder().build()) @@ -991,7 +991,7 @@ class Sep38ServiceTest { } @Test - fun test_postQuote_minimumParametersWithSellAmount() { + fun `test POST quote with minimum parameters and sell amount`() { val mockFee = mockSellAssetFee(fiatUSD) // mock rate integration @@ -1102,7 +1102,7 @@ class Sep38ServiceTest { } @Test - fun test_postQuote_minimumParametersWithBuyAmount() { + fun `test POST quote with minimum parameters and buy amount`() { val mockFee = mockSellAssetFee(fiatUSD) // mock rate integration @@ -1212,7 +1212,7 @@ class Sep38ServiceTest { } @Test - fun test_postQuote_allParametersWithSellAmount() { + fun `test POST quote with all parameters and sell amount`() { val mockFee = mockSellAssetFee(fiatUSD) val now = Instant.now() val tomorrow = now.plus(1, ChronoUnit.DAYS) @@ -1331,7 +1331,7 @@ class Sep38ServiceTest { } @Test - fun test_postQuote_allParametersWithBuyAmount() { + fun `Test POST quote with all parameters and buy amount`() { val mockFee = mockSellAssetFee(fiatUSD) val now = Instant.now() val tomorrow = now.plus(1, ChronoUnit.DAYS) @@ -1450,7 +1450,7 @@ class Sep38ServiceTest { } @Test - fun test_getQuote_failure() { + fun `test GET quote failure`() { // empty sep38QuoteStore should throw an error var ex: AnchorException = assertThrows { sep38Service.getQuote(null, null) } assertInstanceOf(ServerErrorException::class.java, ex) @@ -1535,7 +1535,7 @@ class Sep38ServiceTest { } @Test - fun test_getQuote() { + fun `Test GET quote`() { val mockFee = mockSellAssetFee(fiatUSD) // mocked quote store diff --git a/core/src/test/kotlin/org/stellar/anchor/util/DateUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/DateUtilTest.kt index 20740289b0..5cf0d0464d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/DateUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/DateUtilTest.kt @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test internal class DateUtilTest { @Test - fun testISO8601() { + fun `test fromISO8601UTC`() { val t1 = System.currentTimeMillis() / 1000 val t2 = DateUtil.fromISO8601UTC(DateUtil.toISO8601UTC(t1)) assert(t1 == t2) diff --git a/core/src/test/kotlin/org/stellar/anchor/util/FileUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/FileUtilTest.kt index 3f1ba4e7a8..25f000d023 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/FileUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/FileUtilTest.kt @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test internal class FileUtilTest { @Test - fun testGetResourceFileAsString() { + fun `test getRourceFileAsString() returns correct value`() { val value = FileUtil.getResourceFileAsString("test_resource.txt") assert(value.equals("test_resource.txt")) } diff --git a/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt index 09bc30e576..7c3cb063a3 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt @@ -2,8 +2,11 @@ package org.stellar.anchor.util import io.mockk.* import io.mockk.impl.annotations.MockK -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance import org.slf4j.Logger import org.stellar.anchor.Constants.Companion.TEST_HOST_URL import org.stellar.anchor.Constants.Companion.TEST_JWT_SECRET @@ -45,7 +48,7 @@ internal class LogTest { val wantTestPIIJson = """{"fieldNoPII":"no secret"}""" @Test - fun testMessage() { + fun `test log messages`() { Log.error("Hello") verify { logger.error("Hello") } @@ -63,7 +66,7 @@ internal class LogTest { } @Test - fun testMessageJson() { + fun `test log messages with JSON format`() { val detail = TestBeanPII() Log.error("Hello", detail) @@ -110,7 +113,7 @@ internal class LogTest { } @Test - fun testErrorEx() { + fun `test errorEx`() { Log.errorEx(Exception("mock exception")) verify(exactly = 1) { logger.error(any()) } @@ -123,7 +126,7 @@ internal class LogTest { } @Test - fun testShorter() { + fun `test shorter string conversion`() { assertNull(shorter(null)) assertEquals(shorter("123"), "123") assertEquals(shorter(""), "") @@ -133,7 +136,7 @@ internal class LogTest { } @Test - fun testGetLogger() { + fun `test getLogger`() { unmockkAll() val logger = Log.getLogger() assertNotNull(logger) diff --git a/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt index bc46a6b545..b152ce64e8 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/MathHelperTest.kt @@ -13,19 +13,19 @@ class MathHelperTest { @CsvSource( value = [",", "0,0", "0.1,0.1000000", "1,1", "1,1.000000", "-1,-1", "-1,-1.0000000", "500,5e2"] ) - fun test_equalsAsDecimals_true(valueA: String?, valueB: String?) { + fun `test equalsAsDecimals true`(valueA: String?, valueB: String?) { assertTrue(MathHelper.equalsAsDecimals(valueA, valueB)) } @ParameterizedTest @CsvSource(value = ["0,-0.0000001", "-0.1,0.1000000", "0,1.000000", "0,", ",0"]) - fun test_equalsAsDecimals_false(valueA: String?, valueB: String?) { + fun `test equalsAsDecimals false`(valueA: String?, valueB: String?) { assertFalse(MathHelper.equalsAsDecimals(valueA, valueB)) } @ParameterizedTest @CsvSource(value = ["a,a", "1,a", "a,1"]) - fun test_equalsAsDecimals_throws(valueA: String, valueB: String) { + fun `test equalsAsDecimals throws`(valueA: String, valueB: String) { val ex: Exception = assertThrows { MathHelper.equalsAsDecimals(valueA, valueB) } assertInstanceOf(NumberFormatException::class.java, ex) assertEquals( diff --git a/core/src/test/kotlin/org/stellar/anchor/util/MemoHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/MemoHelperTest.kt index 55dd9a4f74..d312feec53 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/MemoHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/MemoHelperTest.kt @@ -9,7 +9,7 @@ import org.stellar.sdk.xdr.MemoType internal class MemoHelperTest { @Test - fun testMakeMemoError() { + fun `Test makeMemo error`() { assertThrows { MemoHelper.makeMemo("memo", "bad_type") } assertThrows { MemoHelper.makeMemo("bad_number", "id") } @@ -26,7 +26,7 @@ internal class MemoHelperTest { } @Test - fun test_memoHashConversion() { + fun `test memo hash conversion`() { val wantHex = "39623738663066612d393366392d343139382d386439332d6537366664303834" val gotHex = MemoHelper.convertBase64ToHex("OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=") assertEquals(wantHex, gotHex) diff --git a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt index a120b96f39..2ac1534f46 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt @@ -28,7 +28,7 @@ internal class NetUtilTest { } @Test - fun testFetch() { + fun `test fetch()`() { mockkStatic(NetUtil::class) every { NetUtil.getCall(any()) } returns mockCall every { mockCall.execute() } returns mockResponse @@ -44,7 +44,7 @@ internal class NetUtilTest { } @Test - fun testFetchExcpeption() { + fun `test fetch() throws exception`() { mockkStatic(NetUtil::class) every { NetUtil.getCall(any()) } returns mockCall every { mockCall.execute() } returns mockResponse @@ -56,7 +56,7 @@ internal class NetUtilTest { } @Test - fun testGetCall() { + fun `test getCall()`() { val request = OkHttpUtil.buildGetRequest("https://www.stellar.org") assertNotNull(NetUtil.getCall(request)) } diff --git a/core/src/test/kotlin/org/stellar/anchor/util/OkHttpUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/OkHttpUtilTest.kt index 479d5c3d29..daafaaf3e5 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/OkHttpUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/OkHttpUtilTest.kt @@ -11,13 +11,13 @@ internal class OkHttpUtilTest { val TEST_JSON = "{}" } @Test - fun testBuildClient() { + fun `test buildClient() ok`() { val client = OkHttpUtil.buildClient() assertFalse(client.retryOnConnectionFailure) } @Test - fun testBuildJsonPostRequest() { + fun `test buildJsonPostRequest() ok`() { val request = OkHttpUtil.buildJsonPostRequest(TEST_URL, TEST_JSON) assertEquals("www.stellar.org", request.url.host) assertEquals(OkHttpUtil.APPLICATION_JSON_CHARSET_UTF_8, request.header("Content-Type")) @@ -25,7 +25,7 @@ internal class OkHttpUtilTest { } @Test - fun testBuildJsonPutRequest() { + fun `test buildJsonPutRequest() ok`() { val request = OkHttpUtil.buildJsonPutRequest(TEST_URL, TEST_JSON) assertEquals("www.stellar.org", request.url.host) assertEquals(OkHttpUtil.APPLICATION_JSON_CHARSET_UTF_8, request.header("Content-Type")) @@ -33,14 +33,14 @@ internal class OkHttpUtilTest { } @Test - fun testBuildGetRequest() { + fun `test buildGetRequest() ok`() { val request = OkHttpUtil.buildGetRequest(TEST_URL) assertEquals("www.stellar.org", request.url.host) assertEquals("GET", request.method) } @Test - fun testBuildJsonRequestBody() { + fun `test buildJsonRequestBody() ok`() { val requestBody = OkHttpUtil.buildJsonRequestBody(TEST_JSON) assertEquals(TYPE_JSON, requestBody.contentType()) } diff --git a/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.java b/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.java deleted file mode 100644 index ea30f0967b..0000000000 --- a/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.stellar.anchor.util; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.stellar.anchor.util.PropertyUtil.set; - -import java.util.Optional; -import lombok.Data; -import org.junit.jupiter.api.Test; - -class PropertyUtilTest { - @Data - public static class A { - String value; - B b; - } - - @Data - public static class B { - String value; - C c; - } - - @Data - public static class C { - String value; - } - - @Test - void testSet() throws ReflectiveOperationException { - A a = new A(); - assertEquals(Optional.empty(), PropertyUtil.get(a, "value")); - set(a, "value", "value-a"); - assertEquals("value-a", PropertyUtil.get(a, "value").orElse(null)); - - assertEquals(Optional.empty(), PropertyUtil.get(a, "b.value")); - set(a, "b.value", "value-b"); - assertEquals("value-b", PropertyUtil.get(a, "b.value").orElse(null)); - - assertEquals(Optional.empty(), PropertyUtil.get(a, "b.c.value")); - set(a, "b.c.value", "value-c"); - assertEquals("value-c", PropertyUtil.get(a, "b.c.value").orElse(null)); - } - - @Test - void testNotExists() throws ReflectiveOperationException { - A a = new A(); - assertEquals(Optional.empty(), PropertyUtil.get(a, "value")); - assertEquals(Optional.empty(), PropertyUtil.get(a, "b")); - assertEquals(Optional.empty(), PropertyUtil.get(a, "b.c")); - assertEquals(Optional.empty(), PropertyUtil.get(a, "not_found")); - } -} diff --git a/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt new file mode 100644 index 0000000000..7844b201ce --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt @@ -0,0 +1,48 @@ +package org.stellar.anchor.util + +import java.util.* +import lombok.Data +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +internal class PropertyUtilTest { + @Data + class A { + var value: String? = null + var b: B? = null + } + + @Data + class B { + var value: String? = null + var c: C? = null + } + + @Data + class C { + var value: String? = null + } + + @Test + fun `test set property`() { + val a = A() + assertEquals(Optional.empty(), PropertyUtil.get(a, "value")) + PropertyUtil.set(a, "value", "value-a") + assertEquals("value-a", PropertyUtil.get(a, "value").orElse(null)) + assertEquals(Optional.empty(), PropertyUtil.get(a, "b.value")) + PropertyUtil.set(a, "b.value", "value-b") + assertEquals("value-b", PropertyUtil.get(a, "b.value").orElse(null)) + assertEquals(Optional.empty(), PropertyUtil.get(a, "b.c.value")) + PropertyUtil.set(a, "b.c.value", "value-c") + assertEquals("value-c", PropertyUtil.get(a, "b.c.value").orElse(null)) + } + + @Test + fun `test get non-existent property returns empty`() { + val a = A() + assertEquals(Optional.empty(), PropertyUtil.get(a, "value")) + assertEquals(Optional.empty(), PropertyUtil.get(a, "b")) + assertEquals(Optional.empty(), PropertyUtil.get(a, "b.c")) + assertEquals(Optional.empty(), PropertyUtil.get(a, "not_found")) + } +} diff --git a/core/src/test/kotlin/org/stellar/anchor/util/SepHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/SepHelperTest.kt index 132d3e6c89..053c8b3389 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/SepHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/SepHelperTest.kt @@ -5,7 +5,7 @@ import org.stellar.sdk.xdr.MemoType internal class SepHelperTest { @Test - fun test() { + fun `test memoType conversion`() { assert(SepHelper.memoTypeString(MemoType.MEMO_ID).equals("id")) assert(SepHelper.memoTypeString(MemoType.MEMO_HASH).equals("hash")) assert(SepHelper.memoTypeString(MemoType.MEMO_TEXT).equals("text")) diff --git a/core/src/test/kotlin/org/stellar/anchor/util/SepLanguageHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/SepLanguageHelperTest.kt index 7f317fb19b..257494e744 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/SepLanguageHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/SepLanguageHelperTest.kt @@ -18,7 +18,7 @@ class SepLanguageHelperTest { SepLanguageHelper.reset() } @Test - fun testValidateLanguage() { + fun `test validateLanguage()`() { every { appConfig.languages } returns listOf( "en", diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt index 678836753b..c774796c1d 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt @@ -1,5 +1,6 @@ package org.stellar.anchor.platform +import java.time.temporal.ChronoUnit.SECONDS import org.junit.jupiter.api.Assertions.* import org.stellar.anchor.api.platform.PatchTransactionRequest import org.stellar.anchor.api.platform.PatchTransactionsRequest @@ -203,5 +204,8 @@ fun testSep31UnhappyPath() { assertEquals(postTxResponse.id, gotSep31TxResponse.transaction.id) assertEquals(TransactionEvent.Status.COMPLETED.status, gotSep31TxResponse.transaction.status) assertNull(gotSep31TxResponse.transaction.requiredInfoMessage) - assertEquals(patchedTx.completedAt, gotSep31TxResponse.transaction.completedAt) + assertEquals( + patchedTx.completedAt.truncatedTo(SECONDS), + gotSep31TxResponse.transaction.completedAt.truncatedTo(SECONDS) + ) } diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java index 8751a7ac64..9ab1eaf668 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java @@ -49,7 +49,12 @@ public ChallengeResponse createChallenge( homeDomain, clientDomain); ChallengeRequest challengeRequest = - ChallengeRequest.of(account, memo, homeDomain, clientDomain); + ChallengeRequest.builder() + .account(account) + .memo(memo) + .homeDomain(homeDomain) + .clientDomain(clientDomain) + .build(); return sep10Service.createChallenge(challengeRequest); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java b/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java index 9221a24a9b..93398e1f95 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java @@ -92,7 +92,7 @@ public MetricEmitterService( if (metricConfig.isOptionalMetricsEnabled()) { this.executor.scheduleAtFixedRate( - new MetricEmitter(), 0, metricConfig.getRunInterval(), TimeUnit.SECONDS); + new MetricEmitter(), 10, metricConfig.getRunInterval(), TimeUnit.SECONDS); } } From b2100d4127cffe640bc790a35cfa9ee632a7054e Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 19 Aug 2022 10:12:21 -0700 Subject: [PATCH 27/65] fix the contention problem that causes locked database. (#512) --- .../service/MetricEmitterService.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java b/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java index 93398e1f95..dc778be0c1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java @@ -6,6 +6,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.stellar.anchor.config.MetricConfig; import org.stellar.anchor.platform.data.JdbcSep31TransactionRepo; @@ -13,6 +14,7 @@ public class MetricEmitterService { private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + private MetricConfig metricConfig; private final JdbcSep31TransactionRepo sep31TransactionStore; AtomicInteger pendingStellarTxns = new AtomicInteger(0); AtomicInteger pendingCustomerInfoUpdateTxns = new AtomicInteger(0); @@ -24,6 +26,7 @@ public class MetricEmitterService { public MetricEmitterService( MetricConfig metricConfig, JdbcSep31TransactionRepo sep31TransactionRepo) { + this.metricConfig = metricConfig; this.sep31TransactionStore = sep31TransactionRepo; // Create counters Metrics.counter( @@ -89,11 +92,6 @@ public MetricEmitterService( errorTxns); // TODO add gauges for SEP-24 Transactions - - if (metricConfig.isOptionalMetricsEnabled()) { - this.executor.scheduleAtFixedRate( - new MetricEmitter(), 10, metricConfig.getRunInterval(), TimeUnit.SECONDS); - } } class MetricEmitter implements Runnable { @@ -113,12 +111,18 @@ public void run() { } } + @PreDestroy public void stop() { executor.shutdownNow(); } - @PreDestroy - public void destroy() { - stop(); + @PostConstruct + public void start() { + if (metricConfig != null) { + if (metricConfig.isOptionalMetricsEnabled()) { + this.executor.scheduleAtFixedRate( + new MetricEmitter(), 10, metricConfig.getRunInterval(), TimeUnit.SECONDS); + } + } } } From 64ee6177dccc39c0537f541644caf5c37fda15d8 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Fri, 19 Aug 2022 15:04:42 -0300 Subject: [PATCH 28/65] hotfix: CI was not being executed in the develop branch after merging (#513) ### What Fix CI not being executed in the develop branch after merging. ### Why After we changed the default branch to `develop`, we hadn't updated the CI to run on the new default branch (after merging PRs). --- .github/workflows/build_and_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5c0fd7cd0b..275159ad30 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -5,7 +5,7 @@ name: Java CI with Gradle on: push: - branches: [ main ] + branches: [ develop ] pull_request: jobs: From b2e5af61e6e32b37a58f19810cc1cdfaab95bf83 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Fri, 19 Aug 2022 17:18:36 -0300 Subject: [PATCH 29/65] Feature: release checklist (#511) ### What Add a release checklist to be followed before making a new release. With this addition, we will have a new issue option called `Release a new Version!` when creating a [new issue](https://github.com/stellar/java-stellar-anchor-sdk/issues/new/choose). The idea is that we create a ticket for new releases and follow the checklist to make sure the release process is being followed thoroughly. ### Why As part of the new gitflow process outlined in #487. --- .../ISSUE_TEMPLATE/release_a_new_version.md | 31 +++++++++++++++++++ CHANGELOG.md | 5 +++ settings.gradle.kts | 1 + 3 files changed, 37 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/release_a_new_version.md create mode 100644 CHANGELOG.md diff --git a/.github/ISSUE_TEMPLATE/release_a_new_version.md b/.github/ISSUE_TEMPLATE/release_a_new_version.md new file mode 100644 index 0000000000..6d1413fa5a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_a_new_version.md @@ -0,0 +1,31 @@ +--- +name: Release a New Version! +about: Prepare a release to be launched +title: '' +labels: release +--- + + + +### Release Checklist + +> Attention: the examples below use the version `0.1.0` but you should update them to use the version you're releasing. + +- [ ] Decide on a version number based on the current version number and the common rules defined in [Semantic Versioning](https://semver.org). E.g. `0.1.0`. +- [ ] Update this ticket name to reflect the new version number, following the pattern "Release `0.1.0`". +- [ ] Cut a branch for the new release out of the `develop` branch, following the gitflow naming pattern `release/0.1.0`. +- [ ] Update the code to use this version number. +- [ ] Update the [CHANGELOG.md] file with the new version number and release notes. +- [ ] Run tests and linting. **Not only CI/CD tests, but also manual tests to make sure the release is up and running, and that it's stable!** +- [ ] Make all changes necessary to make sure the release is ready to be published. If new issues are found during the manual tests, create new tickets aiming at improving the automated tests so these issues can be automatically detected next time. +- [ ] DO NOT RELEASE before holidays or weekends! Mondays and Tuesdays are preferred. +- [ ] When the team is confident the release is stable, you'll need to create two pull requests: + - [ ] `release/0.1.0 -> main`: this should require two approvals. + - [ ] `release/0.1.0 -> develop`: ideally, this should be merged after the `main` branch is merged. +- [ ] Create a new release on GitHub with the name `0.1.0` and the changes from the [CHANGELOG.md] file. + - **Work-In-Progress:** The release should trigger the release process on GitHub that will: + 1. Publish a new version of the docker image to Docker Hub. + 2. Publish a new version of the SDK to jitpack and + 3. Automatically upload the jar file to the GH release. + +[CHANGELOG.md]: ../../CHANGELOG.md \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..3317a3a914 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## Unreleased + +- Add the [Release Checklist](.github/ISSUE_TEMPLATE/release_a_new_version.md). \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 7ef23c2ed8..5802d75825 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,6 +3,7 @@ rootProject.name = "java-stellar-anchor-sdk" /** APIs and Schemas */ include("api-schema") +/** SDK */ include("core") /** Anchor Platform */ From bfc691526c398b227252e1b96e59adadd7376759 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 22 Aug 2022 16:58:08 -0300 Subject: [PATCH 30/65] Feature: when no cursor can be found in the database, the Stellar streamer will start from the most recent cursor in the Network (#517) ### What - When no cursor can be found in the database, the Stellar streamer will start from the most recent cursor in the Network. - Also, the streaming request was updated to use `?limit=200` for improved efficiency. The default limit is 10, which is 20 times less efficient. ### Why To improve the efficiency of the streamer on average and mostly on the first run. Close #500. --- .../stellar/StellarPaymentObserver.java | 49 ++++++- .../stellar/StellarPaymentObserverTest.kt | 128 ++++++++++++++++++ 2 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserverTest.kt diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java index 3396c2ed0e..b47a6eb454 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java @@ -5,6 +5,7 @@ import static org.stellar.anchor.util.ReflectionUtil.getField; import com.google.gson.annotations.SerializedName; +import java.io.IOException; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; @@ -25,12 +26,18 @@ import org.stellar.sdk.requests.PaymentsRequestBuilder; import org.stellar.sdk.requests.RequestBuilder; import org.stellar.sdk.requests.SSEStream; +import org.stellar.sdk.responses.Page; import org.stellar.sdk.responses.operations.OperationResponse; import org.stellar.sdk.responses.operations.PathPaymentBaseOperationResponse; import org.stellar.sdk.responses.operations.PaymentOperationResponse; import shadow.com.google.common.base.Optional; public class StellarPaymentObserver implements HealthCheckable { + /** The maximum number of results the Stellar Blockchain can return. */ + private static final int MAX_RESULTS = 200; + /** The minimum number of results the Stellar Blockchain can return. */ + private static final int MIN_RESULTS = 1; + final Server server; final Set observers; final StellarPaymentStreamerCursorStore paymentStreamerCursorStore; @@ -59,14 +66,48 @@ public void shutdown() { this.stream.close(); } - public SSEStream watch() { - PaymentsRequestBuilder paymentsRequest = - server.payments().includeTransactions(true).order(RequestBuilder.Order.ASC); + /** + * fetchStreamingCursor will gather a starting cursor for the streamer. If there is a cursor + * already stored in the database, that value will be returned. Otherwise, this method will fetch + * the most recent cursor from the Network and use that as a starting point. + * + * @return the starting point to start streaming from. + */ + String fetchStreamingCursor() { + // Use database value, if any. String lastToken = paymentStreamerCursorStore.load(); if (lastToken != null) { - paymentsRequest.cursor(lastToken); + return lastToken; } + // Otherwise, fetch the latest value from the network. + Page pageOpResponse; + try { + pageOpResponse = + server.payments().order(RequestBuilder.Order.DESC).limit(MIN_RESULTS).execute(); + } catch (IOException e) { + Log.errorEx("Error fetching the latest /payments result.", e); + return null; + } + + if (pageOpResponse == null + || pageOpResponse.getRecords() == null + || pageOpResponse.getRecords().size() == 0) { + return null; + } + return pageOpResponse.getRecords().get(0).getPagingToken(); + } + + public SSEStream watch() { + String latestCursor = fetchStreamingCursor(); + PaymentsRequestBuilder paymentsRequest = + server + .payments() + .includeTransactions(true) + .cursor(latestCursor) + .order(RequestBuilder.Order.ASC) + .limit(MAX_RESULTS); + return paymentsRequest.stream( new EventListener<>() { @Override diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserverTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserverTest.kt new file mode 100644 index 0000000000..f4a22dfc78 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserverTest.kt @@ -0,0 +1,128 @@ +package org.stellar.anchor.platform.payment.observer.stellar + +import com.google.gson.reflect.TypeToken +import io.mockk.* +import io.mockk.impl.annotations.MockK +import java.io.IOException +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.sdk.Server +import org.stellar.sdk.requests.RequestBuilder +import org.stellar.sdk.responses.GsonSingleton +import org.stellar.sdk.responses.Page +import org.stellar.sdk.responses.operations.OperationResponse + +class StellarPaymentObserverTest { + companion object { + const val TEST_HORIZON_URI = "https://horizon-testnet.stellar.org/" + } + + @MockK private lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + } + + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun `test if StellarPaymentObserver will fetch the cursor from the DB, then fallback to the Network`() { + // 1 - If there is a stored cursor, we'll use that. + every { paymentStreamerCursorStore.load() } returns "123" + var stellarObserver = + StellarPaymentObserver(TEST_HORIZON_URI, null, null, paymentStreamerCursorStore) + + var gotCursor = stellarObserver.fetchStreamingCursor() + assertEquals("123", gotCursor) + verify(exactly = 1) { paymentStreamerCursorStore.load() } + + // 2 - If there is no stored constructor, we will fall back to fetching a result from the + // network. + every { paymentStreamerCursorStore.load() } returns null + mockkConstructor(Server::class) + stellarObserver = + StellarPaymentObserver(TEST_HORIZON_URI, null, null, paymentStreamerCursorStore) + + // 2.1 If fetching from the network throws an error, we return `null` + every { + constructedWith(EqMatcher(TEST_HORIZON_URI)) + .payments() + .order(RequestBuilder.Order.DESC) + .limit(1) + .execute() + } throws IOException("Some IO Problem happened!") + + gotCursor = stellarObserver.fetchStreamingCursor() + verify(exactly = 2) { paymentStreamerCursorStore.load() } + verify(exactly = 1) { + constructedWith(EqMatcher(TEST_HORIZON_URI)) + .payments() + .order(RequestBuilder.Order.DESC) + .limit(1) + .execute() + } + assertNull(gotCursor) + + // 2.2 If fetching from the network does not return any result, we return `null` + every { + constructedWith(EqMatcher(TEST_HORIZON_URI)) + .payments() + .order(RequestBuilder.Order.DESC) + .limit(1) + .execute() + } returns null + + gotCursor = stellarObserver.fetchStreamingCursor() + verify(exactly = 3) { paymentStreamerCursorStore.load() } + verify(exactly = 2) { + constructedWith(EqMatcher(TEST_HORIZON_URI)) + .payments() + .order(RequestBuilder.Order.DESC) + .limit(1) + .execute() + } + assertNull(gotCursor) + + // 2.3 If fetching from the network returns a value, use that. + val opPageJson = + """{ + "_embedded": { + "records": [ + { + "paging_token": "4322708489777153", + "type_i": 0 + } + ] + } + }""" + val operationPageType = object : TypeToken?>() {}.type + val operationPage: Page = + GsonSingleton.getInstance().fromJson(opPageJson, operationPageType) + + every { + constructedWith(EqMatcher(TEST_HORIZON_URI)) + .payments() + .order(RequestBuilder.Order.DESC) + .limit(1) + .execute() + } returns operationPage + + gotCursor = stellarObserver.fetchStreamingCursor() + verify(exactly = 4) { paymentStreamerCursorStore.load() } + verify(exactly = 3) { + constructedWith(EqMatcher(TEST_HORIZON_URI)) + .payments() + .order(RequestBuilder.Order.DESC) + .limit(1) + .execute() + } + assertEquals("4322708489777153", gotCursor) + } +} From e0fb6a39e459655b52e950f95ee6e21ff92465dd Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 23 Aug 2022 10:44:34 -0700 Subject: [PATCH 31/65] Move @Bean declarations from AnchorPlatformServer to HelperConfig and MetricsConfig for preparation of configuration management refactoring. (#514) --- .../anchor/platform/AnchorPlatformServer.java | 17 ----------------- .../stellar/anchor/platform/HelperConfig.java | 14 ++++++++++++++ .../stellar/anchor/platform/MetricsConfig.java | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java diff --git a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java index 3117c85894..17a8d816e5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -2,7 +2,6 @@ import static org.springframework.boot.Banner.Mode.OFF; -import com.google.gson.Gson; import java.util.Map; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -10,17 +9,12 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.stellar.anchor.config.MetricConfig; import org.stellar.anchor.platform.configurator.DataAccessConfigurator; import org.stellar.anchor.platform.configurator.PlatformAppConfigurator; import org.stellar.anchor.platform.configurator.PropertiesReader; import org.stellar.anchor.platform.configurator.SpringFrameworkConfigurator; -import org.stellar.anchor.platform.data.JdbcSep31TransactionRepo; -import org.stellar.anchor.platform.service.MetricEmitterService; -import org.stellar.anchor.util.GsonUtils; @SpringBootApplication @EnableJpaRepositories(basePackages = {"org.stellar.anchor.platform.data"}) @@ -66,18 +60,7 @@ public static ConfigurableApplicationContext start( return springApplication.run(); } - @Bean - public Gson gson() { - return GsonUtils.builder().create(); - } - public static void start(int port, String contextPath) { start(port, contextPath, null, false); } - - @Bean - public MetricEmitterService metricService( - MetricConfig metricConfig, JdbcSep31TransactionRepo sep31TransactionRepo) { - return new MetricEmitterService(metricConfig, sep31TransactionRepo); - } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java b/platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java new file mode 100644 index 0000000000..bf5b1da4a6 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java @@ -0,0 +1,14 @@ +package org.stellar.anchor.platform; + +import com.google.gson.Gson; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.util.GsonUtils; + +@Configuration +public class HelperConfig { + @Bean + public Gson gson() { + return GsonUtils.builder().create(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java b/platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java new file mode 100644 index 0000000000..3236b6a837 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java @@ -0,0 +1,16 @@ +package org.stellar.anchor.platform; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.config.MetricConfig; +import org.stellar.anchor.platform.data.JdbcSep31TransactionRepo; +import org.stellar.anchor.platform.service.MetricEmitterService; + +@Configuration +public class MetricsConfig { + @Bean + public MetricEmitterService metricService( + MetricConfig metricConfig, JdbcSep31TransactionRepo sep31TransactionRepo) { + return new MetricEmitterService(metricConfig, sep31TransactionRepo); + } +} From 05229aab61aa0f5285cd5d7f5461ee23ec7085ee Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 23 Aug 2022 17:25:58 -0300 Subject: [PATCH 32/65] Feature: log requests and responses (#519) ### What Log requests and responses. Request payloads are never logged and response payloads are only logged when it's an error response. This is how the log looks like: ```log 11:54:57.180 INFO - o.s.a.p.u.RequestLoggerFilter - "RequestResponseMessage": {"durationMilliseconds": 7, "Request": {"method": "GET", "fullPath": "/sep31/transactions/1?foo=bar&a=b", "authType": null, "principalName": null, "clientId": "127.0.0.1"}, "Response": {"statusCode": 403, "responseBody": "{ "error": "forbidden" }"}} 11:54:58.213 INFO - o.s.a.p.u.RequestLoggerFilter - "RequestResponseMessage": {"durationMilliseconds": 15, "Request": {"method": "GET", "fullPath": "/health", "authType": null, "principalName": null, "clientId": "127.0.0.1"}, "Response": {"statusCode": 200, "responseBody": "[hidden]"}} ``` ### Why Close #489. --- .../anchor/platform/RequestLoggerConfig.java | 19 +++ .../platform/utils/RequestLoggerFilter.java | 122 ++++++++++++++++++ .../utils/RequestResponseMessage.java | 68 ++++++++++ 3 files changed, 209 insertions(+) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/utils/RequestLoggerFilter.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/utils/RequestResponseMessage.java diff --git a/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java b/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java new file mode 100644 index 0000000000..39d7735087 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java @@ -0,0 +1,19 @@ +package org.stellar.anchor.platform; + +import javax.servlet.Filter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.platform.utils.RequestLoggerFilter; + +@Configuration +public class RequestLoggerConfig { + @Bean + public FilterRegistrationBean requestLoggerFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new RequestLoggerFilter()); + registrationBean.addUrlPatterns("/"); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/utils/RequestLoggerFilter.java b/platform/src/main/java/org/stellar/anchor/platform/utils/RequestLoggerFilter.java new file mode 100644 index 0000000000..ec151fafef --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/utils/RequestLoggerFilter.java @@ -0,0 +1,122 @@ +package org.stellar.anchor.platform.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Objects; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; +import org.stellar.anchor.util.Log; + +/** + * Log each request and response. The request body is never logged and the response body is only + * logged when it's an error + * + * @see StackOverflow Answer + */ +public class RequestLoggerFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + long startTime = System.currentTimeMillis(); + + // ========= Log request and response payload ("body") ======== + // We CANNOT simply read the request payload here, because then the InputStream would be + // consumed and cannot be read again by the actual processing/server. + // So we need to apply some stronger magic here :-) + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); + ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response); + + // IMPORTANT: This performs the actual request! + filterChain.doFilter(wrappedRequest, wrappedResponse); + long duration = System.currentTimeMillis() - startTime; + + String principalName = + request.getUserPrincipal() == null ? null : request.getUserPrincipal().getName(); + RequestResponseMessage requestResponseMessage = + RequestResponseMessage.builder() + .request( + RequestResponseMessage.Request.builder() + .method(request.getMethod()) + .path(request.getRequestURI()) + .queryParams(request.getQueryString()) + .authType(request.getAuthType()) + .principalName(principalName) + .clientId(getClientIpAddress(request)) + .build()) + .response( + RequestResponseMessage.Response.builder() + .statusCode(response.getStatus()) + .responseBody(getBody(wrappedResponse)) + .build()) + .durationMilliseconds(duration) + .build(); + + Log.info(requestResponseMessage.toString()); + + // IMPORTANT: copy content of response back into original response: + wrappedResponse.copyBodyToResponse(); + } + + /** + * getBody will get the response body (if it's an error) or omit it if it's not an error. + * + * @param wrappedResponse the wrapped response + * @return the response body, if it's an error, or "[hidden]" if it's not. + */ + private String getBody(ContentCachingResponseWrapper wrappedResponse) { + byte[] buf = wrappedResponse.getContentAsByteArray(); + + if (Objects.toString(buf, "").isEmpty()) { + return ""; + } + + int length = Math.min(buf.length, 2048); + try { + String body = new String(buf, 0, length, wrappedResponse.getCharacterEncoding()); + return body.contains("\"error\"") ? body : "[hidden]"; + } catch (UnsupportedEncodingException ex) { + return "Unsupported Encoding"; + } + } + + public static String getClientIpAddress(HttpServletRequest request) { + final String[] IP_HEADER_CANDIDATES = { + "X-Forwarded-For", + "X-Real-IP", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_X_FORWARDED_FOR", + "HTTP_X_FORWARDED", + "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_CLIENT_IP", + "HTTP_FORWARDED_FOR", + "HTTP_FORWARDED", + "HTTP_VIA", + "REMOTE_ADDR" + }; + + String ip = request.getRemoteAddr(); + + for (String headerName : IP_HEADER_CANDIDATES) { + String headerValue = request.getHeader(headerName); + if (headerValue != null + && headerValue.length() != 0 + && !"unknown".equalsIgnoreCase(headerValue)) { + ip = headerValue.split(",")[0]; + break; + } + } + + if (ip.equals("0:0:0:0:0:0:0:1")) { + ip = "127.0.0.1"; + } + return ip; + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/utils/RequestResponseMessage.java b/platform/src/main/java/org/stellar/anchor/platform/utils/RequestResponseMessage.java new file mode 100644 index 0000000000..58fa27d7e4 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/utils/RequestResponseMessage.java @@ -0,0 +1,68 @@ +package org.stellar.anchor.platform.utils; + +import java.util.stream.Collectors; +import lombok.Builder; + +@Builder +public class RequestResponseMessage { + long durationMilliseconds; + Request request; + Response response; + + @Override + public String toString() { + return String.format( + "\"RequestResponseMessage\": {\"durationMilliseconds\": %s, %s, %s}", + getStrValue(durationMilliseconds), getStrValue(request), getStrValue(response)); + } + + @Builder + static class Request { + String method; + String path; + String queryParams; + String authType; + String principalName; + String clientId; + + @Override + public String toString() { + String fullPath = path; + if (queryParams != null) { + fullPath += String.format("?%s", queryParams); + } + return String.format( + "\"Request\": {\"method\": %s, \"fullPath\": %s, \"authType\": %s, \"principalName\": %s, \"clientId\": %s}", + getStrValue(method), + getStrValue(fullPath), + getStrValue(authType), + getStrValue(principalName), + getStrValue(clientId)); + } + } + + @Builder + static class Response { + int statusCode; + String responseBody; + + @Override + public String toString() { + String responseBody = + this.responseBody.lines().map(String::trim).collect(Collectors.joining(" ")); + return String.format( + "\"Response\": {\"statusCode\": %s, \"responseBody\": %s}", + getStrValue(statusCode), getStrValue(responseBody)); + } + } + + static String getStrValue(Object obj) { + if (obj == null) { + return null; + } + if (obj instanceof String) { + return String.format("\"%s\"", obj); + } + return obj.toString(); + } +} From e10d4bfce7b7014a8f01c0f85bbe00f951acf11e Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 23 Aug 2022 14:57:49 -0700 Subject: [PATCH 33/65] core: Make testing connection of UrlValidateionUtil.validateUrl() optional (#521) * Changed UrlValidateionUtil.validateUrl not to connect to url * Fix the variable name from fieldName to url --- .../org/stellar/anchor/util/UrlValidationUtil.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/stellar/anchor/util/UrlValidationUtil.java b/core/src/main/java/org/stellar/anchor/util/UrlValidationUtil.java index dcb2408279..b2b2605c49 100644 --- a/core/src/main/java/org/stellar/anchor/util/UrlValidationUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/UrlValidationUtil.java @@ -8,10 +8,16 @@ public class UrlValidationUtil { static UrlConnectionStatus validateUrl(String urlString) { + return validateUrl(urlString, false); + } + + static UrlConnectionStatus validateUrl(String urlString, boolean testConnection) { try { URL url = new URL(urlString); - URLConnection conn = url.openConnection(); - conn.connect(); + if (testConnection) { + URLConnection conn = url.openConnection(); + conn.connect(); + } } catch (MalformedURLException e) { return UrlConnectionStatus.MALFORMED; } catch (IOException e) { @@ -28,7 +34,7 @@ public static void rejectIfMalformed(String url, String fieldName, Errors errors String.format("invalidUrl-%s", fieldName), String.format("%s is not in valid format", fieldName)); } else if (urlStatus == UrlConnectionStatus.UNREACHABLE) { - Log.error(String.format("%s field invalid: cannot connect to %s", fieldName, fieldName)); + Log.error(String.format("%s field invalid: cannot connect to %s", fieldName, url)); } } } From a1a3ee8c752ee3627111bd91944b8ee33452b574 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 23 Aug 2022 19:42:04 -0300 Subject: [PATCH 34/65] Feature: add versioning to the project, subprojects and to the /health endpoint (#515) ### What Add versioning to the project, subprojects and to the `/health` endpoint. This is how the response from `/health` looks like: Screen Shot 2022-08-23 at 3 41 20 PM ### Why As part of the gitflow adoption. --- api-schema/build.gradle.kts | 13 ++++++ .../api/platform/HealthCheckResponse.java | 3 ++ .../stellar/anchor/api/shared/Metadata.java | 34 ++++++++++++++++ .../src/main/resources/metadata.properties | 1 + .../stellar/anchor/api/shared/MetadataTest.kt | 11 +++++ .../src/test/resources/metadata.properties | 1 + build.gradle.kts | 40 ++++++++++++++----- core/build.gradle.kts | 2 - service-runner/build.gradle.kts | 1 - 9 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/shared/Metadata.java create mode 100644 api-schema/src/main/resources/metadata.properties create mode 100644 api-schema/src/test/kotlin/org/stellar/anchor/api/shared/MetadataTest.kt create mode 100644 api-schema/src/test/resources/metadata.properties diff --git a/api-schema/build.gradle.kts b/api-schema/build.gradle.kts index 2396ab63e9..6dfedbee0f 100644 --- a/api-schema/build.gradle.kts +++ b/api-schema/build.gradle.kts @@ -1,5 +1,18 @@ plugins { `java-library` + alias(libs.plugins.kotlin.jvm) +} + +tasks { + processResources { + doFirst { + val existingFile = file("$buildDir/resources/main/metadata.properties") + println(existingFile.exists()) + existingFile.delete() + println(existingFile.exists()) + } + filter { line -> line.replace("%APP_VERSION_TOKEN%", rootProject.version.toString()) } + } } dependencies { diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java index 1eb6999229..c4359a7f00 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java @@ -7,11 +7,14 @@ import java.util.List; import java.util.Map; import lombok.Data; +import org.stellar.anchor.api.shared.Metadata; @Data public class HealthCheckResponse { final Instant started; + final String version = Metadata.getVersion(); + @SerializedName("elapsed_time_ms") Duration elapsedTime = Duration.ZERO; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/shared/Metadata.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/Metadata.java new file mode 100644 index 0000000000..614c7af72c --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/Metadata.java @@ -0,0 +1,34 @@ +package org.stellar.anchor.api.shared; + +import java.io.IOException; +import java.util.Objects; +import java.util.Properties; + +public class Metadata { + private static Properties projectProperties; + + private static Properties getProperties() { + if (projectProperties == null) { + projectProperties = new Properties(); + try { + projectProperties.load(Metadata.class.getResourceAsStream("/metadata.properties")); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + return projectProperties; + } + + private static String get(String key) { + return get(key, null); + } + + private static String get(String key, String defaultValue) { + return Objects.toString(getProperties().getProperty(key), defaultValue); + } + + public static String getVersion() { + return get("version", "no version"); + } +} diff --git a/api-schema/src/main/resources/metadata.properties b/api-schema/src/main/resources/metadata.properties new file mode 100644 index 0000000000..acfd476e08 --- /dev/null +++ b/api-schema/src/main/resources/metadata.properties @@ -0,0 +1 @@ +version=%APP_VERSION_TOKEN% diff --git a/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/MetadataTest.kt b/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/MetadataTest.kt new file mode 100644 index 0000000000..abf1de2a8f --- /dev/null +++ b/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/MetadataTest.kt @@ -0,0 +1,11 @@ +package org.stellar.anchor.api.shared + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class MetadataTest { + @Test + fun `test Metadata#getVersion`() { + Assertions.assertEquals("0.1.2", Metadata.getVersion()) + } +} diff --git a/api-schema/src/test/resources/metadata.properties b/api-schema/src/test/resources/metadata.properties new file mode 100644 index 0000000000..33aa3f7c4d --- /dev/null +++ b/api-schema/src/test/resources/metadata.properties @@ -0,0 +1 @@ +version=0.1.2 diff --git a/build.gradle.kts b/build.gradle.kts index 0f24caf9dd..1d1bd5b320 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,6 +3,7 @@ plugins { java alias(libs.plugins.spotless) + alias(libs.plugins.kotlin.jvm) apply false } tasks { @@ -20,9 +21,6 @@ subprojects { apply(plugin = "java") apply(plugin = "com.diffplug.spotless") - group = "org.stellar.anchor-sdk" - version = "0.1.1" - repositories { mavenLocal() mavenCentral() @@ -33,9 +31,6 @@ subprojects { /** Specifies JDK-11 */ java { toolchain { languageVersion.set(JavaLanguageVersion.of(11)) } } - /** Enforces google-java-format at Java compilation. */ - tasks.named("compileJava") { this.dependsOn("spotlessApply") } - spotless { val javaVersion = System.getProperty("java.version") if (javaVersion >= "17") { @@ -88,14 +83,20 @@ subprojects { * This is to fix the Windows default cp-1252 character encoding that may potentially cause * compilation error */ - tasks.compileJava { options.encoding = "UTF-8" } + tasks { + compileJava { + options.encoding = "UTF-8" - tasks.compileTestJava { options.encoding = "UTF-8" } + /** Enforces google-java-format at Java compilation. */ + dependsOn("spotlessApply") + } + + compileTestJava { options.encoding = "UTF-8" } - tasks.javadoc { options.encoding = "UTF-8" } + javadoc { options.encoding = "UTF-8" } - /** JUnit5 should be used for all subprojects. */ - tasks.test { useJUnitPlatform() } + test { useJUnitPlatform() } + } configurations { all { @@ -105,3 +106,20 @@ subprojects { } } } + +allprojects { + group = "org.stellar.anchor-sdk" + version = "1.2.3" + + tasks.jar { + manifest { + attributes( + mapOf( + "Implementation-Title" to project.name, + "Implementation-Version" to project.version + ) + ) + } + } +} + diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 4d8a103914..1d5bfeac9d 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -7,8 +7,6 @@ plugins { alias(libs.plugins.kotlin.jvm) } -version = "1.0.2" - dependencies { compileOnly(libs.servlet.api) compileOnly(libs.slf4j.api) diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index d4147a8540..67f99322c7 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -7,7 +7,6 @@ plugins { application alias(libs.plugins.spring.boot) alias(libs.plugins.spring.dependency.management) - alias(libs.plugins.kotlin.jvm) } dependencies { From b247bda50f241c501dd930bc4fdc54ebdb66bd52 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 23 Aug 2022 20:10:47 -0300 Subject: [PATCH 35/65] hotfix: add missing DB migration and remove unused data models. (#522) ### What Add missing DB migration and remove unused data models. ### Why We were missing these migrations since #454. ### Further Discussion - [ ] #523 --- .../anchor/platform/data/Customer.java | 14 ------------- .../anchor/platform/data/CustomerStatus.java | 12 ----------- ..._drop_sep31_transaction_message_column.sql | 20 +++++++++++++++++++ 3 files changed, 20 insertions(+), 26 deletions(-) delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/Customer.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java create mode 100644 platform/src/main/resources/db/migration/V4__add_observer_tables_and_drop_sep31_transaction_message_column.sql diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/Customer.java b/platform/src/main/java/org/stellar/anchor/platform/data/Customer.java deleted file mode 100644 index d5d6d2f8a5..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/data/Customer.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.stellar.anchor.platform.data; - -import java.util.List; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.OneToMany; -import lombok.Data; - -@Entity -@Data -public class Customer { - @Id private String id; - @OneToMany private List statuses; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java b/platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java deleted file mode 100644 index 6be62a1d97..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/data/CustomerStatus.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.stellar.anchor.platform.data; - -import javax.persistence.Entity; -import javax.persistence.Id; -import lombok.Data; - -@Entity -@Data -public class CustomerStatus { - @Id private String type; - private String status; -} diff --git a/platform/src/main/resources/db/migration/V4__add_observer_tables_and_drop_sep31_transaction_message_column.sql b/platform/src/main/resources/db/migration/V4__add_observer_tables_and_drop_sep31_transaction_message_column.sql new file mode 100644 index 0000000000..6c3dcf98c4 --- /dev/null +++ b/platform/src/main/resources/db/migration/V4__add_observer_tables_and_drop_sep31_transaction_message_column.sql @@ -0,0 +1,20 @@ +CREATE TABLE stellar_payment_observer_page_token +( + id VARCHAR(255) NOT NULL, + cursor VARCHAR(255), + CONSTRAINT pk_stellar_payment_observer_page_token PRIMARY KEY (id) +); + +CREATE TABLE stellar_payment_observing_account +( + account VARCHAR(255) NOT NULL, + last_observed TIMESTAMP WITHOUT TIME ZONE, + CONSTRAINT pk_stellar_payment_observing_account PRIMARY KEY (account) +); + +ALTER TABLE stellar_payment_observing_account + ADD CONSTRAINT uc_stellar_payment_observing_account_account UNIQUE (account); + +DROP TABLE stellar_account_page_token CASCADE; + +ALTER TABLE sep31_transaction DROP COLUMN message; \ No newline at end of file From ae0bc2818cf36142d589b5bac9fb1c86ccecdf56 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Thu, 25 Aug 2022 15:47:29 -0300 Subject: [PATCH 36/65] hotfix: add missing AWS IAM dependency (#526) ### What Add missing AWS IAM dependency. ### Why The refactor from 3ddb1818 missed including the AWS IAM library in the gradle build, which removed the ability to connect with a database using AWS IAM. --- build.gradle.kts | 1 + platform/build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 1d1bd5b320..a15eeffa87 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,6 +63,7 @@ subprojects { implementation(rootProject.libs.aws.sqs) implementation(rootProject.libs.postgresql) implementation(rootProject.libs.bundles.kafka) + implementation(rootProject.libs.spring.kafka) // TODO: we should use log4j2 implementation(rootProject.libs.log4j.template.json) diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index 51ecd6d708..e3d17602ad 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(libs.spring.aws.messaging) implementation(libs.spring.kafka) implementation(libs.aws.rds) + implementation(libs.aws.iam.auth) implementation(libs.commons.cli) implementation(libs.commons.io) implementation(libs.flyway.core) From 888906fa43c70621444dd8887f3e4728d6da696a Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 25 Aug 2022 15:33:46 -0700 Subject: [PATCH 37/65] Fix high-risk vulnerabilities reported by Snyk (#527) --- .../Sep24InteractiveController.java | 2 +- .../org/stellar/anchor/auth/JwtService.java | 5 +- .../CirclePaymentObserverController.java | 16 ++-- .../circle/CirclePaymentObserverService.java | 5 +- .../CirclePaymentObserverServiceTest.kt | 73 +++++++++++++++---- 5 files changed, 71 insertions(+), 30 deletions(-) diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java index 6254373a99..eb3bc3dfa5 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java @@ -23,6 +23,6 @@ public String interactive(HttpServletRequest request) throws SepValidationExcept if (operation == null) throw new SepValidationException("Missing [operation] parameter."); if (operation.equals("withdraw")) return "The sep24 interactive WITHDRAW starts here."; else if (operation.equals("deposit")) return "The sep24 interactive DEPOSIT starts here."; - else return "Undefined operation " + operation; + else return String.format("Undefined operation %s", operation); } } diff --git a/core/src/main/java/org/stellar/anchor/auth/JwtService.java b/core/src/main/java/org/stellar/anchor/auth/JwtService.java index 2902e294f4..0b57fdf696 100644 --- a/core/src/main/java/org/stellar/anchor/auth/JwtService.java +++ b/core/src/main/java/org/stellar/anchor/auth/JwtService.java @@ -4,7 +4,6 @@ import io.jsonwebtoken.impl.DefaultJwsHeader; import java.nio.charset.StandardCharsets; import java.util.Calendar; -import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.stellar.anchor.config.AppConfig; @@ -36,7 +35,7 @@ public String encode(JwtToken token) { .setSubject(token.getSub()); if (token.getClientDomain() != null) { - builder.addClaims(Map.of("client_domain", token.getClientDomain())); + builder.claim("client_domain", token.getClientDomain()); } return builder.signWith(SignatureAlgorithm.HS256, jwtKey).compact(); @@ -46,7 +45,7 @@ public String encode(JwtToken token) { public JwtToken decode(String cipher) { JwtParser jwtParser = Jwts.parser(); jwtParser.setSigningKey(jwtKey); - Jwt jwt = jwtParser.parse(cipher); + Jwt jwt = jwtParser.parseClaimsJws(cipher); Header header = jwt.getHeader(); if (!(header instanceof DefaultJwsHeader)) { // This should not happen diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java index 342a2f3d12..1c8741ec0e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java @@ -4,7 +4,6 @@ import static org.stellar.anchor.util.Log.warnEx; import com.google.gson.Gson; -import java.lang.reflect.Type; import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -15,7 +14,7 @@ import org.stellar.anchor.api.exception.UnprocessableEntityException; import org.stellar.anchor.api.sep.SepExceptionResponse; import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService; -import shadow.com.google.common.reflect.TypeToken; +import org.stellar.anchor.platform.payment.observer.circle.model.CircleNotification; @RestController @RequestMapping("/circle-observer") @@ -36,7 +35,13 @@ public CirclePaymentObserverController( public void handleCircleNotificationJson( @RequestBody(required = false) Map requestBody) throws UnprocessableEntityException, BadRequestException, ServerErrorException { - circlePaymentObserverService.handleCircleNotification(requestBody); + try { + CircleNotification circleNotification = + gson.fromJson(gson.toJson(requestBody), CircleNotification.class); + circlePaymentObserverService.handleCircleNotification(circleNotification); + } catch (Exception ex) { + throw new BadRequestException("Error parsing the request."); + } } @CrossOrigin(origins = "*") @@ -46,9 +51,8 @@ public void handleCircleNotificationJson( consumes = {MediaType.TEXT_PLAIN_VALUE}) public void handleCircleNotificationTextPlain(@RequestBody(required = false) String jsonBodyStr) throws UnprocessableEntityException, BadRequestException, ServerErrorException { - Type type = new TypeToken>() {}.getType(); - Map requestBody = gson.fromJson(jsonBodyStr, type); - circlePaymentObserverService.handleCircleNotification(requestBody); + CircleNotification circleNotification = gson.fromJson(jsonBodyStr, CircleNotification.class); + circlePaymentObserverService.handleCircleNotification(circleNotification); } @ExceptionHandler(UnprocessableEntityException.class) diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java index 665551ce50..1613773d2a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.math.BigDecimal; import java.util.List; -import java.util.Map; import java.util.Objects; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -63,10 +62,8 @@ public CirclePaymentObserverService( this.observers = observers; } - public void handleCircleNotification(Map requestBody) + public void handleCircleNotification(CircleNotification circleNotification) throws UnprocessableEntityException, BadRequestException, ServerErrorException { - CircleNotification circleNotification = - gson.fromJson(gson.toJson(requestBody), CircleNotification.class); String type = Objects.toString(circleNotification.getType(), ""); switch (type) { diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt index ddc7b02a5f..745baeef9e 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt @@ -19,9 +19,13 @@ import org.stellar.anchor.horizon.Horizon import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance +import org.stellar.anchor.platform.payment.observer.circle.model.CircleNotification import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransfer -import org.stellar.sdk.* +import org.stellar.anchor.util.GsonUtils +import org.stellar.sdk.AssetTypeCreditAlphaNum4 +import org.stellar.sdk.Memo +import org.stellar.sdk.Server import org.stellar.sdk.responses.Page import org.stellar.sdk.responses.TransactionResponse import org.stellar.sdk.responses.operations.OperationResponse @@ -60,12 +64,16 @@ class CirclePaymentObserverServiceTest { server.shutdown() } + val gson = GsonUtils.getInstance() + @Test fun test_handleCircleNotification_ignoreUnsupportedType() { // Empty type is ignored var unsupportedNotification = mapOf("foo" to "bar") var ex: UnprocessableEntityException = assertThrows { - circlePaymentObserverService.handleCircleNotification(unsupportedNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(unsupportedNotification) + ) } assertEquals("Not handling notification of unsupported type \"\".", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -74,7 +82,9 @@ class CirclePaymentObserverServiceTest { unsupportedNotification = mapOf("Type" to "ABC") ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(unsupportedNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(unsupportedNotification) + ) } assertEquals("Not handling notification of unsupported type \"ABC\".", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -84,8 +94,11 @@ class CirclePaymentObserverServiceTest { fun test_handleCircleNotification_handleSubscriptionConfirmationNotification() { // missing subscribeUrl var subConfirmationNotification = mapOf("Type" to "SubscriptionConfirmation") + var ex: BadRequestException = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals( "Notification body of type SubscriptionConfirmation is missing subscription URL.", @@ -100,7 +113,9 @@ class CirclePaymentObserverServiceTest { mapOf("Type" to "SubscriptionConfirmation", "SubscribeURL" to serverUrl) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Failed to call \"SubscribeURL\" endpoint.", ex.message) assertInstanceOf(BadRequestException::class.java, ex) @@ -124,7 +139,9 @@ class CirclePaymentObserverServiceTest { ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Calling the \"SubscribeURL\" endpoint didn't succeed.", ex.message) assertInstanceOf(BadRequestException::class.java, ex) @@ -143,7 +160,9 @@ class CirclePaymentObserverServiceTest { .setBody("""{ "success": "ok" }""") server.enqueue(successResponse) assertDoesNotThrow { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } request = server.takeRequest() @@ -158,7 +177,9 @@ class CirclePaymentObserverServiceTest { // missing Message var subConfirmationNotification = mapOf("Type" to "Notification") var ex: AnchorException = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Notification body of type Notification is missing a message.", ex.message) assertInstanceOf(BadRequestException::class.java, ex) @@ -167,7 +188,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to "{}") ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Won't handle notification of type \"null\".", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -177,7 +200,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Missing \"transfer\" value in notification of type \"transfers\".", ex.message) assertInstanceOf(BadRequestException::class.java, ex) @@ -187,7 +212,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Not a complete transfer.", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -197,7 +224,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Not a complete transfer.", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -223,7 +252,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("Neither source nor destination are Stellar accounts.", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -253,7 +284,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("None of the transfer wallets is being tracked.", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -291,7 +324,9 @@ class CirclePaymentObserverServiceTest { subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) ex = assertThrows { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } assertEquals("The only supported Circle currency is USDC.", ex.message) assertInstanceOf(UnprocessableEntityException::class.java, ex) @@ -382,7 +417,9 @@ class CirclePaymentObserverServiceTest { .trimMargin() val subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) assertDoesNotThrow { - circlePaymentObserverService.handleCircleNotification(subConfirmationNotification) + circlePaymentObserverService.handleCircleNotification( + fromMapToCircleNotification(subConfirmationNotification) + ) } verify(exactly = 1) { paymentListener.onReceived(any()) } @@ -558,4 +595,8 @@ class CirclePaymentObserverServiceTest { isWalletTracked = circlePaymentObserverService.isWalletTracked(party) assertFalse(isWalletTracked) } + + fun fromMapToCircleNotification(map: Map): CircleNotification? { + return gson.fromJson(gson.toJson(map), CircleNotification::class.java) + } } From 670ddb49c0c0686dfa15b700daf9315fba674bd2 Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 26 Aug 2022 20:48:55 -0400 Subject: [PATCH 38/65] Use docker compose to test multiple anchor platform configurations end-to-end (#518) - use docker compose to allow for end to end testing of multiple different anchor platform configurations - docker compose will spin up a full anchor platform stack (postgres, kafka, anchor platform/reference server) with different config files and execute the end_to_end_test.py tests on it. - added testing for default configuration, omnibus allowlist configuration, unique addresses configuration - update documentation for omnibusAccounts configuration (use a comma separated list --- Makefile | 35 +++ docs/00 - Stellar Anchor Platform.md | 1 + ...d to End Testing with Different Configs.md | 97 ++++++++ end-to-end-tests/Dockerfile | 10 + end-to-end-tests/README.md | 14 +- end-to-end-tests/end_to_end_tests.py | 45 +++- helm-charts/sep-service/example_values.yaml | 5 +- integration-tests/docker-compose-configs/.env | 2 + .../docker-compose-configs/Dockerfile | 14 ++ .../anchor-platform-config.yaml | 133 +++++++++++ .../docker-compose-config.override.yaml | 45 ++++ .../anchor-platform-config.yaml | 129 +++++++++++ .../anchor-reference-server-config.yaml | 43 ++++ .../assets-test.json | 213 ++++++++++++++++++ .../docker-compose-config.override.yaml | 36 +++ .../stellar.toml | 26 +++ .../anchor-platform-config.yaml | 134 +++++++++++ .../anchor-reference-server-config.yaml | 48 ++++ .../docker-compose-config.override.yaml | 46 ++++ .../docker-compose.base.yaml | 71 ++++++ .../resources/anchor-config-defaults.yaml | 5 +- 21 files changed, 1141 insertions(+), 11 deletions(-) create mode 100644 docs/02 - Contributing/G - End to End Testing with Different Configs.md create mode 100644 end-to-end-tests/Dockerfile create mode 100644 integration-tests/docker-compose-configs/.env create mode 100644 integration-tests/docker-compose-configs/Dockerfile create mode 100644 integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-reference-server-config.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/assets-test.json create mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/docker-compose-config.override.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/stellar.toml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-reference-server-config.yaml create mode 100644 integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml create mode 100644 integration-tests/docker-compose-configs/docker-compose.base.yaml diff --git a/Makefile b/Makefile index b6fdee4f98..fd5ae3ed17 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ SUDO := $(shell docker version >/dev/null 2>&1 || echo "sudo") LABEL ?= $(shell git rev-parse --short HEAD)$(and $(shell git status -s),-dirty-$(shell id -u -n)) # If TAG is not provided set default value TAG ?= stellar/anchor-platform:$(LABEL) +E2E_TAG ?= stellar/anchor-platform-e2e-test:$(LABEL) # https://github.com/opencontainers/image-spec/blob/master/annotations.md BUILD_DATE := $(shell date -u +%FT%TZ) @@ -14,3 +15,37 @@ docker-build: docker-push: $(SUDO) docker push $(TAG) + +docker-build-e2e-test: + $(SUDO) docker build -f end-to-end-tests/Dockerfile --pull --label org.opencontainers.image.created="$(BUILD_DATE)" \ + -t $(E2E_TAG) . + +docker-push-e2e-test: + $(SUDO) docker push $(E2E_TAG) + +build-docker-compose-tests: + docker-compose -f integration-tests/docker-compose-configs/docker-compose.base.yaml build --no-cache + +run-e2e-test-all: + make run-e2e-test-default-config + make run-e2e-test-allowlist + make run-e2e-test-unique-address + +define run_tests + $(SUDO) docker-compose -f integration-tests/docker-compose-configs/docker-compose.base.yaml \ + -f integration-tests/docker-compose-configs/$(1)/docker-compose-config.override.yaml rm -f + + $(SUDO) docker-compose --env-file integration-tests/docker-compose-configs/.env \ + -f integration-tests/docker-compose-configs/docker-compose.base.yaml \ + -f integration-tests/docker-compose-configs/$(1)/docker-compose-config.override.yaml \ + up --exit-code-from end-to-end-tests || echo "E2E Test Failed: $(1)" +endef + +run-e2e-test-default-config: + $(call run_tests,anchor-platform-default-configs) + +run-e2e-test-allowlist: + $(call run_tests,anchor-platform-allowlist) + +run-e2e-test-unique-address: + $(call run_tests,anchor-platform-unique-address) diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index 966a83a0af..1ab2320981 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -27,6 +27,7 @@ The full documentation can be found under the [`docs` directory](/docs), under t - [D - Database Migration](/docs/02%20-%20Contributing/D%20-%20Database%20Migration.md) - [E - Publishing the SDK](/docs/02%20-%20Contributing/E%20-%20Publishing%20the%20SDK.md) - [F - Testing with AWS Services](/docs/02%20-%20Contributing/F%20-%20Testing%20with%20AWS%20Services.md) + - [G - End to End Testing with Different Configs](/docs/02%20-%20Contributing/G%20-%20End%20to%20End%20Testing%20with%20Different%20Configs.md) - [03 - Implementing the Anchor Server](/docs/03%20-%20Implementing%20the%20Anchor%20Server) - [Communication](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication) - [Callback API](/docs/03%20-%20Implementing%20the%20Anchor%20Server/Communication/Callbacks%20API.yml) diff --git a/docs/02 - Contributing/G - End to End Testing with Different Configs.md b/docs/02 - Contributing/G - End to End Testing with Different Configs.md new file mode 100644 index 0000000000..331d7a6a53 --- /dev/null +++ b/docs/02 - Contributing/G - End to End Testing with Different Configs.md @@ -0,0 +1,97 @@ +# End-to-End Testing with Different Configurations + +End-to-end testing of different Anchor Platform configurations can be accomplished with `docker-compose`. We will +maintain a base file (`integration-tests/docker-compose-configs/docker-compose.base.yaml`) and pass in an override +file (`integration-tests/docker-compose-configs/*/docker-compose.override.yaml`) to specify which config files to use +and what `end_to_end_tests` to run against the Anchor Platform configuration. + + +end_to_end_tests.py is a Python CLI tool used to test end-to-end **Anchor Platform + Anchor Server + Kafka + +Horizon** data flows. + +Please refer to [End to End Tests](/end-to-end-tests/README.md) for information on the end-to-end tests. + + +### Anchor Platform Configuration Files: +Each **directory** in `integration-tests/docker-compose-configs` contains all the +configuration files necessary for a full Anchor Platform deployment. **Note**: Except for +`docker-compose-config.override.yaml`, all other files will replace the default ones if provided, and +if not provided, the default ones from +`integration-tests/docker-compose-configs/anchor-platform-default-configs` will be used. +1) `anchor-platform-config` - the configuration file for **Anchor Platform** +2) `anchor-reference-server-config.yaml` - the configuration file for the **Anchor Reference Server** +3) `assets-test.json` - the assets file to be used by the **Anchor Platform** +4) `docker-compose-config.override.yaml` - this file gets merged into `docker-compose-configs/docker-compose.base.yaml +when running docker-compose`. This file contains references to the specific files (the config directory) to be used +and which tests (end-to-end) are to be run against the setup. +5) `stellar.toml` - the TOML file thats served from Anchor Platform's `/.well-known/stellar.toml` endpoint + +### Adding a New Configuration for Testing +1) Create a new directory in `integration-test/docker-compose-configs` +2) Copy any configuration file(s) you're interested in modifying from +`integration-tests/docker-compose-configs/anchor-platform-default-configs` into the new directory **(Note: you only +need to copy the config files you're interested in modifying, `anchor-platform-default-configs/*` files will be +used otherwise)** +3) Make whatever specific config changes you'd like to the copied file(s) +4) Update the `docker-compose-config.override.yaml` file to reference the configuration files in the newly created +directory. + + a) Replace `` with the name of the directory you created for this new configuration. + + b) Replace `` and/or `` with the name of the test(s) you'd like run against the newly added + configuration (refer to: [End to End Tests](/end-to-end-tests/README.md)). + + ```text + version: '2' + services: + anchor-platform-server: + volumes: + # add mounts for the new config directory + - ./:/config_override + + anchor-reference-server: + volumes: + # add mounts for the new config directory + - ./:/config_override + + end-to-end-tests: + command: --domain host.docker.internal:8080 --tests --tests + ``` + +5) At the top of the `docker-compose-config.override.yaml` file, document what this new configuration encompasses +6) Add this new configuration to the `Makefile` (Note: replace `` and +``): + ```text + run-e2e-test-all: + make run-e2e-test-default-config + make run-e2e-test-allowlist + make run-e2e-test-unique-address + make + + : + $(call run_tests,) + ``` + + +### Running Tests: +1) Build the Anchor Platform and end-to-end test images: + ```text + make build-docker-compose-tests + ``` +2) Create a `.env` file and add the **STELLAR_SECRET** to be used to run the end-to-end tests. + + a) **OMNIBUS_ALLOWLIST_KEYS** is used for the e2e allowlist test, one of the keys should be the public key that + corresponds to the . These comma separated values + will be used for the `omnibusAllowList` value in `anchor-platform-allowlist/anchor-platform-config.yaml` + ```text + E2E_SECRET= + OMNIBUS_ALLOWLIST_KEYS= + ``` +3) Run the end-to-end test on all the different configs: + ```text + make run-e2e-test-all + ``` +or you can run individual tests: + ```text + make run-e2e-test-unique-address + ``` \ No newline at end of file diff --git a/end-to-end-tests/Dockerfile b/end-to-end-tests/Dockerfile new file mode 100644 index 0000000000..3d5c90d59f --- /dev/null +++ b/end-to-end-tests/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.8-slim-buster + +WORKDIR /app + +COPY end-to-end-tests/requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY end-to-end-tests/. . + +Entrypoint [ "python3", "end_to_end_tests.py"] \ No newline at end of file diff --git a/end-to-end-tests/README.md b/end-to-end-tests/README.md index 0e956790e5..e76e68d2dd 100644 --- a/end-to-end-tests/README.md +++ b/end-to-end-tests/README.md @@ -4,7 +4,7 @@ end_to_end_tests.py is a Python CLI tool used to test end-to-end Anchor Platform ## Available Tests -###sep31_flow +### sep31_flow 1) (End-to-End Test) Get the SEP endpoints/keys from the Platform Server TOML file (*/.well-known/stellar.toml*) 2) (End-to-End Test) Get a token from the *GET /* endpoint to authenticate all further requests 3) (End-to-End Test) Make a *PUT //customer* request to create a customer @@ -14,20 +14,24 @@ end_to_end_tests.py is a Python CLI tool used to test end-to-end Anchor Platform 7) The Anchor Reference Server detects the published event, "processes" the event and makes a PATCH request back to the Platform Server to update the transaction's status as "complete" 8) (End-to-End Test) Polls the Platform Server for the transaction to be marked as "complete" -###sep31_flow_with_sep38 +### sep31_flow_with_sep38 Same as **sep31_flow** but a SEP38 quote is requested before step 4 and the "quote_id" is passed into the transaction creation step. -###sep38_create_quote +### sep38_create_quote Request a SEP38 quote from the Anchor Platform -###all +### omnibus_allowlist +Test the omnibus allowlist feature in SEP-10. If the supplied account is not in the `omnibusAllowList`, the request +should be rejected with a 403 error. + +### all Run all the available tests ## How To Run ```shell python anchor_platform_functional_test.py --domain --tests --secret ``` -##Examples: +## Examples: ```shell python anchor_platform_functional_test.py --domain localhost:8080 --tests sep31_flow_with_sep38 --secret ``` diff --git a/end-to-end-tests/end_to_end_tests.py b/end-to-end-tests/end_to_end_tests.py index 753f4bf3c0..5285b5e77c 100644 --- a/end-to-end-tests/end_to_end_tests.py +++ b/end-to-end-tests/end_to_end_tests.py @@ -1,5 +1,5 @@ import argparse - +import os import requests import json import base64 @@ -47,6 +47,7 @@ def get_anchor_platform_token(endpoints, public_key, secret_key): endpoints.ANCHOR_PLATFORM_AUTH_ENDPOINT, data={"transaction": envelope.to_xdr()}, ) + print(response) content = json.loads(response.content) return content["token"] @@ -213,12 +214,29 @@ def test_sep38_create_quote(endpoints, keypair, payload): quote = create_anchor_test_quote(endpoints, headers, payload) return quote +def wait_for_anchor_platform_ready(domain, poll_interval=3, timeout=180): + print(f"polling {domain}/.well-known/stellar.toml to check for readiness") + attempt = 1 + while attempt*poll_interval <= timeout: + try: + toml = fetch_stellar_toml(domain, use_http=True) + print("anchor platform is ready") + return + except Exception as e: + print(e) + attempt += 1 + time.sleep(poll_interval) + else: + print("error: timed out while polling for readiness") + exit(1) + if __name__ == "__main__": TESTS = [ "sep31_flow", "sep31_flow_with_sep38", - "sep38_create_quote" + "sep38_create_quote", + "omnibus_allowlist" ] parser = argparse.ArgumentParser() @@ -226,14 +244,22 @@ def test_sep38_create_quote(endpoints, keypair, payload): #parser.add_argument('--load-size', "-ls", help="number of tests to execute (multithreaded)", type=int, default=1) parser.add_argument('--tests', "-t", nargs="*", help=f"names of tests to execute: {TESTS}", default=TESTS) parser.add_argument('--domain', "-d", help="The Anchor Platform endpoint", default="http://localhost:8000") - parser.add_argument('--secret', "-s", help="The secret key used for transactions") + parser.add_argument('--secret', "-s", help="The secret key used for transactions", default=os.environ.get('E2E_SECRET')) + parser.add_argument('--delay', help="Seconds to delay before running the tests", default=0) args = parser.parse_args() + if args.delay: + print(f"delaying start by {args.delay} seconds") + time.sleep(int(args.delay)) + domain = args.domain + wait_for_anchor_platform_ready(domain) + endpoints = Endpoints(args.domain) keypair = Keypair.from_secret(args.secret) + tests = TESTS if args.tests[0] == "all" else args.tests for test in tests: @@ -309,5 +335,18 @@ def test_sep38_create_quote(endpoints, keypair, payload): "context": "sep31" } test_sep38_create_quote(endpoints, keypair, QUOTE_PAYLOAD_USDC_TO_JPYC) + elif test == "omnibus_allowlist": + print("####################### Testing Omnibus Allowlist #######################") + print(f"Omnibus Allowlist - testing with allowed key: {keypair.public_key}") + response = requests.get(endpoints.ANCHOR_PLATFORM_AUTH_ENDPOINT, {"account": keypair.public_key}) + assert response.status_code == 200, f"return code should be 200, got: {response.status_code}" + print(f"Omnibus Allowlist - testing with allowed key: {keypair.public_key} - success") + + random_kp = Keypair.random() + print(f"Omnibus Allowlist - testing with disallowed (random) key: {random_kp.public_key}") + response = requests.get(endpoints.ANCHOR_PLATFORM_AUTH_ENDPOINT, {"account": random_kp.public_key}) + assert response.status_code == 403, f"return code should be 403, got: {response.status_code}" + print(f"Omnibus Allowlist - testing with disallowed (random) key: {random_kp.public_key} expecting 403 error - success") + else: exit(f"Error: unknown test {test}") diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index 6bd84f27cd..ab443c38de 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -120,8 +120,9 @@ stellar: signingSeed: ${SEP10_SIGNING_SEED} requireKnownOmnibusAccount: false omnibusAccountList: - # - GDOHXZYP5ABGCTKAEROOJFN6X5GY7VQNXFNK2SHSAD32GSVMUJBPG75E - # - GC5KRPVW4TFA6ORIGTOFM3DEG6DSLACRXGDN3LNCAI7IPAGIWMVXUHVS +# omnibusAccountList: | +# GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAV, +# GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAA sep12: enabled: true customerIntegrationEndpoint: https://anchor-reference-server-dev.stellar.org diff --git a/integration-tests/docker-compose-configs/.env b/integration-tests/docker-compose-configs/.env new file mode 100644 index 0000000000..a2efdd1ebd --- /dev/null +++ b/integration-tests/docker-compose-configs/.env @@ -0,0 +1,2 @@ +E2E_SECRET= +OMNIBUS_ALLOWLIST_KEYS= \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/Dockerfile b/integration-tests/docker-compose-configs/Dockerfile new file mode 100644 index 0000000000..ed3e6bf602 --- /dev/null +++ b/integration-tests/docker-compose-configs/Dockerfile @@ -0,0 +1,14 @@ +FROM openjdk:11-jdk AS build +WORKDIR /code +COPY . . +RUN pwd && ls +RUN ./gradlew clean build -x test --stacktrace + +FROM ubuntu:20.04 + +RUN apt-get update && \ + apt-get install -y --no-install-recommends openjdk-11-jre + +COPY --from=build /code/service-runner/build/libs/anchor-platform-runner*.jar /app/anchor-platform-runner.jar + +ENTRYPOINT ["java", "-jar", "/app/anchor-platform-runner.jar"] diff --git a/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml new file mode 100644 index 0000000000..bd6ae05e05 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml @@ -0,0 +1,133 @@ + +stellar: + anchor: + config: in-memory + app-config: + type: config-spring-property + settings: app-config # The absolute location of the configuration data in this yaml file + + data-access: + type: data-spring-jdbc + settings: data-spring-jdbc-local-postgres # use local postgres instance + + logging: + type: logging-logback + settings: logging-logback-settings # The absolute location of the configuration data in this yaml file + +app-config: + app: + stellarNetworkPassphrase: Test SDF Network ; September 2015 + horizonUrl: https://horizon-testnet.stellar.org + hostUrl: http://host.docker.internal:8080 + languages: en + assets: file:config/assets-test.json + + jwtSecretKey: secret + + integration-auth: + authType: JWT_TOKEN + platformToAnchorSecret: myPlatformToAnchorSecret + anchorToPlatformSecret: myAnchorToPlatformSecret + expirationMilliseconds: 30000 + + sep1: + enabled: true + stellarFile: file:config/stellar.toml + + sep10: + enabled: true + homeDomain: host.docker.internal:8080 + clientAttributionRequired: false + clientAttributionAllowList: lobstr.co,preview.lobstr.co + authTimeout: 900 + jwtTimeout: 86400 + + signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X + omnibusAccountList: ${OMNIBUS_ALLOWLIST_KEYS} + + # leaving this here for future testing + # omnibusAccountList: + # - GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAV + # - GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAA + + requireKnownOmnibusAccount: true + + sep12: + enabled: true + customerIntegrationEndpoint: http://host.docker.internal:8081 + + sep24: + enabled: true + interactiveJwtExpiration: 3600 + + interactiveUrl: http://host.docker.internal:8081/sep24/interactive + + sep31: + enabled: true + feeIntegrationEndPoint: http://host.docker.internal:8081 + uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 + paymentType: STRICT_SEND + depositInfoGeneratorType: self # self or circle + + sep38: + enabled: true + quoteIntegrationEndPoint: http://host.docker.internal:8081 + + circle: + circleUrl: https://api-sandbox.circle.com + apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # circle API key + + payment-gateway: + circle: + name: "circle" + enabled: true + + stellar: + enabled: false + name: "stellar" + horizonUrl: https://horizon-testnet.stellar.org + secretKey: secret # stellar account secret key + + circle-payment-observer: + enabled: true + horizonUrl: https://horizon-testnet.stellar.org + stellarNetwork: TESTNET # TESTNET or PUBLIC + trackedWallet: all + + event: + enabled: true + publisherType: kafka + + kafka.publisher: + bootstrapServer: host.docker.internal:29092 + useSingleQueue: false + eventTypeToQueue: + all: ap_event_single_queue + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + +data-spring-jdbc-local-postgres: + spring.jpa.generate-ddl: true + spring.jpa.database: POSTGRESQL + spring.jpa.show-sql: false + spring.datasource.driver-class-name: org.postgresql.Driver + spring.datasource.url: jdbc:postgresql://host.docker.internal:5440/ + spring.datasource.username: postgres + spring.datasource.password: password + spring.mvc.converters.preferred-json-mapper: gson + spring.flyway.user: postgres + spring.flyway.password: password + spring.flyway.url: jdbc:postgresql://host.docker.internal:5440/ + spring.flyway.locations: classpath:/db/migration + +spring: + logging: + level: + root: INFO + org.springframework: INFO + org.springframework.web.filter: INFO + org.stellar: INFO + mvc: + async.request-timeout: 6000 diff --git a/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml new file mode 100644 index 0000000000..dbf1e1f46b --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml @@ -0,0 +1,45 @@ +# Configuration Description - enable to omnibusAllowList feature to check if an account is whitelisted for SEP-10 +version: '2' +services: + anchor-platform-server: + volumes: + # add mounts for the new config directory + - ./anchor-platform-allowlist:/config_override + environment: + - OMNIBUS_ALLOWLIST_KEYS=${OMNIBUS_ALLOWLIST_KEYS?err} + ports: + # override ports, do not append + - "8080:8080" + + anchor-reference-server: + volumes: + # add mounts for the new config directory + - ./anchor-platform-allowlist:/config_override + ports: + # override ports, do not append + - "8081:8081" + + db: + environment: + - PGPORT=5440 + ports: + # override ports, do not append + - "5440:5440" + + zookeeper: + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ports: + - 2181:2181 + + kafka: + ports: + # TODO: might need to change this + - 29092:29092 + environment: + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://host.docker.internal:29092 + + end-to-end-tests: + command: --domain host.docker.internal:8080 --tests omnibus_allowlist \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml new file mode 100644 index 0000000000..b420f8d448 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml @@ -0,0 +1,129 @@ + +stellar: + anchor: + config: in-memory + app-config: + type: config-spring-property + settings: app-config # The absolute location of the configuration data in this yaml file + + data-access: + type: data-spring-jdbc + settings: data-spring-jdbc-local-postgres # use local postgres instance + + logging: + type: logging-logback + settings: logging-logback-settings # The absolute location of the configuration data in this yaml file + +app-config: + app: + stellarNetworkPassphrase: Test SDF Network ; September 2015 + horizonUrl: https://horizon-testnet.stellar.org + hostUrl: http://host.docker.internal:8080 + languages: en + assets: file:config/assets-test.json + + jwtSecretKey: secret + + integration-auth: + authType: JWT_TOKEN + platformToAnchorSecret: myPlatformToAnchorSecret + anchorToPlatformSecret: myAnchorToPlatformSecret + expirationMilliseconds: 30000 + + sep1: + enabled: true + stellarFile: file:config/stellar.toml + + sep10: + enabled: true + homeDomain: host.docker.internal:8080 + clientAttributionRequired: false + clientAttributionAllowList: lobstr.co,preview.lobstr.co + authTimeout: 900 + jwtTimeout: 86400 + + signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X + + omnibusAccountList: + + requireKnownOmnibusAccount: false + + sep12: + enabled: true + customerIntegrationEndpoint: http://host.docker.internal:8081 + + sep24: + enabled: true + interactiveJwtExpiration: 3600 + + interactiveUrl: http://host.docker.internal:8081/sep24/interactive + + sep31: + enabled: true + feeIntegrationEndPoint: http://host.docker.internal:8081 + uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 + paymentType: STRICT_SEND + depositInfoGeneratorType: self # self or circle + + sep38: + enabled: true + quoteIntegrationEndPoint: http://host.docker.internal:8081 + + circle: + circleUrl: https://api-sandbox.circle.com + apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # circle API key + + payment-gateway: + circle: + name: "circle" + enabled: true + + stellar: + enabled: false + name: "stellar" + horizonUrl: https://horizon-testnet.stellar.org + secretKey: secret # stellar account secret key + + circle-payment-observer: + enabled: true + horizonUrl: https://horizon-testnet.stellar.org + stellarNetwork: TESTNET # TESTNET or PUBLIC + trackedWallet: all + + event: + enabled: true + publisherType: kafka + + kafka.publisher: + bootstrapServer: host.docker.internal:29092 + useSingleQueue: false + eventTypeToQueue: + all: ap_event_single_queue + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + +data-spring-jdbc-local-postgres: + spring.jpa.generate-ddl: true + spring.jpa.database: POSTGRESQL + spring.jpa.show-sql: false + spring.datasource.driver-class-name: org.postgresql.Driver + spring.datasource.url: jdbc:postgresql://host.docker.internal:5440/ + spring.datasource.username: postgres + spring.datasource.password: password + spring.mvc.converters.preferred-json-mapper: gson + spring.flyway.user: postgres + spring.flyway.password: password + spring.flyway.url: jdbc:postgresql://host.docker.internal:5440/ + spring.flyway.locations: classpath:/db/migration + +spring: + logging: + level: + root: INFO + org.springframework: INFO + org.springframework.web.filter: INFO + org.stellar: INFO + mvc: + async.request-timeout: 6000 diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-reference-server-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-reference-server-config.yaml new file mode 100644 index 0000000000..d02a540678 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-reference-server-config.yaml @@ -0,0 +1,43 @@ +server: + servlet: + context-path: / + port: 8081 + +# +# The settings of the anchor reference serer +# +anchor.settings: + version: 0.0.1 + platformApiEndpoint: http://host.docker.internal:8080 + hostUrl: http://host.docker.internal:8081 + +integration-auth: + authType: JWT_TOKEN + platformToAnchorSecret: myPlatformToAnchorSecret + anchorToPlatformSecret: myAnchorToPlatformSecret + expirationMilliseconds: 30000 + +event: + listenerType: kafka + +kafka.listener: + bootstrapServer: host.docker.internal:29092 + useSingleQueue: false + eventTypeToQueue: + all: ap_event_single_queue + quoteCreated: ap_event_quote_created + transactionCreated: ap_event_transaction_created + transactionStatusChanged: ap_event_transaction_status_changed + transactionError: ap_event_transaction_error + +amqp.listener: + endpoint: host.docker.internal:5672 +# +# Spring Data JDBC settings for H2 +# +spring.datasource.url: jdbc:h2:mem:test +spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.H2Dialect +spring.flyway.enabled: false + +# force gson as serializer/deserializer over jackson +spring.mvc.converters.preferred-json-mapper: gson diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/assets-test.json b/integration-tests/docker-compose-configs/anchor-platform-default-configs/assets-test.json new file mode 100644 index 0000000000..b24db2d6ca --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/assets-test.json @@ -0,0 +1,213 @@ +{ + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 2, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving USD" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving USD" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "stellar", + "code": "JPYC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 4, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving JPY" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving JPY" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": false, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "iso4217", + "code": "USD", + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "sep38": { + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "country_codes": ["USA"], + "decimals": 4, + "sell_delivery_methods": [ + { + "name": "WIRE", + "description": "Send USD directly to the Anchor's bank account." + } + ], + "buy_delivery_methods": [ + { + "name": "WIRE", + "description": "Have USD sent directly to your bank account." + } + ] + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38_enabled": true + } + ] +} diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-default-configs/docker-compose-config.override.yaml new file mode 100644 index 0000000000..e48daed593 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/docker-compose-config.override.yaml @@ -0,0 +1,36 @@ +# Configuration Description - default Anchor Platform configuration, also used as a template for new configurations +version: '2' +services: + anchor-platform-server: + ports: + # override ports, do not append + - "8080:8080" + anchor-reference-server: + ports: + # override ports, do not append + - "8081:8081" + + db: + environment: + - PGPORT=5440 + ports: + # override ports, do not append + - "5440:5440" + + zookeeper: + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ports: + - 2181:2181 + + kafka: + ports: + # TODO: might need to change this + - 29092:29092 + environment: + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://host.docker.internal:29092 + + end-to-end-tests: + command: --domain host.docker.internal:8080 --tests sep31_flow --tests sep31_flow_with_sep38 \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/stellar.toml b/integration-tests/docker-compose-configs/anchor-platform-default-configs/stellar.toml new file mode 100644 index 0000000000..90a6969293 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/stellar.toml @@ -0,0 +1,26 @@ +ACCOUNTS = [] +VERSION = "0.1.0" +NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" +SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + +WEB_AUTH_ENDPOINT = "http://host.docker.internal:8080/auth" +KYC_SERVER = "http://host.docker.internal:8080/sep12" +TRANSFER_SERVER_SEP0024 = "http://host.docker.internal:8080/sep24" +DIRECT_PAYMENT_SERVER = "http://host.docker.internal:8080/sep31" +ANCHOR_QUOTE_SERVER = "http://host.docker.internal:8080/sep38" + +[[CURRENCIES]] +code = "USDC" +issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" +status = "test" +is_asset_anchored = true +anchor_asset_type = "fiat" +desc = "A test USDC issued by Stellar." + +[[CURRENCIES]] +code = "JPYC" +issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" +status = "test" +is_asset_anchored = true +anchor_asset_type = "fiat" +desc = "A test JPYC issued by Stellar." \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml new file mode 100644 index 0000000000..92e370dda9 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml @@ -0,0 +1,134 @@ + +stellar: + anchor: + config: in-memory + app-config: + type: config-spring-property + settings: app-config # The absolute location of the configuration data in this yaml file + + data-access: + type: data-spring-jdbc + settings: data-spring-jdbc-local-postgres # use local postgres instance + + logging: + type: logging-logback + settings: logging-logback-settings # The absolute location of the configuration data in this yaml file + +# ************************************ +# Application settings +# ************************************ +app-config: + # shared / general application configurations + app: + stellarNetworkPassphrase: Test SDF Network ; September 2015 + horizonUrl: https://horizon-testnet.stellar.org + hostUrl: http://host.docker.internal:8080 + languages: en + assets: file:config/assets-test.json + + jwtSecretKey: secret + + integration-auth: + authType: JWT_TOKEN + platformToAnchorSecret: myPlatformToAnchorSecret + anchorToPlatformSecret: myAnchorToPlatformSecret + expirationMilliseconds: 30000 + + sep1: + enabled: true + stellarFile: file:config/stellar.toml + + # sep-10 + sep10: + enabled: true + homeDomain: host.docker.internal:8080 + clientAttributionRequired: false + clientAttributionAllowList: lobstr.co,preview.lobstr.co + authTimeout: 900 + jwtTimeout: 86400 + + signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X + + omnibusAccountList: + + requireKnownOmnibusAccount: false + + sep12: + enabled: true + customerIntegrationEndpoint: http://host.docker.internal:8081 + + sep24: + enabled: true + interactiveJwtExpiration: 3600 + + interactiveUrl: http://host.docker.internal:8081/sep24/interactive + + sep31: + enabled: true + feeIntegrationEndPoint: http://host.docker.internal:8081 + uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 + paymentType: STRICT_SEND + depositInfoGeneratorType: api # self or circle or api + + sep38: + enabled: true + quoteIntegrationEndPoint: http://host.docker.internal:8081 + + circle: + circleUrl: https://api-sandbox.circle.com + apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # circle API key + + payment-gateway: + circle: + name: "circle" + enabled: true + + stellar: + enabled: false + name: "stellar" + horizonUrl: https://horizon-testnet.stellar.org + secretKey: secret # stellar account secret key + + circle-payment-observer: + enabled: true + horizonUrl: https://horizon-testnet.stellar.org + stellarNetwork: TESTNET # TESTNET or PUBLIC + trackedWallet: all + + event: + enabled: true + publisherType: kafka + + kafka.publisher: + bootstrapServer: host.docker.internal:29092 + useSingleQueue: false + eventTypeToQueue: + all: ap_event_single_queue + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + +data-spring-jdbc-local-postgres: + spring.jpa.generate-ddl: true + spring.jpa.database: POSTGRESQL + spring.jpa.show-sql: false + spring.datasource.driver-class-name: org.postgresql.Driver + spring.datasource.url: jdbc:postgresql://host.docker.internal:5440/ + spring.datasource.username: postgres + spring.datasource.password: password + spring.mvc.converters.preferred-json-mapper: gson + spring.flyway.user: postgres + spring.flyway.password: password + spring.flyway.url: jdbc:postgresql://host.docker.internal:5440/ + spring.flyway.locations: classpath:/db/migration + +spring: + logging: + level: + root: INFO + org.springframework: INFO + org.springframework.web.filter: INFO + org.stellar: INFO + mvc: + async.request-timeout: 6000 diff --git a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-reference-server-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-reference-server-config.yaml new file mode 100644 index 0000000000..1910c7f0b5 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-reference-server-config.yaml @@ -0,0 +1,48 @@ +server: + servlet: + context-path: / + port: 8081 + +# +# The settings of the anchor reference serer +# +anchor.settings: + version: 0.0.1 + platformApiEndpoint: http://host.docker.internal:8080 + hostUrl: http://host.docker.internal:8081 + + # The Stellar wallet to which the customer will send the Stellar assets. + distributionWallet: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF + distributionWalletMemo: + distributionWalletMemoType: + +integration-auth: + authType: JWT_TOKEN + platformToAnchorSecret: myPlatformToAnchorSecret + anchorToPlatformSecret: myAnchorToPlatformSecret + expirationMilliseconds: 30000 + +event: + listenerType: kafka + +kafka.listener: + bootstrapServer: host.docker.internal:29092 + useSingleQueue: false + eventTypeToQueue: + all: ap_event_single_queue + quoteCreated: ap_event_quote_created + transactionCreated: ap_event_transaction_created + transactionStatusChanged: ap_event_transaction_status_changed + transactionError: ap_event_transaction_error + +amqp.listener: + endpoint: host.docker.internal:5672 +# +# Spring Data JDBC settings for H2 +# +spring.datasource.url: jdbc:h2:mem:test +spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.H2Dialect +spring.flyway.enabled: false + +# force gson as serializer/deserializer over jackson +spring.mvc.converters.preferred-json-mapper: gson diff --git a/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml new file mode 100644 index 0000000000..cc6aee7cbe --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml @@ -0,0 +1,46 @@ +# Configuration Description - set depositInfoGeneratorType: api, this will tell Anchor Platform to contact the +# Anchor Reference Server for a destination account/memo to be used in a SEP-31 transaction +# - set distributionWallet: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF, in +# the Anchor Reference Server +version: '2' +services: + anchor-platform-server: + volumes: + # add mounts for the new config directory + - ./anchor-platform-unique-address:/config_override + ports: + # override ports, do not append + - "8080:8080" + + anchor-reference-server: + volumes: + # add mounts for the new config directory + - ./anchor-platform-unique-address:/config_override + ports: + # override ports, do not append + - "8081:8081" + + db: + environment: + - PGPORT=5440 + ports: + # override ports, do not append + - "5440:5440" + + zookeeper: + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_TICK_TIME: 2000 + ports: + - 2181:2181 + + kafka: + ports: + # TODO: might need to change this + - 29092:29092 + environment: + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://host.docker.internal:29092 + + end-to-end-tests: + command: --domain host.docker.internal:8080 --tests sep31_flow --tests sep31_flow_with_sep38 \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/docker-compose.base.yaml b/integration-tests/docker-compose-configs/docker-compose.base.yaml new file mode 100644 index 0000000000..d707b069a0 --- /dev/null +++ b/integration-tests/docker-compose-configs/docker-compose.base.yaml @@ -0,0 +1,71 @@ +version: '2' +services: + anchor-platform-server: + build: + context: ../../ + dockerfile: integration-tests/docker-compose-configs/Dockerfile + entrypoint: ["/bin/sh", "-c" , + "cp -rf /config_defaults/* /config + && if [ -d \"/config_override\" ]; then cp -rf /config_override/* /config; fi + && java -jar /app/anchor-platform-runner.jar --sep-server"] + environment: + - STELLAR_ANCHOR_CONFIG=file:/config/anchor-platform-config.yaml + - LOG_APPENDER=console_appender + volumes: + # add mounts for the new config directory + - ./anchor-platform-default-configs:/config_defaults + tmpfs: + - /config + depends_on: + - db + - kafka + + anchor-reference-server: + build: + context: ../../ + dockerfile: integration-tests/docker-compose-configs/Dockerfile + entrypoint: [ "/bin/sh", "-c" , + "cp -rf /config_defaults/* /config + && if [ -d \"/config_override\" ]; then cp -rf /config_override/* /config; fi + && java -jar /app/anchor-platform-runner.jar --anchor-reference-server" ] + environment: + - REFERENCE_SERVER_CONFIG_ENV=file:/config/anchor-reference-server-config.yaml + volumes: + # add mounts for the new config directory + - ./anchor-platform-default-configs:/config_defaults + tmpfs: + - /config + depends_on: + - db + - kafka + + db: + image: postgres:latest + environment: + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + + zookeeper: + platform: linux/x86_64 + image: confluentinc/cp-zookeeper:latest + + kafka: + platform: linux/x86_64 + image: confluentinc/cp-kafka:latest + depends_on: + - zookeeper + environment: + KAFKA_BROKER_ID: 1 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + + end-to-end-tests: + build: + context: ../../ + dockerfile: end-to-end-tests/Dockerfile + depends_on: + - anchor-platform-server + - anchor-reference-server + environment: + - E2E_SECRET=${E2E_SECRET?err} \ No newline at end of file diff --git a/platform/src/main/resources/anchor-config-defaults.yaml b/platform/src/main/resources/anchor-config-defaults.yaml index 67e0d16c9e..964cafbfbe 100644 --- a/platform/src/main/resources/anchor-config-defaults.yaml +++ b/platform/src/main/resources/anchor-config-defaults.yaml @@ -147,8 +147,11 @@ app-config: # network. signingSeed: ${SEP10_SIGNING_SEED} - # The list of omnibus account. + # The list of omnibus accounts (comma separated). # The detail of the SEP-10 omnibus account is described at: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#memos + # omnibusAccountList: | + # GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAV, + # GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAA omnibusAccountList: # Require authenticating clients to be in the omnibusAccountList From f2cf545a6ef50003dcf9fae0042d0b38474e0f54 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 29 Aug 2022 15:14:37 -0300 Subject: [PATCH 39/65] Hotfix: update docs to say SEP-24 is not supported (#531) ### What Update docs to say SEP-24 is not supported ### Why Despite we having some SEP-24 code in the project, it's not released yet and we need to add a ton more tests to state it's ready for implementation. --- docs/00 - Stellar Anchor Platform.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index 1ab2320981..ac6af6dcff 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -59,7 +59,7 @@ Here are the important terminology used in this project: | :------: | :----------------------------------------------------------------------------: | :----------: | :--------------------------: | :---------------------------: | :------------------: | | [SEP-10] | Handles authentication. | YES | NO | YES | YES | | [SEP-12] | Handles KYC. | YES | YES | YES | YES | - | [SEP-24] | Handles deposit & withdrawal of assets in/out the Stellar network. | YES | NO | NO | YES | + | [SEP-24] | Handles deposit & withdrawal of assets in/out the Stellar network. | NO | NO | NO | NO | | [SEP-31] | Used for international remittances. **Only the receiver side is implemented.** | YES | YES | YES | YES | | [SEP-38] | Used for [rfq] **in conjunction with [SEP-31]**. | YES | YES | YES | YES | From ee4408c8eca1c8a0ac6e6a374aabd09b6d3689e1 Mon Sep 17 00:00:00 2001 From: Stephen Date: Mon, 29 Aug 2022 16:13:22 -0400 Subject: [PATCH 40/65] remove jpa.generate-ddl from postgres config (#532) - remove jpa.generate-ddl from the postgres database configs - update docker-compose integration tests to use the same image (dont need to build both) for anchor platform and reference server --- ...reference-server-docker-compose-config.yaml | 1 - .../resources/anchor-reference-server.yaml | 1 - .../anchor-platform-dev/anchor-config.yaml | 1 - helm-charts/sep-service/example_values.yaml | 1 - .../docker-compose-configs/Dockerfile | 5 ++++- .../anchor-platform-config.yaml | 1 - .../anchor-platform-config.yaml | 1 - .../anchor-platform-config.yaml | 1 - .../docker-compose.base.yaml | 18 +++++------------- .../docker-entrypoint.sh | 10 ++++++++++ .../main/resources/anchor-config-defaults.yaml | 2 -- .../anchor-docker-compose-config.yaml | 4 +--- 12 files changed, 20 insertions(+), 26 deletions(-) create mode 100644 integration-tests/docker-compose-configs/docker-entrypoint.sh diff --git a/anchor-reference-server/src/main/resources/anchor-reference-server-docker-compose-config.yaml b/anchor-reference-server/src/main/resources/anchor-reference-server-docker-compose-config.yaml index 3a777e9127..7691db4847 100644 --- a/anchor-reference-server/src/main/resources/anchor-reference-server-docker-compose-config.yaml +++ b/anchor-reference-server/src/main/resources/anchor-reference-server-docker-compose-config.yaml @@ -72,7 +72,6 @@ spring.mvc.converters.preferred-json-mapper: gson # # Spring Data JDBC settings for Postgres # -#spring.jpa.generate-ddl: true #spring.jpa.database: POSTGRESQL #spring.jpa.show-sql: false #spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml index 7012efb9a8..d6a6dd5d54 100644 --- a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml +++ b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml @@ -97,7 +97,6 @@ spring.mvc.converters.preferred-json-mapper: gson # # Spring Data JDBC settings for Postgres # -#spring.jpa.generate-ddl: true #spring.jpa.database: POSTGRESQL #spring.jpa.show-sql: false #spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/anchor-config.yaml b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/anchor-config.yaml index 00624da40d..9acabff8be 100644 --- a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/anchor-config.yaml +++ b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/anchor-config.yaml @@ -123,7 +123,6 @@ anchor-config.yaml: | data-spring-jdbc-sqlite: spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect spring.jpa.hibernate.ddl-auto: update - spring.jpa.generate-ddl: true spring.jpa.hibernate.show_sql: false spring.datasource.url: jdbc:sqlite:anchor-proxy.db spring.datasource.driver-class-name: org.sqlite.JDBC diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index ab443c38de..4800c063c8 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -74,7 +74,6 @@ stellar: settings: data-spring-jdbc-sqlite # settings: data-spring-jdbc-aws-aurora-postgres # data_spring_jdbc_aws_aurora_postgres: -# spring.jpa.generate-ddl: true # spring.jpa.database: POSTGRESQL # spring.jpa.show-sql: false # spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/integration-tests/docker-compose-configs/Dockerfile b/integration-tests/docker-compose-configs/Dockerfile index ed3e6bf602..4babd040be 100644 --- a/integration-tests/docker-compose-configs/Dockerfile +++ b/integration-tests/docker-compose-configs/Dockerfile @@ -11,4 +11,7 @@ RUN apt-get update && \ COPY --from=build /code/service-runner/build/libs/anchor-platform-runner*.jar /app/anchor-platform-runner.jar -ENTRYPOINT ["java", "-jar", "/app/anchor-platform-runner.jar"] +COPY --from=build /code/integration-tests/docker-compose-configs/docker-entrypoint.sh /app/docker-entrypoint.sh +RUN chmod +x /app/docker-entrypoint.sh + +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml index bd6ae05e05..17049e7ad6 100644 --- a/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml +++ b/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml @@ -109,7 +109,6 @@ app-config: transaction_error: ap_event_transaction_error data-spring-jdbc-local-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml index b420f8d448..a9dbe71f86 100644 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml @@ -105,7 +105,6 @@ app-config: transaction_error: ap_event_transaction_error data-spring-jdbc-local-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml index 92e370dda9..47b1acf039 100644 --- a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml +++ b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml @@ -110,7 +110,6 @@ app-config: transaction_error: ap_event_transaction_error data-spring-jdbc-local-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/integration-tests/docker-compose-configs/docker-compose.base.yaml b/integration-tests/docker-compose-configs/docker-compose.base.yaml index d707b069a0..bf7988e3a9 100644 --- a/integration-tests/docker-compose-configs/docker-compose.base.yaml +++ b/integration-tests/docker-compose-configs/docker-compose.base.yaml @@ -1,13 +1,11 @@ version: '2' services: anchor-platform-server: + image: anchor-platform build: context: ../../ dockerfile: integration-tests/docker-compose-configs/Dockerfile - entrypoint: ["/bin/sh", "-c" , - "cp -rf /config_defaults/* /config - && if [ -d \"/config_override\" ]; then cp -rf /config_override/* /config; fi - && java -jar /app/anchor-platform-runner.jar --sep-server"] + command: "--sep-server" environment: - STELLAR_ANCHOR_CONFIG=file:/config/anchor-platform-config.yaml - LOG_APPENDER=console_appender @@ -21,13 +19,8 @@ services: - kafka anchor-reference-server: - build: - context: ../../ - dockerfile: integration-tests/docker-compose-configs/Dockerfile - entrypoint: [ "/bin/sh", "-c" , - "cp -rf /config_defaults/* /config - && if [ -d \"/config_override\" ]; then cp -rf /config_override/* /config; fi - && java -jar /app/anchor-platform-runner.jar --anchor-reference-server" ] + image: anchor-platform + command: --anchor-reference-server environment: - REFERENCE_SERVER_CONFIG_ENV=file:/config/anchor-reference-server-config.yaml volumes: @@ -36,8 +29,7 @@ services: tmpfs: - /config depends_on: - - db - - kafka + - anchor-platform-server db: image: postgres:latest diff --git a/integration-tests/docker-compose-configs/docker-entrypoint.sh b/integration-tests/docker-compose-configs/docker-entrypoint.sh new file mode 100644 index 0000000000..01bf67de49 --- /dev/null +++ b/integration-tests/docker-compose-configs/docker-entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash +cp -rf /config_defaults/* /config + +# replace default files in /config with override files +if [ -d \"/config_override\" ]; + then + cp -rf /config_override/* /config; +fi + +java -jar /app/anchor-platform-runner.jar $1 \ No newline at end of file diff --git a/platform/src/main/resources/anchor-config-defaults.yaml b/platform/src/main/resources/anchor-config-defaults.yaml index 964cafbfbe..9a53bb36aa 100644 --- a/platform/src/main/resources/anchor-config-defaults.yaml +++ b/platform/src/main/resources/anchor-config-defaults.yaml @@ -304,7 +304,6 @@ data-spring-jdbc-sqlite: # settings for aurora postgres connection using IAM for authentication # NOTE: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables need to be set data-spring-jdbc-aws-aurora-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver @@ -319,7 +318,6 @@ data-spring-jdbc-aws-aurora-postgres: spring.flyway.locations: classpath:/db/migration data-spring-jdbc-local-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 9f6047966d..79915baf30 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -284,10 +284,9 @@ data-spring-jdbc-sqlite: spring.datasource.username: admin spring.datasource.password: admin spring.mvc.converters.preferred-json-mapper: gson - spring.liquibase.enabled: false # certain features that liquibase uses (ex: addForeignKeyConstraint) are not supported by sqlite + spring.flyway.enabled: false # certain features that liquibase uses (ex: addForeignKeyConstraint) are not supported by sqlite data-spring-jdbc-aws-aurora-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver @@ -298,7 +297,6 @@ data-spring-jdbc-aws-aurora-postgres: spring.liquibase.change-log: classpath:/db/changelog/db.changelog-master.yaml data-spring-jdbc-local-postgres: - spring.jpa.generate-ddl: true spring.jpa.database: POSTGRESQL spring.jpa.show-sql: false spring.datasource.driver-class-name: org.postgresql.Driver From f495d9f230a3b98c28e511ceb0050a623e58b9e4 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 30 Aug 2022 15:16:38 -0300 Subject: [PATCH 41/65] Feature: add default issue templates for bug reports, feature requests and some useful links (#534) ### What Add default issue templates for bug reports, feature requests and some useful links. ### Why To influence the community to share the relevant information we'll need to investigate bugs and evaluate feature requests. --- .github/ISSUE_TEMPLATE/bug_report.md | 21 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 +++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 18 ++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000..8dfc2a762e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,21 @@ +--- +name: Bug report +about: Tell us about a bug you found +title: '' +labels: bug + +--- + + + +### What version are you using? + + +### What did you do? + + +### What happened? + + +### What did you expect to see instead? + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..685f2ab0f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Stellar Ecosystem Proposals (SEPs) + url: https://github.com/stellar/stellar-protocol + about: The SEPs implemented in this project are defined here. + - name: Stellar Laboratory + url: https://laboratory.stellar.org/#?network=test + about: The best place to experiment with the Stellar network. + - name: Docker Images + url: https://hub.docker.com/r/stellar/anchor-platform + about: Where to check the available Docker images that have been published. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..22343e37d7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Tell us what you'd like to see +title: 'Feature Request: ' +labels: 'feature request' + +--- + + + +### What problem does your feature solve? + + +### What would you like to see? + + +### What alternatives are there? + From 506410130a4ad40dd1655f278dc97d473bb4365d Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 30 Aug 2022 18:09:14 -0300 Subject: [PATCH 42/65] Feature: configure CI for the Gitflow release process (#533) ### What Configure CI for the Gitflow release process. Currently, we're only handling Docker releases. Here's how it will work: * [`.github/workflows/basic_tests.yml`](https://github.com/stellar/java-stellar-anchor-sdk/blob/5e1f90378fa87561c645089adb9143afbadd22df/.github/workflows/basic_tests.yml) * **When is it executed?** * On every PR * When code is merged into `develop`, `release/**` or `main`. * **What it does?** * Checks if the code is formatted. * Runs the unit tests. * Runs the embedded integration tests. * Tests the SEP endpoints with the node tool [stellar/stellar-anchor-tests](https://github.com/stellar/stellar-anchor-tests) * [`.github/workflows/release_develop.yml`](https://github.com/stellar/java-stellar-anchor-sdk/blob/5e1f90378fa87561c645089adb9143afbadd22df/.github/workflows/release_develop.yml) * **When is it executed?** * When code is merged into `develop`. * **What it does?** * Run the `basic_tests.yml`. * Deploy an image to [Docker Hub](https://hub.docker.com/r/stellar/anchor-platform) using the `edge` and the commit sha as Docker tags. * [`.github/workflows/release_release.yml`](https://github.com/stellar/java-stellar-anchor-sdk/blob/5e1f90378fa87561c645089adb9143afbadd22df/.github/workflows/release_release.yml) * **When is it executed?** * When code is merged into `release/**`. * **What it does?** * Run the `basic_tests.yml`. * Deploy an image to [Docker Hub](https://hub.docker.com/r/stellar/anchor-platform) using the `-edge` Docker tag, e.g. `1.1.0-edge`. * [`.github/workflows/release.yml`](https://github.com/stellar/java-stellar-anchor-sdk/blob/feature/configure-ci-releases/.github/workflows/release.yml) * **When is it executed?** * When a new release is created and published. * **What it does?** * Run the `basic_tests.yml`. * Deploy an image to [Docker Hub](https://hub.docker.com/r/stellar/anchor-platform) using the `latest` and the release GH tag as Docker tags. ### Why As part of #487 --- .../{build_and_test.yml => basic_tests.yml} | 36 ++++----------- .github/workflows/release.yml | 41 +++++++++++++++++ .github/workflows/release_develop.yml | 43 ++++++++++++++++++ .github/workflows/release_release.yml | 44 +++++++++++++++++++ build.gradle.kts | 6 ++- core/build.gradle.kts | 2 +- 6 files changed, 142 insertions(+), 30 deletions(-) rename .github/workflows/{build_and_test.yml => basic_tests.yml} (74%) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/release_develop.yml create mode 100644 .github/workflows/release_release.yml diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/basic_tests.yml similarity index 74% rename from .github/workflows/build_and_test.yml rename to .github/workflows/basic_tests.yml index 275159ad30..e3f02d0aba 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/basic_tests.yml @@ -1,12 +1,16 @@ # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven -name: Java CI with Gradle +name: Basic Tests on: push: - branches: [ develop ] + branches: + - develop + - main + - 'release/**' pull_request: + workflow_call: # Allows this workflow to be called from another workflow jobs: build_and_test: @@ -18,8 +22,10 @@ jobs: with: java-version: '11' distribution: 'adopt' + - name: Check code formatting run: ./gradlew spotlessCheck || echo "❌ Your code is not properly formatted. You can run './gradlew spotlessApply' to format it. 👀" + - name: Clean, Build and Test run: | ./gradlew clean build @@ -30,32 +36,6 @@ jobs: echo *** Anchor reference server test report *** cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/anchor-reference-server/build/reports/tests/test/index.html - build_and_push_anchor_platform: - if: github.event.pull_request == null - needs: [build_and_test] - runs-on: ubuntu-latest - name: Push stellar/anchor-platform:sha to DockerHub - steps: - - uses: actions/checkout@v3 - - - name: Get image tag (short sha) - shell: bash - id: get_tag - run: echo ::set-output name=TAG::$(git rev-parse --short ${{ github.sha }} ) - - - name: Login to DockerHub - uses: docker/login-action@bb984efc561711aaa26e433c32c3521176eae55b - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push to DockerHub - uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b - with: - push: true - tags: stellar/anchor-platform:${{ steps.get_tag.outputs.TAG }},stellar/anchor-platform:latest - file: Dockerfile - sep_validation_suite: needs: [build_and_test] runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..d84cdb6b5b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Release + +on: + release: + types: + - published + +jobs: + tests: + uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests + + build_and_push_docker_image: + needs: [tests] + runs-on: ubuntu-latest + name: Push stellar/anchor-platform:VERSION to DockerHub + steps: + - uses: actions/checkout@v3 + + - name: Login to DockerHub + uses: docker/login-action@bb984efc561711aaa26e433c32c3521176eae55b + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push to DockerHub + uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b + with: + push: true + tags: stellar/anchor-platform:${{ GITHUB_REF_NAME }},stellar/anchor-platform:latest + file: Dockerfile + + complete: + if: always() + needs: [tests, build_and_push_docker_image] + runs-on: ubuntu-latest + steps: + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/release_develop.yml b/.github/workflows/release_develop.yml new file mode 100644 index 0000000000..ff21bac870 --- /dev/null +++ b/.github/workflows/release_develop.yml @@ -0,0 +1,43 @@ +name: "Release from the Develop Branch" + +on: + push: + branches: + - develop + +jobs: + tests: + uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests + + build_and_push_docker_image: + needs: [tests] + runs-on: ubuntu-latest + name: Push stellar/anchor-platform:sha to DockerHub + steps: + - uses: actions/checkout@v3 + + - name: Get image SHA + shell: bash + id: get_sha + run: echo ::set-output name=SHA::$(git rev-parse --short ${{ github.sha }} ) + + - name: Login to DockerHub + uses: docker/login-action@bb984efc561711aaa26e433c32c3521176eae55b + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push to DockerHub + uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b + with: + push: true + tags: stellar/anchor-platform:${{ steps.get_sha.outputs.SHA }},stellar/anchor-platform:edge + file: Dockerfile + + complete: + if: always() + needs: [tests, build_and_push_docker_image] + runs-on: ubuntu-latest + steps: + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 \ No newline at end of file diff --git a/.github/workflows/release_release.yml b/.github/workflows/release_release.yml new file mode 100644 index 0000000000..61d56113a3 --- /dev/null +++ b/.github/workflows/release_release.yml @@ -0,0 +1,44 @@ +name: "Release from the release/** Branch" + +on: + push: + branches: + - 'release/**' + - 'releases/**' + +jobs: + tests: + uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests + + build_and_push_docker_image: + needs: [tests] + runs-on: ubuntu-latest + name: Push stellar/anchor-platform:sha to DockerHub + steps: + - uses: actions/checkout@v3 + + - name : Get Project Version + shell: bash + id: get_version + run: echo ::set-output name=VERSION::$(${{github.workspace}}/gradlew -q printVersionName) + + - name: Login to DockerHub + uses: docker/login-action@bb984efc561711aaa26e433c32c3521176eae55b + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push to DockerHub + uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b + with: + push: true + tags: stellar/anchor-platform:${{ steps.get_version.outputs.VERSION }}-edge + file: Dockerfile + + complete: + if: always() + needs: [tests, build_and_push_docker_image] + runs-on: ubuntu-latest + steps: + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index a15eeffa87..a26c0db6e4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -110,7 +110,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "1.2.3" + version = "1.1.0" tasks.jar { manifest { @@ -124,3 +124,7 @@ allprojects { } } +tasks.register("printVersionName") { + println(rootProject.version.toString()) +} + diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 1d5bfeac9d..a9da76c5fb 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -62,7 +62,7 @@ publishing { pom { name.set("stellar-anchor-sdk") description.set("Stellar Anchor SDK - Java") - url.set("https://www.github.com/stellar/java-stellar-anchor-sdk") + url.set("https://github.com/stellar/java-stellar-anchor-sdk") licenses { license { name.set("Apache 2.0") From a1fecfee1557e3a091ba4045e82711993b6bb587 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Wed, 31 Aug 2022 14:59:33 -0300 Subject: [PATCH 43/65] Fix: some CI jobs were running twice (#539) ### What Update the triggers from one workflow, since that workflow was being called from other workflows. ### Why Because the jobs from basic_tests.yml were being executed twice in the tests from https://github.com/stellar/java-stellar-anchor-sdk/commit/506410130a4ad40dd1655f278dc97d473bb4365d. --- .github/workflows/basic_tests.yml | 5 ----- .github/workflows/{release.yml => release_main.yml} | 0 Makefile | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) rename .github/workflows/{release.yml => release_main.yml} (100%) diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index e3f02d0aba..2b7b18d0a7 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -4,11 +4,6 @@ name: Basic Tests on: - push: - branches: - - develop - - main - - 'release/**' pull_request: workflow_call: # Allows this workflow to be called from another workflow diff --git a/.github/workflows/release.yml b/.github/workflows/release_main.yml similarity index 100% rename from .github/workflows/release.yml rename to .github/workflows/release_main.yml diff --git a/Makefile b/Makefile index fd5ae3ed17..c79a406905 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ define run_tests $(SUDO) docker-compose --env-file integration-tests/docker-compose-configs/.env \ -f integration-tests/docker-compose-configs/docker-compose.base.yaml \ -f integration-tests/docker-compose-configs/$(1)/docker-compose-config.override.yaml \ - up --exit-code-from end-to-end-tests || echo "E2E Test Failed: $(1)" + up --exit-code-from end-to-end-tests || (echo "E2E Test Failed: $(1)" && exit 1) endef run-e2e-test-default-config: From da093f154ee0d5bf566cdb5b482f8d41a3b57451 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Wed, 31 Aug 2022 16:06:53 -0300 Subject: [PATCH 44/65] Feature: update how the platform deals with incoming payments with insufficient amounts (#529) ### What Update how the platform deals with incoming payments with insufficient amounts. The changes include: * Update the Sep31Transaction schema to hold the list of StellarTransactions. This way, we make sure to keep track of all incoming payments even when they don't fully match the criteria. * Update JdbcSep31Transaction to store the stellar transactions as a json, instead of a join table * Update the Platform API design schema. * Unify similar objects * Delete unused objects ### Why We were not handling the use case where the incoming payment amount is smaller than expected. --- .../api/platform/GetTransactionResponse.java | 34 +-- .../anchor/api/shared/StellarPayment.java | 67 +++++ .../anchor/api/shared/StellarTransaction.java | 58 ++++ .../anchor/api/shared/StellarPaymentTest.kt | 81 ++++++ .../api/shared/StellarTransactionTest.kt | 250 ++++++++++++++++++ .../stellar/anchor/event/models/Payment.java | 29 -- .../event/models/StellarTransaction.java | 29 -- .../anchor/event/models/TransactionEvent.java | 9 +- .../anchor/sep31/Sep31Transaction.java | 21 +- .../anchor/sep31/Sep31TransactionBuilder.java | 7 + .../anchor/sep31/PojoSep31Transaction.java | 3 + .../anchor/sep31/Sep31TransactionTest.kt | 27 +- .../Communication/Platform API.yml | 25 +- gradle/libs.versions.toml | 2 + platform/build.gradle.kts | 1 + .../platform/data/JdbcSep31Transaction.java | 24 +- .../platform/data/StellarTransaction.java | 53 ---- .../observer/stellar/StellarPayment.java | 19 -- .../observer/stellar/StellarTransaction.java | 22 -- .../PaymentOperationToEventListener.java | 183 ++++++------- ...now_store_stellar_transactions_as_json.sql | 7 + .../PaymentOperationToEventListenerTest.kt | 224 +++++++++++++--- .../service/TransactionServiceTest.kt | 27 +- 23 files changed, 840 insertions(+), 362 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/shared/StellarPayment.java create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/shared/StellarTransaction.java create mode 100644 api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarPaymentTest.kt create mode 100644 api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarTransactionTest.kt delete mode 100644 core/src/main/java/org/stellar/anchor/event/models/Payment.java delete mode 100644 core/src/main/java/org/stellar/anchor/event/models/StellarTransaction.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/StellarTransaction.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java create mode 100644 platform/src/main/resources/db/migration/V5__sep31_transaction_now_store_stellar_transactions_as_json.sql diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java index 2871377b14..1418d468c7 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/GetTransactionResponse.java @@ -6,10 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; -import org.stellar.anchor.api.shared.Amount; -import org.stellar.anchor.api.shared.Customers; -import org.stellar.anchor.api.shared.Refund; -import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.api.shared.*; @Data @SuperBuilder @@ -61,33 +58,4 @@ public class GetTransactionResponse { Customers customers; StellarId creator; - - @Data - public static class StellarTransaction { - String id; - String memo; - - @SerializedName("memo_type") - String memoType; - - @SerializedName("created_at") - Instant createdAt; - - String envelope; - Payment payment; - } - - @Data - public static class Payment { - @SerializedName("operation_id") - String operationId; - - @SerializedName("source_account") - String sourceAccount; - - @SerializedName("destination_account") - String destinationAccount; - - Amount amount; - } } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarPayment.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarPayment.java new file mode 100644 index 0000000000..591444c866 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarPayment.java @@ -0,0 +1,67 @@ +package org.stellar.anchor.api.shared; + +import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class StellarPayment { + String id; + + Amount amount; + + @SerializedName("payment_type") + Type paymentType; + + @SerializedName("source_account") + String sourceAccount; + + @SerializedName("destination_account") + String destinationAccount; + + public enum Type { + @SerializedName("payment") + PAYMENT("payment"), + + @SerializedName("path_payment") + PATH_PAYMENT("path_payment"); + + private final String name; + + Type(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + public static List addOrUpdatePayments( + List paymentList, StellarPayment... newPayments) { + HashMap paymentHashMap = new HashMap<>(); + + if (paymentList != null) { + for (StellarPayment p : paymentList) { + paymentHashMap.put(p.getId(), p); + } + } + + if (newPayments != null) { + for (StellarPayment p : newPayments) { + paymentHashMap.put(p.getId(), p); + } + } + + return new ArrayList<>(paymentHashMap.values()); + } +} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarTransaction.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarTransaction.java new file mode 100644 index 0000000000..70fa6b72e2 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/StellarTransaction.java @@ -0,0 +1,58 @@ +package org.stellar.anchor.api.shared; + +import com.google.gson.annotations.SerializedName; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class StellarTransaction { + String id; + String memo; + + @SerializedName("memo_type") + String memoType; + + @SerializedName("created_at") + Instant createdAt; + + String envelope; + + List payments; + + public static List addOrUpdateTransactions( + List transactionList, StellarTransaction... newTransactions) { + HashMap txHashMap = new HashMap<>(); + + if (transactionList != null) { + for (StellarTransaction tx : transactionList) { + txHashMap.put(tx.getId(), tx); + } + } + + if (newTransactions != null) { + for (StellarTransaction tx : newTransactions) { + List newPayments = tx.getPayments(); + if (txHashMap.containsKey(tx.getId())) { + StellarTransaction existingTx = txHashMap.get(tx.getId()); + newPayments = + StellarPayment.addOrUpdatePayments( + existingTx.getPayments(), tx.getPayments().toArray(StellarPayment[]::new)); + } + tx.setPayments(newPayments); + + txHashMap.put(tx.getId(), tx); + } + } + + return new ArrayList<>(txHashMap.values()); + } +} diff --git a/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarPaymentTest.kt b/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarPaymentTest.kt new file mode 100644 index 0000000000..fdff363281 --- /dev/null +++ b/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarPaymentTest.kt @@ -0,0 +1,81 @@ +package org.stellar.anchor.api.shared + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class StellarPaymentTest { + companion object { + private const val stellarUSDC = + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + + private val mockPayment1 = + StellarPayment.builder() + .id("1111") + .amount(Amount("100.0000", stellarUSDC)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + private val mockPayment2 = + StellarPayment.builder() + .id("2222") + .amount(Amount("200.0000", stellarUSDC)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + private val mockPayment3 = + StellarPayment.builder() + .id("3333") + .amount(Amount("300.0000", stellarUSDC)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + + @Test + fun `test addOrUpdatePayments with empty payment list`() { + var paymentList: List? = null + + paymentList = StellarPayment.addOrUpdatePayments(paymentList, mockPayment1) + + val wantPaymentList = listOf(mockPayment1) + + assertEquals(wantPaymentList, paymentList) + } + + @Test + fun `test addOrUpdatePayments with existing identical payment`() { + var paymentList: List? = listOf(mockPayment1) + + paymentList = StellarPayment.addOrUpdatePayments(paymentList, mockPayment1) + + val wantPaymentList = listOf(mockPayment1) + + assertEquals(wantPaymentList, paymentList) + } + + @Test + fun `test addOrUpdatePayments updating existing payment`() { + // mockPayment1 with incomplete information: + var paymentList: List? = listOf(StellarPayment.builder().id("1111").build()) + + paymentList = StellarPayment.addOrUpdatePayments(paymentList, mockPayment1) + + val wantPaymentList = listOf(mockPayment1) + + assertEquals(wantPaymentList, paymentList) + } + + @Test + fun `test addOrUpdatePayments adding 2 payments`() { + var paymentList: List? = listOf(mockPayment1) + + paymentList = StellarPayment.addOrUpdatePayments(paymentList, mockPayment2, mockPayment3) + + val wantPaymentList = listOf(mockPayment1, mockPayment2, mockPayment3) + + assertEquals(wantPaymentList, paymentList) + } +} diff --git a/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarTransactionTest.kt b/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarTransactionTest.kt new file mode 100644 index 0000000000..6084d73ec4 --- /dev/null +++ b/api-schema/src/test/kotlin/org/stellar/anchor/api/shared/StellarTransactionTest.kt @@ -0,0 +1,250 @@ +package org.stellar.anchor.api.shared + +import java.time.Instant +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class StellarTransactionTest { + companion object { + private const val stellarUSDC = + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + + private val createdAt = Instant.now() + private val mockPayment1 = + StellarPayment.builder() + .id("1111") + .amount(Amount("100.0000", stellarUSDC)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + private val mockPayment2 = + StellarPayment.builder() + .id("2222") + .amount(Amount("200.0000", stellarUSDC)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + private val mockPayment3 = + StellarPayment.builder() + .id("3333") + .amount(Amount("300.0000", stellarUSDC)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + + @Test + fun `test addOrUpdateTransactions with empty tx list`() { + var txList: List? = null + + txList = + StellarTransaction.addOrUpdateTransactions( + txList, + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + val wantTxList = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + assertEquals(wantTxList, txList) + } + + @Test + fun `test addOrUpdateTransactions with existing identical tx`() { + var txList: List? = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + txList = + StellarTransaction.addOrUpdateTransactions( + txList, + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + val wantTxList = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + assertEquals(wantTxList, txList) + } + + @Test + fun `test addOrUpdateTransactions updating existing tx and payment`() { + var txList: List? = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .payments(listOf(StellarPayment.builder().id("1111").build())) + .build() + ) + + txList = + StellarTransaction.addOrUpdateTransactions( + txList, + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + val wantTxList = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + assertEquals(wantTxList, txList) + } + + @Test + fun `test addOrUpdateTransactions updating existing tx by adding 2 payments`() { + var txList: List? = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1)) + .build() + ) + + txList = + StellarTransaction.addOrUpdateTransactions( + txList, + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment2, mockPayment3)) + .build() + ) + + val wantTxList = + listOf( + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment1, mockPayment2, mockPayment3)) + .build() + ) + + assertEquals(wantTxList, txList) + } + + @Test + fun `test addOrUpdateTransactions by adding 2 tx`() { + var txList: List? = + listOf( + StellarTransaction.builder() + .id("A") + .payments(listOf(StellarPayment.builder().id("1111").build())) + .build() + ) + + txList = + StellarTransaction.addOrUpdateTransactions( + txList, + StellarTransaction.builder() + .id("B") + .memo("my-memo-b") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment2)) + .build(), + StellarTransaction.builder() + .id("C") + .memo("my-memo-c") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment3)) + .build() + ) + + val wantTxList = + listOf( + StellarTransaction.builder() + .id("A") + .payments(listOf(StellarPayment.builder().id("1111").build())) + .build(), + StellarTransaction.builder() + .id("B") + .memo("my-memo-b") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment2)) + .build(), + StellarTransaction.builder() + .id("C") + .memo("my-memo-c") + .memoType("text") + .createdAt(createdAt) + .envelope("here_comes_the_envelope") + .payments(listOf(mockPayment3)) + .build() + ) + + assertEquals(wantTxList, txList) + } +} diff --git a/core/src/main/java/org/stellar/anchor/event/models/Payment.java b/core/src/main/java/org/stellar/anchor/event/models/Payment.java deleted file mode 100644 index f9c8e70ccb..0000000000 --- a/core/src/main/java/org/stellar/anchor/event/models/Payment.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.stellar.anchor.event.models; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.gson.annotations.SerializedName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import org.stellar.anchor.api.shared.Amount; - -@Data -@Builder -@AllArgsConstructor -public class Payment { - @JsonProperty("operation_id") - @SerializedName("operation_id") - String operationId; - - @JsonProperty("source_account") - @SerializedName("source_account") - String sourceAccount; - - @JsonProperty("destination_account") - @SerializedName("destination_account") - String destinationAccount; - - Amount amount; - - public Payment() {} -} diff --git a/core/src/main/java/org/stellar/anchor/event/models/StellarTransaction.java b/core/src/main/java/org/stellar/anchor/event/models/StellarTransaction.java deleted file mode 100644 index ad1b87e935..0000000000 --- a/core/src/main/java/org/stellar/anchor/event/models/StellarTransaction.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.stellar.anchor.event.models; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.gson.annotations.SerializedName; -import java.time.Instant; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -@AllArgsConstructor -public class StellarTransaction { - String id; - String memo; - - @JsonProperty("memo_type") - @SerializedName("memo_type") - String memoType; - - @JsonProperty("created_at") - @SerializedName("created_at") - Instant createdAt; - - String envelope; - Payment[] payments; - - public StellarTransaction() {} -} diff --git a/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java b/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java index 78ba746dcf..7716f9ca4a 100644 --- a/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java +++ b/core/src/main/java/org/stellar/anchor/event/models/TransactionEvent.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.google.gson.annotations.SerializedName; import java.time.Instant; +import java.util.List; import java.util.Objects; import lombok.AllArgsConstructor; import lombok.Builder; @@ -12,6 +13,7 @@ import org.stellar.anchor.api.shared.Customers; import org.stellar.anchor.api.shared.Refund; import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.api.shared.StellarTransaction; @Data @Builder @@ -81,7 +83,7 @@ public String getType() { @JsonProperty("stellar_transactions") @SerializedName("stellar_transactions") - StellarTransaction[] stellarTransactions; + List stellarTransactions; @JsonProperty("external_transaction_id") @SerializedName("external_transaction_id") @@ -119,6 +121,11 @@ public enum Status { this.status = status; } + @Override + public String toString() { + return this.status; + } + public static Status from(String statusStr) { for (Status status : values()) { if (Objects.equals(status.status, statusStr)) { 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 db201cb510..af6a2e5096 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -3,15 +3,10 @@ import java.time.Instant; import java.util.List; import java.util.Map; -import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse; -import org.stellar.anchor.api.shared.Amount; -import org.stellar.anchor.api.shared.Customers; -import org.stellar.anchor.api.shared.Refund; -import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.api.shared.*; import org.stellar.anchor.event.models.TransactionEvent; -import org.stellar.anchor.util.StringHelper; public interface Sep31Transaction { String getId(); @@ -86,6 +81,10 @@ public interface Sep31Transaction { void setStellarTransactionId(String stellarTransactionId); + List getStellarTransactions(); + + void setStellarTransactions(List stellarTransactions); + String getExternalTransactionId(); void setExternalTransactionId(String externalTransactionId); @@ -186,14 +185,6 @@ default Sep31GetTransactionResponse toSep31GetTransactionResponse() { refunds = getRefunds().toPlatformApiRefund(getAmountInAsset()); } - List stellarTransactions = null; - if (!StringHelper.isEmpty(getStellarTransactionId())) { - GetTransactionResponse.StellarTransaction stellarTxn = - new GetTransactionResponse.StellarTransaction(); - stellarTxn.setId(getStellarTransactionId()); - stellarTransactions = List.of(stellarTxn); - } - return org.stellar.anchor.api.platform.GetTransactionResponse.builder() .id(getId()) .sep(31) @@ -210,7 +201,7 @@ default Sep31GetTransactionResponse toSep31GetTransactionResponse() { .transferReceivedAt(getTransferReceivedAt()) .message(getRequiredInfoMessage()) // Assuming these are meant to be the same. .refunds(refunds) - .stellarTransactions(stellarTransactions) + .stellarTransactions(getStellarTransactions()) .externalTransactionId(getExternalTransactionId()) // TODO .custodialTransactionId(txn.get) .customers(getCustomers()) diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java index 314739a2de..6daddb98f7 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java @@ -1,9 +1,11 @@ package org.stellar.anchor.sep31; import java.time.Instant; +import java.util.List; import java.util.Map; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.api.shared.StellarTransaction; public class Sep31TransactionBuilder { final Sep31Transaction txn; @@ -104,6 +106,11 @@ public Sep31TransactionBuilder stellarTransactionId(String stellarTransactionId) return this; } + public Sep31TransactionBuilder stellarTransactions(List stellarTransactions) { + txn.setStellarTransactions(stellarTransactions); + return this; + } + public Sep31TransactionBuilder externalTransactionId(String externalTransactionId) { txn.setExternalTransactionId(externalTransactionId); return this; diff --git a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java index e11569ef40..76b7afc16e 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java @@ -1,10 +1,12 @@ package org.stellar.anchor.sep31; import java.time.Instant; +import java.util.List; import java.util.Map; import lombok.Data; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.api.shared.StellarTransaction; @Data public class PojoSep31Transaction implements Sep31Transaction { @@ -23,6 +25,7 @@ public class PojoSep31Transaction implements Sep31Transaction { Instant startedAt; Instant completedAt; String stellarTransactionId; + List stellarTransactions; String externalTransactionId; String requiredInfoMessage; String quoteId; 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 4dcbe1a841..c157ce416b 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -30,6 +30,7 @@ class Sep31TransactionTest { @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore private lateinit var sep31Transaction: Sep31Transaction + private lateinit var stellarTransaction: StellarTransaction private val txId = "a4baff5f-778c-43d6-bbef-3e9fb41d096e" @@ -73,6 +74,26 @@ class Sep31TransactionTest { AssetInfo.Sep31TxnFieldSpec("bank account number of the destination", null, false), ) + stellarTransaction = + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(mockTransferReceivedAt) + .envelope("here_comes_the_envelope") + .payments( + listOf( + StellarPayment.builder() + .id("4609238642995201") + .amount(Amount("100.0000", fiatUSD)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + ) + ) + .build() + // mock the database SEP-31 transaction sep31Transaction = Sep31TransactionBuilder(sep31TransactionStore) @@ -94,6 +115,7 @@ class Sep31TransactionTest { .transferReceivedAt(mockTransferReceivedAt) .completedAt(mockCompletedAt) .stellarTransactionId("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .stellarTransactions(listOf(stellarTransaction)) .externalTransactionId("external-tx-id") .refunded(true) .refunds(mockRefunds) @@ -141,9 +163,6 @@ class Sep31TransactionTest { ) .build() - val wantStellarTransaction = GetTransactionResponse.StellarTransaction() - wantStellarTransaction.id = "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300" - val wantGetTransactionResponse: GetTransactionResponse = GetTransactionResponse.builder() .id(txId) @@ -161,7 +180,7 @@ class Sep31TransactionTest { .transferReceivedAt(mockTransferReceivedAt) .message("Please don't forget to foo bar") .refunds(wantRefunds) - .stellarTransactions(listOf(wantStellarTransaction)) + .stellarTransactions(listOf(stellarTransaction)) .externalTransactionId("external-tx-id") .customers( Customers( diff --git a/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml b/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml index 1769ad1ac3..bf05e58cef 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml @@ -459,18 +459,19 @@ components: type: object required: - id - - memo - - memo_type - created_at - envelope - payments properties: id: type: string + description: The ID of the transaction in the Stellar network. memo: type: string + description: The memo of the transaction in the Stellar network. memo_type: type: string + description: The memo type of the transaction in the Stellar network. Should be present if memo is not null. enum: - text - hash @@ -478,19 +479,37 @@ components: created_at: type: string format: date-time + description: The time the transaction was registered in the Stellar network. envelope: type: string + description: The transaction envelope, containing all the transaction information. payments: type: array items: type: object + required: + - id + - payment_type + - source_account + - destination_account + - amount properties: - operationId: + id: + type: string + description: The ID of the payment in the Stellar Network. + payment_type: type: string + description: The type of payment in the Stellar Network. + enum: + - payment + - path_payment + default: payment source_account: type: string + description: The account being debited in the Stellar Network. destination_account: type: string + description: The account being credited in the Stellar Network. amount: $ref: '#/components/schemas/Amount' Error: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c660ee38ef..de090f57a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ flyway-core = "8.5.13" google-gson = "2.8.9" httpclient = "4.5.13" h2database = "1.4.200" +hibernate-types = "2.18.0" jackson-dataformat-yaml = "2.13.2" java-stellar-sdk = "0.34.1" javax-jaxb-api = "2.3.1" @@ -62,6 +63,7 @@ flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway-core" google-gson = { module = "com.google.code.gson:gson", version.ref = "google-gson" } httpclient = { module = "org.apache.httpcomponents:httpclient", version.ref = "httpclient" } h2database = { module = "com.h2database:h2", version.ref = "h2database" } +hibernate-types = { module = "com.vladmihalcea:hibernate-types-55", version.ref = "hibernate-types" } jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson-dataformat-yaml" } java-stellar-sdk = { module = "com.github.stellar:java-stellar-sdk", version.ref = "java-stellar-sdk" } javax-jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "javax-jaxb-api" } diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index e3d17602ad..6b63fa0661 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation(libs.commons.cli) implementation(libs.commons.io) implementation(libs.flyway.core) + implementation(libs.hibernate.types) implementation(libs.google.gson) implementation(libs.java.stellar.sdk) diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java index 6043d2a17c..45b4163177 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java @@ -3,13 +3,17 @@ import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import com.google.gson.reflect.TypeToken; +import com.vladmihalcea.hibernate.type.json.JsonType; import java.time.Instant; +import java.util.List; import java.util.Map; -import java.util.Set; import javax.persistence.*; import lombok.Data; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.api.shared.StellarTransaction; import org.stellar.anchor.reference.model.StellarIdConverter; import org.stellar.anchor.sep31.Refunds; import org.stellar.anchor.sep31.Sep31Transaction; @@ -19,6 +23,7 @@ @Entity @Access(AccessType.FIELD) @Table(name = "sep31_transaction") +@TypeDef(name = "json", typeClass = JsonType.class) public class JdbcSep31Transaction implements Sep31Transaction, SepTransaction { static Gson gson = GsonUtils.getInstance(); @@ -64,6 +69,10 @@ public class JdbcSep31Transaction implements Sep31Transaction, SepTransaction { @SerializedName("stellar_transaction_id") String stellarTransactionId; + @Column(columnDefinition = "json") + @Type(type = "json") + List stellarTransactions; + @SerializedName("external_transaction_id") String externalTransactionId; @@ -86,11 +95,6 @@ public class JdbcSep31Transaction implements Sep31Transaction, SepTransaction { @Column(length = 2047) StellarId creator; - // Ignored by JPA and Gson - @SerializedName("required_info_updates") - @Transient - AssetInfo.Sep31TxnFieldSpecs requiredInfoUpdates; - // Ignored by JPA and Gson @SerializedName("fields") @Transient @@ -108,6 +112,11 @@ public void setFieldsJson(String fieldsJson) { } } + // Ignored by JPA and Gson + @SerializedName("required_info_updates") + @Transient + AssetInfo.Sep31TxnFieldSpecs requiredInfoUpdates; + @Access(AccessType.PROPERTY) @Column(name = "requiredInfoUpdates") public String getRequiredInfoUpdatesJson() { @@ -141,7 +150,4 @@ public void setRefundsJson(String refundsJson) { Instant updatedAt; Instant transferReceivedAt; String amountExpected; - - @OneToMany(fetch = FetchType.EAGER, mappedBy = "sep31Transaction") - Set stellarTransactions = new java.util.LinkedHashSet<>(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/StellarTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/StellarTransaction.java deleted file mode 100644 index b1f9163cf3..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/data/StellarTransaction.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.stellar.anchor.platform.data; - -import com.google.gson.Gson; -import com.google.gson.annotations.SerializedName; -import java.time.Instant; -import javax.persistence.*; -import lombok.Data; -import org.stellar.anchor.util.GsonUtils; - -@Entity -@Table(name = "stellar_transaction") -@Data -public class StellarTransaction { - private static Gson gson = GsonUtils.getInstance(); - - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(name = "id", nullable = false) - private Long id; - - @ManyToOne - @JoinColumn(name = "sep_31_transaction_id") - private JdbcSep31Transaction sep31Transaction; - - String memo; - - @SerializedName("memo_type") - String memoType; - - @SerializedName("created_at") - Instant createdAt; - - String envelope; - - transient String paymentJson; - - @Access(AccessType.PROPERTY) - @Column(name = "payment") - public String getPaymentJson() { - return gson.toJson(this.payment); - } - - public void setPaymentJson(String requiredInfoUpdatesJson) { - if (requiredInfoUpdatesJson != null) { - this.payment = gson.fromJson(paymentJson, Payment.class); - } - } - - @Transient Payment payment; - - @Data - class Payment {} -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java deleted file mode 100644 index befd6266c3..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPayment.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.stellar; - -import com.google.gson.annotations.SerializedName; -import lombok.Builder; -import org.stellar.anchor.api.shared.Amount; - -@Builder -public class StellarPayment { - @SerializedName("operation_id") - String operationId; - - @SerializedName("source_account") - String sourceAccount; - - @SerializedName("destination_account") - String destinationAccount; - - Amount amount; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java deleted file mode 100644 index ec3facd431..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarTransaction.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.stellar; - -import com.google.gson.annotations.SerializedName; -import java.time.Instant; -import lombok.Builder; -import lombok.Data; - -@Data -@Builder -public class StellarTransaction { - String id; - String memo; - - @SerializedName("memo_type") - String memoType; - - @SerializedName("created_at") - Instant createdAt; - - String envelope; - StellarPayment payment; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index ac42458267..71bd4c6ff3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -1,8 +1,5 @@ package org.stellar.anchor.platform.service; -import static org.stellar.anchor.api.sep.SepTransactionStatus.ERROR; -import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_RECEIVER; -import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_SENDER; import static org.stellar.anchor.util.MathHelper.*; import io.micrometer.core.instrument.Metrics; @@ -17,8 +14,9 @@ import org.springframework.stereotype.Component; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.SepException; -import org.stellar.anchor.api.sep.SepTransactionStatus; import org.stellar.anchor.api.shared.Amount; +import org.stellar.anchor.api.shared.StellarPayment; +import org.stellar.anchor.api.shared.StellarTransaction; import org.stellar.anchor.event.EventPublishService; import org.stellar.anchor.event.models.*; import org.stellar.anchor.platform.payment.observer.PaymentListener; @@ -52,7 +50,7 @@ public void onReceived(ObservedPayment payment) { // Check if the payment contains the expected asset type if (!List.of("credit_alphanum4", "credit_alphanum12").contains(payment.getAssetType())) { // Asset type does not match - Log.warn("Not an issued asset"); + Log.infoF("{} is not an issued asset.", payment.getAssetType()); return; } @@ -63,7 +61,8 @@ public void onReceived(ObservedPayment payment) { try { memo = MemoHelper.convertHexToBase64(payment.getTransactionMemo()); } catch (DecoderException ex) { - Log.warn("Not a HEX string"); + Log.warnF( + "The memo type is \"hash\" but the memo string {} could not be parsed as such.", memo); Log.warnEx(ex); } } @@ -72,55 +71,96 @@ public void onReceived(ObservedPayment payment) { Sep31Transaction txn; try { txn = transactionStore.findByStellarMemo(memo); + if (txn == null) { + Log.infoF("Not expecting any transaction with the memo {}.", payment.getTransactionMemo()); + return; + } } catch (AnchorException e) { - Log.error( - String.format( - "error finding transaction that matches the memo (%s).", - payment.getTransactionMemo())); + Log.errorF( + "Error finding transaction that matches the memo {}.", payment.getTransactionMemo()); e.printStackTrace(); return; } - if (txn == null) { - Log.info( - String.format( - "Not expecting any transaction with the memo %s.", payment.getTransactionMemo())); - return; - } // Compare asset code String paymentAssetName = "stellar:" + payment.getAssetName(); if (!txn.getAmountInAsset().equals(paymentAssetName)) { - Log.warn( - String.format( - "Payment asset %s does not match the expected asset %s", - payment.getAssetCode(), txn.getAmountInAsset())); + Log.warnF( + "Payment asset {} does not match the expected asset {}.", + payment.getAssetCode(), + txn.getAmountInAsset()); return; } + // parse payment creation time + Instant paymentTime = parsePaymentTime(payment.getCreatedAt()); + + // Build Stellar Transaction object + StellarTransaction stellarTransaction = + StellarTransaction.builder() + .id(payment.getTransactionHash()) + .memo(txn.getStellarMemo()) + .memoType(txn.getStellarMemoType()) + .createdAt(paymentTime) + .envelope(payment.getTransactionEnvelope()) + .payments( + List.of( + StellarPayment.builder() + .id(payment.getId()) + .paymentType( + payment.getType() == ObservedPayment.Type.PAYMENT + ? StellarPayment.Type.PAYMENT + : StellarPayment.Type.PATH_PAYMENT) + .sourceAccount(payment.getFrom()) + .destinationAccount(payment.getTo()) + .amount(new Amount(payment.getAmount(), payment.getAssetName())) + .build())) + .build(); + + // Update statuses + TransactionEvent.Status oldStatus = TransactionEvent.Status.from(txn.getStatus()); + TransactionEvent.Status newStatus = TransactionEvent.Status.PENDING_RECEIVER; + // Check if the payment contains the expected amount (or greater) BigDecimal expectedAmount = decimal(txn.getAmountIn()); BigDecimal gotAmount = decimal(payment.getAmount()); - if (gotAmount.compareTo(expectedAmount) < 0) { - Log.warn( + String message = "Incoming payment for SEP-31 transaction"; + if (gotAmount.compareTo(expectedAmount) >= 0) { + Log.info(message); + txn.setTransferReceivedAt(paymentTime); + } else { + message = String.format( - "Payment amount %s is smaller than the expected amount %s", - payment.getAmount(), txn.getAmountIn())); - updateTransactionStatusTo(ERROR, txn, payment); - return; + "The incoming payment amount was insufficient! Expected: \"%s\", Received: \"%s\"", + formatAmount(expectedAmount), formatAmount(gotAmount)); + Log.warn(message); } - // Set the transaction status. - TransactionEvent.Status oldStatus = TransactionEvent.Status.from(txn.getStatus()); - TransactionEvent.Status newStatus = TransactionEvent.Status.PENDING_RECEIVER; - TransactionEvent.StatusChange statusChange = - new TransactionEvent.StatusChange(oldStatus, newStatus); - if (txn.getStatus().equals(PENDING_SENDER.toString())) { - updateTransactionStatusTo(PENDING_RECEIVER, txn, payment); + // Update the database transaction fields + txn.setUpdatedAt(paymentTime); + txn.setStatus(newStatus.toString()); + txn.setStellarTransactionId(payment.getTransactionHash()); + List stellarTransactions = + StellarTransaction.addOrUpdateTransactions( + txn.getStellarTransactions(), stellarTransaction); + txn.setStellarTransactions(stellarTransactions); + // Save + try { + transactionStore.save(txn); + } catch (SepException ex) { + Log.errorEx("Error saving Sep31Transaction upon received event", ex); } // send to the event queue - TransactionEvent event = receivedPaymentToEvent(txn, payment, statusChange); + TransactionEvent.StatusChange statusChange = + new TransactionEvent.StatusChange(oldStatus, newStatus); + TransactionEvent event = + receivedPaymentToEvent(txn, payment, statusChange, message, stellarTransaction); sendToQueue(event); + + // Update metrics + Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", newStatus.toString()) + .increment(); Metrics.counter(AnchorMetrics.PAYMENT_RECEIVED.toString(), "asset", payment.getAssetName()) .increment(Double.parseDouble(payment.getAmount())); } @@ -132,11 +172,15 @@ public void onSent(ObservedPayment payment) { private void sendToQueue(TransactionEvent event) { eventService.publish(event); - Log.info("Sent to event queue" + GsonUtils.getInstance().toJson(event)); + Log.infoF("Sent to event queue {}", GsonUtils.getInstance().toJson(event)); } TransactionEvent receivedPaymentToEvent( - Sep31Transaction txn, ObservedPayment payment, TransactionEvent.StatusChange statusChange) { + Sep31Transaction txn, + ObservedPayment payment, + TransactionEvent.StatusChange statusChange, + String message, + StellarTransaction newStellarTransaction) { TransactionEvent event = TransactionEvent.builder() .eventId(UUID.randomUUID().toString()) @@ -156,27 +200,9 @@ TransactionEvent receivedPaymentToEvent( .updatedAt(txn.getUpdatedAt()) .completedAt(null) .transferReceivedAt(txn.getTransferReceivedAt()) - .message("Incoming payment for SEP-31 transaction") + .message(message) .refunds(null) - .stellarTransactions( - new StellarTransaction[] { - StellarTransaction.builder() - .id(payment.getTransactionHash()) - .memo(txn.getStellarMemo()) - .memoType(txn.getStellarMemoType()) - .createdAt(txn.getTransferReceivedAt()) - .envelope(payment.getTransactionEnvelope()) - .payments( - new Payment[] { - Payment.builder() - .operationId(payment.getId()) - .sourceAccount(payment.getFrom()) - .destinationAccount(payment.getTo()) - .amount(new Amount(payment.getAmount(), payment.getAssetName())) - .build() - }) - .build() - }) + .stellarTransactions(List.of(newStellarTransaction)) .externalTransactionId(payment.getExternalTransactionId()) .custodialTransactionId(null) .sourceAccount(payment.getFrom()) @@ -187,50 +213,13 @@ TransactionEvent receivedPaymentToEvent( return event; } - void updateTransactionStatusTo( - SepTransactionStatus newStatus, Sep31Transaction txn, ObservedPayment payment) { - // parse payment creation time - Instant paymentTime = null; + Instant parsePaymentTime(String paymentTimeStr) { try { - paymentTime = DateTimeFormatter.ISO_INSTANT.parse(payment.getCreatedAt(), Instant::from); + return DateTimeFormatter.ISO_INSTANT.parse(paymentTimeStr, Instant::from); } catch (DateTimeParseException | NullPointerException ex) { - Log.error( - String.format("error parsing payment.getCreatedAt() (%s).", payment.getCreatedAt())); + Log.errorF("Error parsing paymentTimeStr {}.", paymentTimeStr); ex.printStackTrace(); - } - - // update the transaction differently based on the new status - switch (newStatus) { - case ERROR: - if (paymentTime != null) { - txn.setUpdatedAt(paymentTime); - } - txn.setStatus(ERROR.getName()); - saveTransaction(txn); - Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", ERROR.getName()) - .increment(); - break; - - case PENDING_RECEIVER: - if (paymentTime != null) { - txn.setUpdatedAt(paymentTime); - txn.setTransferReceivedAt(paymentTime); - } - txn.setStatus(PENDING_RECEIVER.toString()); - txn.setStellarTransactionId(payment.getTransactionHash()); - saveTransaction(txn); - Metrics.counter("sep31.transaction", "status", PENDING_RECEIVER.toString()).increment(); - - default: - Log.errorF("Unsupported new status {}.", newStatus); - } - } - - void saveTransaction(Sep31Transaction txn) { - try { - transactionStore.save(txn); - } catch (SepException ex) { - Log.errorEx(ex); + return null; } } } diff --git a/platform/src/main/resources/db/migration/V5__sep31_transaction_now_store_stellar_transactions_as_json.sql b/platform/src/main/resources/db/migration/V5__sep31_transaction_now_store_stellar_transactions_as_json.sql new file mode 100644 index 0000000000..7a071b8a96 --- /dev/null +++ b/platform/src/main/resources/db/migration/V5__sep31_transaction_now_store_stellar_transactions_as_json.sql @@ -0,0 +1,7 @@ +ALTER TABLE sep31_transaction + ADD stellar_transactions JSON; + +ALTER TABLE stellar_transaction + DROP CONSTRAINT fk_stellar_transaction_on_sep_31_transaction; + +DROP TABLE stellar_transaction CASCADE; \ No newline at end of file diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt index 928615fb79..6155d8a021 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt @@ -5,6 +5,7 @@ import io.mockk.impl.annotations.MockK import java.time.Instant import java.time.format.DateTimeFormatter import kotlin.test.assertEquals +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.stellar.anchor.api.exception.SepException @@ -12,6 +13,8 @@ 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.StellarId +import org.stellar.anchor.api.shared.StellarPayment +import org.stellar.anchor.api.shared.StellarTransaction import org.stellar.anchor.event.EventPublishService import org.stellar.anchor.event.models.* import org.stellar.anchor.platform.data.JdbcSep31Transaction @@ -34,6 +37,12 @@ class PaymentOperationToEventListenerTest { PaymentOperationToEventListener(transactionStore, eventPublishService) } + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + @Test fun test_onReceiver_failValidation() { // Payment missing txHash shouldn't trigger an event nor reach the DB @@ -86,34 +95,17 @@ class PaymentOperationToEventListenerTest { slotMemo = slot() p.transactionMemo = "my_memo_4" p.assetCode = "FOO" - var sep31TxMock = JdbcSep31Transaction() + val sep31TxMock = JdbcSep31Transaction() sep31TxMock.amountInAsset = "BAR" every { transactionStore.findByStellarMemo(capture(slotMemo)) } returns sep31TxMock paymentOperationToEventListener.onReceived(p) verify { eventPublishService wasNot Called } verify(exactly = 1) { transactionStore.findByStellarMemo("my_memo_4") } assertEquals("my_memo_4", slotMemo.captured) - - // If payment amount is smaller than the expected, don't trigger event - slotMemo = slot() - p.transactionMemo = "my_memo_5" - p.assetCode = "FOO" - p.assetName = "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" - p.amount = "9.9999999" - p.createdAt = "2022-08-09T17:32:44Z" - sep31TxMock = JdbcSep31Transaction() - sep31TxMock.amountInAsset = - "stellar:FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" - sep31TxMock.amountIn = "10" - every { transactionStore.findByStellarMemo(capture(slotMemo)) } returns sep31TxMock - paymentOperationToEventListener.onReceived(p) - verify { eventPublishService wasNot Called } - verify(exactly = 1) { transactionStore.findByStellarMemo("my_memo_5") } - assertEquals("my_memo_5", slotMemo.captured) } @Test - fun test_onReceiver_success() { + fun `test onReceiver gets the expected amount and sends the event with PENDING_RECEIVER status`() { val startedAtMock = Instant.now().minusSeconds(120) val transferReceivedAt = Instant.now() val transferReceivedAtStr = DateTimeFormatter.ISO_INSTANT.format(transferReceivedAt) @@ -132,6 +124,7 @@ class PaymentOperationToEventListenerTest { p.sourceAccount = "GCJKWN7ELKOXLDHJTOU4TZOEJQL7TYVVTQFR676MPHHUIUDAHUA7QGJ4" p.from = "GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO" p.to = "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + p.type = ObservedPayment.Type.PATH_PAYMENT p.createdAt = transferReceivedAtStr p.transactionEnvelope = "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" @@ -170,6 +163,30 @@ class PaymentOperationToEventListenerTest { val slotTx = slot() every { transactionStore.save(capture(slotTx)) } returns sep31TxMock + val stellarTransaction = + StellarTransaction.builder() + .id("1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30") + .memo("OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=") + .memoType("hash") + .createdAt(transferReceivedAt) + .envelope( + "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" + ) + .payments( + listOf( + StellarPayment.builder() + .id("755914248193") + .paymentType(StellarPayment.Type.PATH_PAYMENT) + .sourceAccount("GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO") + .destinationAccount("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") + .amount( + Amount("10.0000000", "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") + ) + .build() + ) + ) + .build() + val wantEvent = TransactionEvent.builder() .type(TransactionEvent.Type.TRANSACTION_STATUS_CHANGED) @@ -205,36 +222,154 @@ class PaymentOperationToEventListenerTest { StellarId.builder().id(receiverId).build() ) ) - .stellarTransactions( - arrayOf( - StellarTransaction.builder() - .id("1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30") - .memo("OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=") - .memoType("hash") - .createdAt(transferReceivedAt) - .envelope( - "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" - ) - .payments( - arrayOf( - Payment.builder() - .operationId("755914248193") - .sourceAccount("GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO") - .destinationAccount("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") - .amount( - Amount( - "10.0000000", - "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" - ) - ) - .build() - ) + .stellarTransactions(listOf(stellarTransaction)) + .build() + + val slotEvent = slot() + every { eventPublishService.publish(capture(slotEvent)) } just Runs + + paymentOperationToEventListener.onReceived(p) + verify(exactly = 1) { + transactionStore.findByStellarMemo("OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=") + } + verify(exactly = 1) { eventPublishService.publish(any()) } + + wantEvent.eventId = slotEvent.captured.eventId + assertEquals(wantEvent, slotEvent.captured) + + // wantSep31Tx + val wantSep31Tx = gson.fromJson(gson.toJson(sep31TxMock), JdbcSep31Transaction::class.java) + wantSep31Tx.status = TransactionEvent.Status.PENDING_RECEIVER.status + wantSep31Tx.stellarTransactionId = + "1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30" + wantSep31Tx.transferReceivedAt = transferReceivedAt + wantSep31Tx.updatedAt = transferReceivedAt + wantSep31Tx.stellarTransactions = listOf(stellarTransaction) + + assertEquals(wantSep31Tx, slotTx.captured) + } + + @Test + fun `test onReceiver gets less than the expected amount it sends the PENDING_RECEIVER status with a message`() { + val startedAtMock = Instant.now().minusSeconds(120) + val transferReceivedAt = Instant.now() + val transferReceivedAtStr = DateTimeFormatter.ISO_INSTANT.format(transferReceivedAt) + val fooAsset = "stellar:FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + val barAsset = "stellar:BAR:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + + val p = ObservedPayment.builder().build() + p.transactionHash = "1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30" + p.transactionMemo = "39623738663066612d393366392d343139382d386439332d6537366664303834" + p.transactionMemoType = "hash" + p.assetType = "credit_alphanum4" + p.assetCode = "FOO" + p.assetIssuer = "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + p.assetName = "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + p.amount = "9.0000000" + p.sourceAccount = "GCJKWN7ELKOXLDHJTOU4TZOEJQL7TYVVTQFR676MPHHUIUDAHUA7QGJ4" + p.from = "GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO" + p.to = "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + p.type = ObservedPayment.Type.PAYMENT + p.createdAt = transferReceivedAtStr + p.transactionEnvelope = + "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" + p.id = "755914248193" + + val senderId = "d2bd1412-e2f6-4047-ad70-a1a2f133b25c" + val receiverId = "137938d4-43a7-4252-a452-842adcee474c" + + val slotMemo = slot() + val sep31TxMock = JdbcSep31Transaction() + sep31TxMock.id = "ceaa7677-a5a7-434e-b02a-8e0801b3e7bd" + sep31TxMock.amountExpected = "10" + sep31TxMock.amountIn = "10" + sep31TxMock.amountInAsset = fooAsset + sep31TxMock.amountOut = "20" + sep31TxMock.amountOutAsset = barAsset + sep31TxMock.amountFee = "0.5" + sep31TxMock.amountFeeAsset = fooAsset + sep31TxMock.quoteId = "cef1fc13-3f65-4612-b1f2-502d698c816b" + sep31TxMock.startedAt = startedAtMock + sep31TxMock.updatedAt = startedAtMock + sep31TxMock.transferReceivedAt = null // the event should have a valid `transferReceivedAt` + sep31TxMock.stellarMemo = "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=" + sep31TxMock.stellarMemoType = "hash" + sep31TxMock.status = SepTransactionStatus.PENDING_SENDER.toString() + sep31TxMock.senderId = senderId + sep31TxMock.receiverId = receiverId + sep31TxMock.creator = + StellarId.builder() + .account("GBE4B7KE62NUBFLYT3BIG4OP5DAXBQX2GSZZOVAYXQKJKIU7P6V2R2N4") + .build() + + val sep31TxCopy = gson.fromJson(gson.toJson(sep31TxMock), JdbcSep31Transaction::class.java) + every { transactionStore.findByStellarMemo(capture(slotMemo)) } returns sep31TxCopy + + val slotTx = slot() + every { transactionStore.save(capture(slotTx)) } returns sep31TxMock + + val stellarTransaction = + StellarTransaction.builder() + .id("1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30") + .memo("OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=") + .memoType("hash") + .createdAt(transferReceivedAt) + .envelope( + "AAAAAgAAAAAQfdFrLDgzSIIugR73qs8U0ZiKbwBUclTTPh5thlbgnAAAB9AAAACwAAAABAAAAAEAAAAAAAAAAAAAAABiMbeEAAAAAAAAABQAAAAAAAAAAAAAAADcXPrnCDi+IDcGSvu/HjP779qjBv6K9Sie8i3WDySaIgAAAAA8M2CAAAAAAAAAAAAAAAAAJXdMB+xylKwEPk1tOLU82vnDM0u15RsK6/HCKsY1O3MAAAAAPDNggAAAAAAAAAAAAAAAALn+JaJ9iXEcrPeRFqEMGo6WWFeOwW15H/vvCOuMqCsSAAAAADwzYIAAAAAAAAAAAAAAAADbWpHlX0LQjIjY0x8jWkclnQDK8jFmqhzCmB+1EusXwAAAAAA8M2CAAAAAAAAAAAAAAAAAmy3UTqTnhNzIg8TjCYiRh9l07ls0Hi5FTqelhfZ4KqAAAAAAPDNggAAAAAAAAAAAAAAAAIwiZIIbYJn7MbHrrM+Pg85c6Lcn0ZGLb8NIiXLEIPTnAAAAADwzYIAAAAAAAAAAAAAAAAAYEjPKA/6lDpr/w1Cfif2hK4GHeNODhw0kk4kgLrmPrQAAAAA8M2CAAAAAAAAAAAAAAAAASMrE32C3vL39cj84pIg2mt6OkeWBz5OSZn0eypcjS4IAAAAAPDNggAAAAAAAAAAAAAAAAIuxsI+2mSeh3RkrkcpQ8bMqE7nXUmdvgwyJS/dBThIPAAAAADwzYIAAAAAAAAAAAAAAAACuZxdjR/GXaymdc9y5WFzz2A8Yk5hhgzBZsQ9R0/BmZwAAAAA8M2CAAAAAAAAAAAAAAAAAAtWBvyq0ToNovhQHSLeQYu7UzuqbVrm0i3d1TjRm7WEAAAAAPDNggAAAAAAAAAAAAAAAANtrzNON0u1IEGKmVsm80/Av+BKip0ioeS/4E+Ejs9YPAAAAADwzYIAAAAAAAAAAAAAAAAD+ejNcgNcKjR/ihUx1ikhdz5zmhzvRET3LGd7oOiBlTwAAAAA8M2CAAAAAAAAAAAAAAAAASXG3P6KJjS6e0dzirbso8vRvZKo6zETUsEv7OSP8XekAAAAAPDNggAAAAAAAAAAAAAAAAC5orVpxxvGEB8ISTho2YdOPZJrd7UBj1Bt8TOjLOiEKAAAAADwzYIAAAAAAAAAAAAAAAAAOQR7AqdGyIIMuFLw9JQWtHqsUJD94kHum7SJS9PXkOwAAAAA8M2CAAAAAAAAAAAAAAAAAIosijRx7xSP/+GA6eAjGeV9wJtKDySP+OJr90euE1yQAAAAAPDNggAAAAAAAAAAAAAAAAKlHXWQvwNPeT4Pp1oJDiOpcKwS3d9sho+ha+6pyFwFqAAAAADwzYIAAAAAAAAAAAAAAAABjCjnoL8+FEP0LByZA9PfMLwU1uAX4Cb13rVs83e1UZAAAAAA8M2CAAAAAAAAAAAAAAAAAokhNCZNGq9uAkfKTNoNGr5XmmMoY5poQEmp8OVbit7IAAAAAPDNggAAAAAAAAAABhlbgnAAAAEBa9csgF5/0wxrYM6oVsbM4Yd+/3uVIplS6iLmPOS4xf8oLQLtjKKKIIKmg9Gc/yYm3icZyU7icy9hGjcujenMN" + ) + .payments( + listOf( + StellarPayment.builder() + .id("755914248193") + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO") + .destinationAccount("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") + .amount( + Amount("9.0000000", "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") ) .build() ) ) .build() + val wantEvent = + TransactionEvent.builder() + .type(TransactionEvent.Type.TRANSACTION_STATUS_CHANGED) + .id("ceaa7677-a5a7-434e-b02a-8e0801b3e7bd") + .status(TransactionEvent.Status.PENDING_RECEIVER) + .statusChange( + TransactionEvent.StatusChange( + TransactionEvent.Status.PENDING_SENDER, + TransactionEvent.Status.PENDING_RECEIVER + ) + ) + .sep(TransactionEvent.Sep.SEP_31) + .kind(TransactionEvent.Kind.RECEIVE) + .amountExpected(Amount("10", fooAsset)) + .amountIn(Amount("9.0000000", fooAsset)) + .amountOut(Amount("20", barAsset)) + .amountFee(Amount("0.5", fooAsset)) + .quoteId("cef1fc13-3f65-4612-b1f2-502d698c816b") + .startedAt(startedAtMock) + .updatedAt(transferReceivedAt) + .transferReceivedAt(null) + .message("The incoming payment amount was insufficient! Expected: \"10\", Received: \"9\"") + .sourceAccount("GAJKV32ZXP5QLYHPCMLTV5QCMNJR3W6ZKFP6HMDN67EM2ULDHHDGEZYO") + .destinationAccount("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364") + .creator( + StellarId.builder() + .account("GBE4B7KE62NUBFLYT3BIG4OP5DAXBQX2GSZZOVAYXQKJKIU7P6V2R2N4") + .build() + ) + .customers( + Customers( + StellarId.builder().id(senderId).build(), + StellarId.builder().id(receiverId).build() + ) + ) + .stellarTransactions(listOf(stellarTransaction)) + .build() + val slotEvent = slot() every { eventPublishService.publish(capture(slotEvent)) } just Runs @@ -252,8 +387,9 @@ class PaymentOperationToEventListenerTest { wantSep31Tx.status = TransactionEvent.Status.PENDING_RECEIVER.status wantSep31Tx.stellarTransactionId = "1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30" - wantSep31Tx.transferReceivedAt = transferReceivedAt + wantSep31Tx.transferReceivedAt = null wantSep31Tx.updatedAt = transferReceivedAt + wantSep31Tx.stellarTransactions = listOf(stellarTransaction) assertEquals(wantSep31Tx, slotTx.captured) } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt index 27e7f2d82f..66dc71bc94 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt @@ -86,6 +86,27 @@ class TransactionServiceTest { val mockTransferReceivedAt = mockUpdatedAt.plusSeconds(60) val mockCompletedAt = mockTransferReceivedAt.plusSeconds(60) + // mock the stellar transaction + val stellarTransaction = + StellarTransaction.builder() + .id("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .memo("my-memo") + .memoType("text") + .createdAt(mockTransferReceivedAt) + .envelope("here_comes_the_envelope") + .payments( + listOf( + StellarPayment.builder() + .id("4609238642995201") + .amount(Amount("100.0000", fiatUSD)) + .paymentType(StellarPayment.Type.PAYMENT) + .sourceAccount("GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ") + .destinationAccount("GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7") + .build() + ) + ) + .build() + // mock refunds val mockRefundPayment1 = RefundPaymentBuilder(sep31TransactionStore).id("1111").amount("50.0000").fee("4.0000").build() @@ -127,6 +148,7 @@ class TransactionServiceTest { .transferReceivedAt(mockTransferReceivedAt) .completedAt(mockCompletedAt) .stellarTransactionId("2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300") + .stellarTransactions(listOf(stellarTransaction)) .externalTransactionId("external-tx-id") .refunded(true) .refunds(mockRefunds) @@ -168,9 +190,6 @@ class TransactionServiceTest { ) .build() - val wantStellarTransaction = GetTransactionResponse.StellarTransaction() - wantStellarTransaction.id = "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300" - val wantGetTransactionResponse: GetTransactionResponse = GetTransactionResponse.builder() .id(txId) @@ -188,7 +207,7 @@ class TransactionServiceTest { .transferReceivedAt(mockTransferReceivedAt) .message("Please don't forget to foo bar") .refunds(wantRefunds) - .stellarTransactions(listOf(wantStellarTransaction)) + .stellarTransactions(listOf(stellarTransaction)) .externalTransactionId("external-tx-id") .customers( Customers( From e0d0edbe9ec89795ef986f553d72a4449dde3e18 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Thu, 1 Sep 2022 20:52:35 -0300 Subject: [PATCH 45/65] Feature: add integration tests to CI (#536) ### What Add a new CI step that executes end-to-end tests for the project on the branches `develop` and `release/**`, `hotfix/**`, and whenever we launch a new GH release. ### Why So we have a safer workflow, where we make sure that our changes are not breaking any of the use cases we support. --- .github/workflows/end_to_end_tests.yml | 33 +++++++++++++++++++ .github/workflows/release_main.yml | 5 ++- ...d to End Testing with Different Configs.md | 6 ++++ .../docker-compose.base.yaml | 14 +++++++- .../docker-entrypoint.sh | 2 +- 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/end_to_end_tests.yml diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml new file mode 100644 index 0000000000..7a7acfa3a1 --- /dev/null +++ b/.github/workflows/end_to_end_tests.yml @@ -0,0 +1,33 @@ +name: Integration Tests + +on: + workflow_call: # Allows this workflow to be called from another workflow + push: + branches: + - main + - develop + - 'release/**' + - 'releases/**' + - 'hotfix/**' + +jobs: + end_to_end_tests: + runs-on: ubuntu-latest + env: + E2E_SECRET: ${{ secrets.E2E_SECRET }} + OMNIBUS_ALLOWLIST_KEYS: ${{ secrets.OMNIBUS_ALLOWLIST_KEYS }} + + steps: + - uses: actions/checkout@v3 + + - name: Build images for end-to-end tests + run: make build-docker-compose-tests + + - name: Run end-to-end tests (Default Config) + run: make run-e2e-test-default-config + + - name: Run end-to-end tests (Unique Address) + run: make run-e2e-test-unique-address + + - name: Run end-to-end tests (Allowlist) + run: make run-e2e-test-allowlist diff --git a/.github/workflows/release_main.yml b/.github/workflows/release_main.yml index d84cdb6b5b..63e97d6b79 100644 --- a/.github/workflows/release_main.yml +++ b/.github/workflows/release_main.yml @@ -10,7 +10,10 @@ on: jobs: tests: - uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests + uses: ./.github/workflows/basic_tests.yml # execute the callable basic_tests.yml + + end_to_end_tests: + uses: ./.github/workflows/end_to_end_tests.yml # execute the callable end_to_end_tests.yml build_and_push_docker_image: needs: [tests] diff --git a/docs/02 - Contributing/G - End to End Testing with Different Configs.md b/docs/02 - Contributing/G - End to End Testing with Different Configs.md index 331d7a6a53..dbfb67203c 100644 --- a/docs/02 - Contributing/G - End to End Testing with Different Configs.md +++ b/docs/02 - Contributing/G - End to End Testing with Different Configs.md @@ -71,6 +71,12 @@ directory. : $(call run_tests,) ``` +1) Update the `end_to_end_tests.yml` GitHub Action by adding a new step to run the new configuration (Note: replace `` and +``): + ```yaml + - name: Run end-to-end tests () + run: make + ``` ### Running Tests: diff --git a/integration-tests/docker-compose-configs/docker-compose.base.yaml b/integration-tests/docker-compose-configs/docker-compose.base.yaml index bf7988e3a9..1f72e06cf8 100644 --- a/integration-tests/docker-compose-configs/docker-compose.base.yaml +++ b/integration-tests/docker-compose-configs/docker-compose.base.yaml @@ -17,6 +17,8 @@ services: depends_on: - db - kafka + extra_hosts: + - "host.docker.internal:host-gateway" anchor-reference-server: image: anchor-platform @@ -30,16 +32,22 @@ services: - /config depends_on: - anchor-platform-server + extra_hosts: + - "host.docker.internal:host-gateway" db: image: postgres:latest environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password + extra_hosts: + - "host.docker.internal:host-gateway" zookeeper: platform: linux/x86_64 image: confluentinc/cp-zookeeper:latest + extra_hosts: + - "host.docker.internal:host-gateway" kafka: platform: linux/x86_64 @@ -51,6 +59,8 @@ services: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + extra_hosts: + - "host.docker.internal:host-gateway" end-to-end-tests: build: @@ -60,4 +70,6 @@ services: - anchor-platform-server - anchor-reference-server environment: - - E2E_SECRET=${E2E_SECRET?err} \ No newline at end of file + - E2E_SECRET=${E2E_SECRET?err} + extra_hosts: + - "host.docker.internal:host-gateway" diff --git a/integration-tests/docker-compose-configs/docker-entrypoint.sh b/integration-tests/docker-compose-configs/docker-entrypoint.sh index 01bf67de49..f00d4a4fce 100644 --- a/integration-tests/docker-compose-configs/docker-entrypoint.sh +++ b/integration-tests/docker-compose-configs/docker-entrypoint.sh @@ -2,7 +2,7 @@ cp -rf /config_defaults/* /config # replace default files in /config with override files -if [ -d \"/config_override\" ]; +if [ -d "/config_override" ]; then cp -rf /config_override/* /config; fi From 46160b04a7791fffacae023ac829a4a348c6c558 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Wed, 14 Sep 2022 19:07:24 -0300 Subject: [PATCH 46/65] Feature: update the Stellar Payment observer to be a standalone service (#546) ### What Update the Stellar Payment observer to be a standalone service. ### Why Close #294 --- .github/workflows/basic_tests.yml | 15 +- .../api/platform/HealthCheckResponse.java | 7 +- .../org/stellar/anchor/config/SepConfig.java | 3 - .../anchor/event/KafkaEventService.java | 7 +- .../stellar/anchor/sep31/Sep31Service.java | 4 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 1 + docker-compose.yaml | 2 +- ...- Running & Configuring the Application.md | 8 +- .../example-fargate/Dockerfile | 2 + .../example_config/anchor_config.yaml | 9 +- .../example-fargate/readme.md | 2 +- .../example-fargate/scripts/copy_config.sh | 1 + .../scripts/stellar_observer.sh | 11 ++ .../example-fargate/terraform/task_def_ref.tf | 7 +- .../example-fargate/terraform/task_def_sep.tf | 4 - .../terraform/task_def_stellar_observer.tf | 112 +++++++++++ .../stellar-observer-deployment.yaml | 33 ++++ helm-charts/sep-service/README.md | 174 +++++++++--------- .../sep-service/templates/deployment.yaml | 4 +- .../docker-compose.base.yaml | 21 +++ .../platform/AnchorPlatformIntegrationTest.kt | 23 +-- .../platform/ApiKeyAuthIntegrationTest.kt | 3 +- .../stellar/anchor/platform/PlatformTests.kt | 18 +- .../org/stellar/anchor/platform/SystemUtil.kt | 24 +++ .../integration-test.anchor-config.yaml | 4 +- .../anchor/platform/AnchorPlatformServer.java | 6 +- .../anchor/platform/PaymentConfig.java | 2 + .../platform/StellarObservingService.java | 62 +++++++ .../PaymentOperationToEventListener.java | 2 + .../resources/anchor-config-defaults.yaml | 11 +- .../anchor/platform/ServiceRunner.java | 16 +- 31 files changed, 445 insertions(+), 153 deletions(-) delete mode 100644 core/src/main/java/org/stellar/anchor/config/SepConfig.java create mode 100755 docs/resources/deployment-examples/example-fargate/scripts/stellar_observer.sh create mode 100644 docs/resources/deployment-examples/example-fargate/terraform/task_def_stellar_observer.tf create mode 100644 docs/resources/deployment-examples/example-k8s/anchor-platform-dev/stellar-observer-deployment.yaml create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/SystemUtil.kt create mode 100644 platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index 2b7b18d0a7..bb4ae96eda 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -22,13 +22,20 @@ jobs: run: ./gradlew spotlessCheck || echo "❌ Your code is not properly formatted. You can run './gradlew spotlessApply' to format it. 👀" - name: Clean, Build and Test + run: ./gradlew clean build + + - name: Print Test Results + if: success() || failure() run: | - ./gradlew clean build - echo *** SEP test report *** + echo "\n\n*** API Schema test report ***\n" + cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/api-schema/build/reports/tests/test/index.html + echo "\n\n*** SEP test report ***\n" cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/core/build/reports/tests/test/index.html - echo *** Platform test report *** + echo "\n\n*** Platform test report ***\n" cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/platform/build/reports/tests/test/index.html - echo *** Anchor reference server test report *** + echo "\n\n*** Integration tests report ***\n" + cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/integration-tests/build/reports/tests/test/index.html + echo "\n\n*** Anchor reference server test report ***\n" cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/anchor-reference-server/build/reports/tests/test/index.html sep_validation_suite: diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java index c4359a7f00..ef3aa6536a 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResponse.java @@ -11,7 +11,8 @@ @Data public class HealthCheckResponse { - final Instant started; + @SerializedName("started_at") + final Instant startedAt; final String version = Metadata.getVersion(); @@ -24,7 +25,7 @@ public class HealthCheckResponse { Map checks; public HealthCheckResponse() { - this.started = Instant.now(); + this.startedAt = Instant.now(); } public HealthCheckResponse complete(List results) { @@ -33,7 +34,7 @@ public HealthCheckResponse complete(List results) { checks.put(result.name(), result); } numberOfChecks = checks.size(); - elapsedTime = Duration.between(started, Instant.now()); + elapsedTime = Duration.between(startedAt, Instant.now()); return this; } diff --git a/core/src/main/java/org/stellar/anchor/config/SepConfig.java b/core/src/main/java/org/stellar/anchor/config/SepConfig.java deleted file mode 100644 index a9e0cbbaa8..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/SepConfig.java +++ /dev/null @@ -1,3 +0,0 @@ -package org.stellar.anchor.config; - -public class SepConfig {} diff --git a/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java b/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java index 96f5fca74b..afa32451ce 100644 --- a/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java +++ b/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java @@ -10,19 +10,17 @@ import org.apache.kafka.common.header.internals.RecordHeader; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.kafka.support.serializer.JsonSerializer; -import org.springframework.stereotype.Component; import org.stellar.anchor.config.KafkaConfig; import org.stellar.anchor.event.models.AnchorEvent; import org.stellar.anchor.util.Log; -@Component public class KafkaEventService implements EventPublishService { final Producer producer; final Map eventTypeToQueue; final boolean useSingleQueue; public KafkaEventService(KafkaConfig kafkaConfig) { - // TODO: log the event config + Log.debugF("kafkaConfig: {}", kafkaConfig); Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServer()); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); @@ -36,8 +34,7 @@ public KafkaEventService(KafkaConfig kafkaConfig) { "software.amazon.msk.auth.iam.IAMClientCallbackHandler"); } - this.producer = new KafkaProducer(props); - + this.producer = new KafkaProducer<>(props); this.eventTypeToQueue = kafkaConfig.getEventTypeToQueue(); this.useSingleQueue = kafkaConfig.isUseSingleQueue(); } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index 1338e67624..4856f9cd61 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -139,6 +139,7 @@ public Sep31PostTransactionResponse postTransaction( .build(); Amount fee = Context.get().getFee(); + Instant now = Instant.now(); Sep31Transaction txn = new Sep31TransactionBuilder(sep31TransactionStore) .id(generateSepTransactionId()) @@ -146,7 +147,8 @@ public Sep31PostTransactionResponse postTransaction( .statusEta(null) .amountFee(fee.getAmount()) .amountFeeAsset(fee.getAsset()) - .startedAt(Instant.now()) + .startedAt(now) + .updatedAt(now) // this will be overwritten by the sep31TransactionStore#save method. .completedAt(null) .stellarTransactionId(null) .externalTransactionId(null) 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 9cc364a29e..025b71e269 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -787,6 +787,7 @@ class Sep31ServiceTest { "amountFee": "10", "amountFeeAsset": "$stellarUSDC", "startedAt": "$txStartedAt", + "updatedAt": "$txStartedAt", "quoteId": "my_quote_id", "clientDomain": "vibrant.stellar.org", "fields": { diff --git a/docker-compose.yaml b/docker-compose.yaml index a1c281446d..47b6665076 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,7 +4,7 @@ services: build: context: . dockerfile: ./Dockerfile - command: --anchor-reference-server --sep-server + command: --anchor-reference-server --sep-server --stellar-observer environment: - STELLAR_ANCHOR_CONFIG=file:/config/anchor-docker-compose-config.yaml - REFERENCE_SERVER_CONFIG_ENV=file:/reference-config/anchor-reference-server-docker-compose-config.yaml diff --git a/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md b/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md index b2288e3289..c5fe361990 100644 --- a/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md +++ b/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md @@ -26,6 +26,8 @@ This section covers how to run the application from source code using the provid - This uses the default configuration file at [`anchor-reference-server.yaml`], but you can use a custom configuration file by setting the `REFERENCE_SERVER_CONFIG_ENV` environment variable to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. 4. Start the Anchor Platform: `./gradlew service-runner:bootRun --args=--sep-server` - This uses the default configuration file at [`anchor-config-defaults.yaml`], but you can use a custom configuration file by setting the `STELLAR_ANCHOR_CONFIG` environment variable to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. +5. Start the Stellar Observer: `./gradlew service-runner:bootRun --args=--stellar-observer` + - This also uses the default configuration file at [`anchor-config-defaults.yaml`], but you can use a custom configuration file by setting the `STELLAR_ANCHOR_CONFIG` environment variable to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. ## Configuring the Project @@ -63,7 +65,7 @@ The Platform configuration loader tries to fetch the configuration from up to th 1. The JVM Option `-Dstellar.anchor.config`, for instance: ```shell - ./gradlew service-runner:bootRun --args=--sep-server -PjvmArgs="-Dstellar.anchor.config=[path-to-yaml]" + ./gradlew service-runner:bootRun --args="--sep-server --stellar-observer" -PjvmArgs="-Dstellar.anchor.config=[path-to-yaml]" ``` 2. The file `.anchor/anchor-config.yaml` in the user's home directory. If the path of the `yaml` is not specified by the JVM options, the server searches for the `./anchor/anchor-config.yaml` file in the user's home directory. @@ -72,7 +74,7 @@ The Platform configuration loader tries to fetch the configuration from up to th ```shell STELLAR_ANCHOR_CONFIG=classpath:/anchor-config-defaults.yaml - ./gradlew service-runner:bootRun --args=--sep-server + ./gradlew service-runner:bootRun --args="--sep-server --stellar-observer" ``` If all of the above fail, the server fails with an error. @@ -142,7 +144,7 @@ docker run -v {/local/path/to/config/file/}:/config -p 8081:8081 stellar-anchor- --env-file ./my-env-file ``` -> Note 1: this image can run --sep-server (port: 8080), --anchor-reference-server (port: 8081). +> Note 1: this image can run --sep-server (port: 8080), --anchor-reference-server (port: 8081) and --stellar-observer (no port needed). > Note 2: to check all the available environment variables, please refer to the [`anchor-config-defaults.yaml`] file. diff --git a/docs/resources/deployment-examples/example-fargate/Dockerfile b/docs/resources/deployment-examples/example-fargate/Dockerfile index 0470ae4918..898275c280 100644 --- a/docs/resources/deployment-examples/example-fargate/Dockerfile +++ b/docs/resources/deployment-examples/example-fargate/Dockerfile @@ -6,6 +6,8 @@ ADD ./scripts/copy_config.sh /copy_config.sh RUN chmod +x /copy_config.sh ADD ./scripts/sep.sh /config_files/sep.sh RUN chmod +x /config_files/sep.sh +ADD ./scripts/stellar_observer.sh /config_files/stellar_observer.sh +RUN chmod +x /config_files/stellar_observer.sh RUN echo $(ls -1 /config) ADD ./anchor_config.yaml /config_files/anchor_config.yaml ADD ./reference_config.yaml /config_files/reference_config.yaml diff --git a/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml b/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml index 507019d5f9..d60349961f 100644 --- a/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml +++ b/docs/resources/deployment-examples/example-fargate/example_config/anchor_config.yaml @@ -27,11 +27,10 @@ stellar: # `data-spring-jdbc` is the only `type` value supported for now. # data-access: - type: data-spring-jdbc # Activate [config-spring-jdbc] module. - settings: data-spring-jdbc-h2 - #type: data-spring-jdbc - #settings: data-spring-jdbc-sqlite # The absolute location of the configuration data in this yaml file - #settings: data-spring-jdbc-local-postgres # use local postgres instance + type: data-spring-jdbc # Activate [config-spring-jdbc] module. + settings: data-spring-jdbc-sqlite # The absolute location of the configuration data in this yaml file + #settings: data-spring-jdbc-h2 + #settings: data-spring-jdbc-local-postgres # use local postgres instance #settings: data-spring-jdbc-aws-aurora-postgres # use AWS Aurora Postgres with IAM authentication # # Sets how the generates the logging information diff --git a/docs/resources/deployment-examples/example-fargate/readme.md b/docs/resources/deployment-examples/example-fargate/readme.md index 682e99830a..d01b06829e 100644 --- a/docs/resources/deployment-examples/example-fargate/readme.md +++ b/docs/resources/deployment-examples/example-fargate/readme.md @@ -12,7 +12,7 @@ This documentation will configure AWS Infrastructure, Anchor Platform and a samp # Steps 1. Pre-requisites - 1. Fork the [stellar-java-anchor-sdk](https://github.com/stellar/java-stellar-anchor-sdk_) github repository + 1. Fork the [stellar-java-anchor-sdk](https://github.com/stellar/java-stellar-anchor-sdk) github repository 2. Create AWS Account and IAM account with deployment permissions 3. Create DNS Hosted Zone for Anchor Platform with public DNS 4. Configure Terraform Cloud Account diff --git a/docs/resources/deployment-examples/example-fargate/scripts/copy_config.sh b/docs/resources/deployment-examples/example-fargate/scripts/copy_config.sh index 61152118e7..b9fe2c31ba 100755 --- a/docs/resources/deployment-examples/example-fargate/scripts/copy_config.sh +++ b/docs/resources/deployment-examples/example-fargate/scripts/copy_config.sh @@ -3,6 +3,7 @@ cp -v /config_files/stellar_wks.toml /anchor_config cp -v /config_files/anchor_config.yaml /anchor_config cp -v /config_files/reference_config.yaml /anchor_config cp -v /config_files/sep.sh /anchor_config +cp -v /config_files/stellar_observer.sh /anchor_config cat /anchor_config/anchor_config.yaml cat /anchor_config/stellar_wks.toml cat /anchor_config/reference_config.yaml diff --git a/docs/resources/deployment-examples/example-fargate/scripts/stellar_observer.sh b/docs/resources/deployment-examples/example-fargate/scripts/stellar_observer.sh new file mode 100755 index 0000000000..de82209eea --- /dev/null +++ b/docs/resources/deployment-examples/example-fargate/scripts/stellar_observer.sh @@ -0,0 +1,11 @@ +#! /bin/bash +echo "ls /anchor_config" +cat /anchor_config/anchor_config.yaml +cat /anchor_config/stellar_wks.toml +echo "ls /app" +ls /app +echo "env" +env +echo "starting stellar observer..." +export _JAVA_OPTIONS=-Dlogging.level.org.springframework=DEBUG +java -jar /app/anchor-platform-runner.jar --stellar-observer \ No newline at end of file diff --git a/docs/resources/deployment-examples/example-fargate/terraform/task_def_ref.tf b/docs/resources/deployment-examples/example-fargate/terraform/task_def_ref.tf index 23e3585316..ed388d1057 100644 --- a/docs/resources/deployment-examples/example-fargate/terraform/task_def_ref.tf +++ b/docs/resources/deployment-examples/example-fargate/terraform/task_def_ref.tf @@ -3,11 +3,10 @@ resource "aws_ecs_task_definition" "ref" { requires_compatibilities = ["FARGATE"] cpu = 256 memory = 512 - family = "${var.environment}-ref" + family = "${var.environment}-ref" execution_role_arn = aws_iam_role.ecs_task_execution_role.arn task_role_arn = aws_iam_role.ecs_task_role.arn - - volume { + volume { name = "config" } @@ -27,7 +26,7 @@ resource "aws_ecs_task_definition" "ref" { logConfiguration = { "logDriver": "awslogs", "options": { - "awslogs-group": "anchor-platform", + "awslogs-group": "anchorplatform", "awslogs-region": "${var.aws_region}", "awslogs-create-group": "true", "awslogs-stream-prefix": "ref-config" diff --git a/docs/resources/deployment-examples/example-fargate/terraform/task_def_sep.tf b/docs/resources/deployment-examples/example-fargate/terraform/task_def_sep.tf index e63b178a91..b92a303298 100644 --- a/docs/resources/deployment-examples/example-fargate/terraform/task_def_sep.tf +++ b/docs/resources/deployment-examples/example-fargate/terraform/task_def_sep.tf @@ -89,10 +89,6 @@ resource "aws_ecs_task_definition" "sep" { { "name": "STELLAR_ANCHOR_CONFIG", "value": "file:/anchor_config/anchor_config.yaml" - }, - { - "name": "TEST3", - "value": "TEST3" } ], logConfiguration = { diff --git a/docs/resources/deployment-examples/example-fargate/terraform/task_def_stellar_observer.tf b/docs/resources/deployment-examples/example-fargate/terraform/task_def_stellar_observer.tf new file mode 100644 index 0000000000..6fd056d07a --- /dev/null +++ b/docs/resources/deployment-examples/example-fargate/terraform/task_def_stellar_observer.tf @@ -0,0 +1,112 @@ +resource "aws_ecs_task_definition" "sep" { + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = 256 + memory = 512 + family = "${var.environment}-stellar-observer" + execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + volume { + name = "config" + } + + container_definitions = jsonencode([{ + name = "${var.environment}-stellar-observer" + image = "${var.aws_account}.dkr.ecr.${var.aws_region}.amazonaws.com/${aws_ecr_repository.anchor_config.name}:latest" + entryPoint = ["/copy_config.sh"] + + essential = false + "mountPoints": [ + { + "readOnly": false, + "containerPath": "/anchor_config", + "sourceVolume": "config" + } + ], + logConfiguration = { + "logDriver": "awslogs", + "options": { + "awslogs-group": "anchorplatform", + "awslogs-region": "${var.aws_region}", + "awslogs-create-group": "true", + "awslogs-stream-prefix": "stellar-observer", + "awslogs-multiline-pattern": "^[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \\w+ -", + } + } + },{ + name = "${var.environment}-sep" //? + image = "stellar/anchor-platform:${var.image_tag}" + dependsOn = [ { + containerName = "${var.environment}-stellar-observer" + condition = "START" + }] + #entryPoint = ["/anchor_config/sep.sh"] + entryPoint = ["java", "-jar", "/app/anchor-platform-runner.jar", "--stellar-observer"] + essential = true + secrets = [ + { + "name": "SQS_ACCESS_KEY", + "valueFrom": data.aws_ssm_parameter.sqs_access_key.arn + }, + { + "name": "SQS_SECRET_KEY", + "valueFrom": data.aws_ssm_parameter.sqs_secret_key.arn + }, + { + "name": "SQLITE_USERNAME", + "valueFrom": data.aws_ssm_parameter.sqlite_username.arn + }, + { + "name": "SQLITE_PASSWORD", + "valueFrom": data.aws_ssm_parameter.sqlite_password.arn + }, + { + "name": "SEP_10_SIGNING_SEED", + "valueFrom": data.aws_ssm_parameter.sep10_signing_seed.arn + }, + { + "name": "JWT_SECRET", + "valueFrom": data.aws_ssm_parameter.jwt_secret.arn + }, + { + "name": "PLATFORM_TO_ANCHOR_SECRET", + "valueFrom": data.aws_ssm_parameter.platform_to_anchor_secret.arn + }, + { + "name": "ANCHOR_TO_PLATFORM_SECRET", + "valueFrom": data.aws_ssm_parameter.anchor_to_platform_secret.arn + } + ] + + "mountPoints": [ + { + "readOnly": true, + "containerPath": "/anchor_config", + "sourceVolume": "config" + } + ] + "environment": [ + { + "name": "STELLAR_ANCHOR_CONFIG", + "value": "file:/anchor_config/anchor_config.yaml" + } + ], + logConfiguration = { + "logDriver": "awslogs", + "options": { + "awslogs-group": "anchorplatform", + "awslogs-region": "${var.aws_region}", + "awslogs-create-group": "true", + "awslogs-stream-prefix": "stellar-observer", + "awslogs-multiline-pattern": "^[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \\w+ -", + } + + } + portMappings = [{ + protocol = "tcp" + containerPort = 8080 + hostPort = 8080 + }] + } + ]) +} \ No newline at end of file diff --git a/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/stellar-observer-deployment.yaml b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/stellar-observer-deployment.yaml new file mode 100644 index 0000000000..04516095fc --- /dev/null +++ b/docs/resources/deployment-examples/example-k8s/anchor-platform-dev/stellar-observer-deployment.yaml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anchor-platform-stellar-observer + namespace: anchor-platform-dev + labels: + app: anchor-platform-stellar-observer + annotations: + fluxcd.io/automated: "true" + reloader.stakater.com/auto: "true" +spec: + replicas: 1 + selector: + matchLabels: + app: anchor-platform-stellar-observer + template: + metadata: + labels: + app: anchor-platform-stellar-observer + spec: + containers: + - name: stellar-observer + image: stellar/anchor-platform:latest + args: ["--stellar-observer"] + imagePullPolicy: Always + volumeMounts: + - name: config-volume + mountPath: /config + readOnly: true + volumes: + - name: config-volume + configMap: + name: anchor-platform-sep-server-config diff --git a/helm-charts/sep-service/README.md b/helm-charts/sep-service/README.md index 1dc6ed6111..c482336973 100644 --- a/helm-charts/sep-service/README.md +++ b/helm-charts/sep-service/README.md @@ -1,57 +1,66 @@ # Anchor Platform SEP Service Helm Chart + ## Introduction + This chart installs the Stellar Anchor Platform SEP Serviceon Kubernetes cluster using the Helm package manager. ## Upgrading your Clusters + To upgrade the Anchor Platform, you can use the helm upgrade command. example: -``` +```sh helm upgrade -f myvalues.yaml -f override.yaml --version 0.1.0 my-release stellar/anchorplatform ``` ## Installing the Chart + Add the Anchor Platform SEP Service Helm Chart repository: -``` +```sh $ helm repo add stellar *stellar-helm-chart-repo* (this will be added once available) ``` To install the chart with the release name my-release: -``` +```sh $ helm install my-release stellar/anchorplatform ``` ## Uninstalling the Chart + To uninstall/delete the my-release deployment: -``` +```sh $ helm delete my-release The command removes all the Kubernetes components associated with the operator and deletes the release. ``` ## Configuration + Helm Chart Configuration will be used to configure deployment via Helm Chart values substitution. If a parameter value is not provided, Anchor Platform will attempt to use a `classpath` [default values file](../../platform/src/main/resources/anchor-config-defaults.yaml). For further information on all available parameters and Anchor Platform configuration, please refer to the [Anchor Platform Configuration Guide](../../docs/00%20-%20Stellar%20Anchor%20Platform.md) ### Helm Chart Kubernetes Configuration + The following table lists the configurable parameters of the Anchor Platform chart and their default values. These are also reflected in the example [values.yaml](./values.yaml). -| Parameter | Description | Required? | Default Value | -|---|---|---|---| -| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | -| service.containerPort | ingress backend container port | yes | 8080 | -| service.servicePort | ingress backend service port | yes | 8080 | -| service.replicas | number of instances of anchor platform | yes | 1 | -| service.type | service type | yes | NodePort | -| service.name | name of service | yes | sep | -| image.repo | dockerhub image repository | yes | stellar | -| image.name | dockerhub image name | yes | anchor-platform | -| image.tag | dockerhub anchorplatform dag | yes | latest | -| deployment.replicas | number of instances | yes | 1 | -| deployment.envFrom | kubernetes secrets name | no | N/A | -| ingress.metadata | ingress metadata (list) | no | N/A | -| ingress.labels | ingress labels (list) | no | N/A | -| ingress.annotations | ingress annotations (list) | no | N/A | -| ingress.tls.host | tls certificate hostname | no | N/A | -| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | N/A | -| ingress.rules | ingress backend rules (list) | | | + +| Parameter | Description | Required? | Default Value | +|------------------------|------------------------------------------------------------------------------------------|-----------|-----------------| +| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | +| service.containerPort | ingress backend container port | yes | 8080 | +| service.servicePort | ingress backend service port | yes | 8080 | +| service.replicas | number of instances of anchor platform | yes | 1 | +| service.type | service type | yes | NodePort | +| service.name | name of service | yes | sep | +| image.repo | dockerhub image repository | yes | stellar | +| image.name | dockerhub image name | yes | anchor-platform | +| image.tag | dockerhub anchorplatform dag | yes | latest | +| deployment.replicas | number of instances | yes | 1 | +| deployment.envFrom | kubernetes secrets name | no | N/A | +| ingress.metadata | ingress metadata (list) | no | N/A | +| ingress.labels | ingress labels (list) | no | N/A | +| ingress.annotations | ingress annotations (list) | no | N/A | +| ingress.tls.host | tls certificate hostname | no | N/A | +| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | N/A | +| ingress.rules | ingress backend rules (list) | | | ### Database + Unless you are using sql-lite (default configuration) your values.yaml should contain database access configuration, it should contain both the stellar.anchor.data_access.type (currently only `data-spring-jdbc` is supported) and `stellar.anchor.data_access.setttings` which contains the name of the yaml key (nested under key `stellar`) containing your database configuration settings. For example, if you plan to use AWS Aurora, you would set the data_access type and settings along with the configuration for database access as follows: -``` +```yaml stellar: anchor: data_access: @@ -72,9 +81,10 @@ stellar: ``` ### Secrets Configuration + The following is an example kubernetes secrets manifest that will store base64 encoded secrets referenced using placeholders in the anchor platform configuration file. In the following example, by replacing configuration values with ${JWT_SECRET} and ${SEP10_SIGNING_SEED} anchor platform will read those values from environment variables injected by the kubernetes deployment. -``` +```yaml apiVersion: v1 data: JWT_SECRET: c2VjcmV0 @@ -87,10 +97,11 @@ type: Opaque ``` # Assets Configuration + The following is an example of the `assets` configuration that can be set in the helm chart's `values.yaml`. Not setting this section will pass in an empty assets list. -``` +```yaml assets: - "schema": "stellar" "code": "USDC" @@ -153,63 +164,62 @@ assets: ``` ## Helm Chart Kubernetes Configuration + The following table lists the configurable parameters of the Anchor Platform chart and their default values. These are also reflected in the example [values.yaml](./values.yaml). -| Parameter | Description | Required? | Default Value | -|---|---|---|---| -| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | -| service.containerPort | ingress backend container port | yes | 8080 | -| service.servicePort | ingress backend service port | yes | 8080 | -| service.replicas | number of instances of anchor platform | yes | 1 | -| service.type | service type | yes | NodePort | -| service.name | name of service | yes | sep | -| image.repo | dockerhub image repository | yes | stellar | -| image.name | dockerhub image name | yes | anchor-platform | -| image.tag | dockerhub anchorplatform dag | yes | latest | -| deployment.replicas | number of instances | yes | 1 | -| deployment.envFrom | kubernetes secrets name | no | n/a | -| ingress.metadata | ingress metadata (list) | no | n/a | -| ingress.labels | ingress labels (list) | no | n/a | -| ingress.annotations | ingress annotations (list) | no | n/a | -| ingress.tls.host | tls certificate hostname | no | n/a | -| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | n/a | -| ingress.rules | ingress backend rulues (list) | | | ->>>>>>> main:helm-charts/sep-service/README.md + +| Parameter | Description | Required? | Default Value | +|------------------------|------------------------------------------------------------------------------------------|-----------|-----------------| +| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | +| service.containerPort | ingress backend container port | yes | 8080 | +| service.servicePort | ingress backend service port | yes | 8080 | +| service.replicas | number of instances of anchor platform | yes | 1 | +| service.type | service type | yes | NodePort | +| service.name | name of service | yes | sep | +| image.repo | dockerhub image repository | yes | stellar | +| image.name | dockerhub image name | yes | anchor-platform | +| image.tag | dockerhub anchorplatform dag | yes | latest | +| deployment.replicas | number of instances | yes | 1 | +| deployment.envFrom | kubernetes secrets name | no | n/a | +| ingress.metadata | ingress metadata (list) | no | n/a | +| ingress.labels | ingress labels (list) | no | n/a | +| ingress.annotations | ingress annotations (list) | no | n/a | +| ingress.tls.host | tls certificate hostname | no | n/a | +| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | n/a | +| ingress.rules | ingress backend rulues (list) | | | ## Helm Chart Stellar Anchor Platform Configuration + The following table lists the additional configurable parameters of the Anchor Platform chart and their default values. -| Parameter | Description | Required? | Default Value | -|---|---|---|---| -| stellar.app_config.app.hostUrl | URL of the Anchor Platform SEP Service | y | N/A | -| stellar.app_config.app.jwtSecretKey | web encryption key | y | ${JWT_SECRET_KEY} | N/A | -| stellar.app_config.app.stellarNetwork | TESTNET OR PUBNET| n | TESTNET | -| stellar.app_config.app.logLevel | TRACE,DEBUG,INFO,WARN,ERROR,FATAL | n | INFO | -| stellar.anchor.data_access.type | database access type | yes | data-spring-jdbc | -| stellar.anchor.data_access.setttings | values config root-level key for jdbc config | yes | data-spring-jdbc-sqlite | -| stellar.toml.documentation.ORG_NAME | organization name to configure stellar.toml | yes | My Organization | -| stellar.toml.documentation.ORG_URL | your organization URL to configure stellar.toml | yes | https:/myorg.org | -| stellar.toml.documentation.ORG_DESCRIPTION | your organization description to configure stellar.toml | yes | https://mylogo.png | -| stellar.toml.documentation.ORG_SUPPORT_EMAIL | your organization support email address to configure stellar.toml | yes | myname@myorg.org | -| stellar.app_config.app.integration_auth.auth_type | Anchor Platform to Anchor Backend Authentication type JWT_TOKEN, API_KEY or NONE | n | NONE | -| stellar.app_config.app.integration_auth.platformToAnchorSecret | secret value | n | ${PLATFORM_TO_ANCHOR_SECRET} | -| stellar.app_config.app.integration_auth.anchorToPlatformSecret | secret value | n | ${ANCHOR_TO_PLATFORM_SECRET} | -| stellar.app_config.app.integration_auth.expirationMilliseconds | integration auth credential expiration ms | n | 30000 | -| stellar.app_config.app.anchor_callback | endpoint to retrieve unique address & memo for sep 31 post /transaction | n | N/A | -| sep1.enabled | sep1 true if service enabled | yes | true | -| sep10.enabled | sep1 true if service enabled | yes | true | -| sep10.homeDomain | a domain hosting a SEP-1 stellar.toml | n | N/A | -| sep10.signingSeed | secret key of Stellar Account used for auth. (public key in stellar.toml ACCOUNTS) | y | N/A | -| sep12.enabled | sep1 true if service enabled | yes | true | -| sep12.customerIntegrationEndpoint| URL of SEP 12 KYC Endpoint | no | N/A | -| sep31.enabled | sep1 true if service enabled | yes | true | -| sep31.feeIntegrationEndPoint | URL of Fees Endpoint | no | N/A | -| sep38.enabled | sep1 true if service enabled | yes | true | -| sep38.quoteIntegrationEndPoint | URL of Quotes Endpoint | no | N/A | -| event.enabled | sep1 true if event service enabled | yes | true | -| event.publisherType | kafka|sqs | no | kafka | -<<<<<<< HEAD:helm-charts/sep-service/readme.md -| kafka_publisher.bootstrapServer | kafka broker host:port | no | N/A | -| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | -======= -| kafka_publisher.bootstrapServer | kafka broker host:port | no | n/a | -| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | -| assets | see [Assets Configuration](#assets-configuration) | yes | [] | + +| Parameter | Description | Required? | Default Value | +|----------------------------------------------------------------|------------------------------------------------------------------------------------|-----------|------------------------------| +| stellar.app_config.app.hostUrl | URL of the Anchor Platform SEP Service | y | N/A | +| stellar.app_config.app.jwtSecretKey | web encryption key | y | ${JWT_SECRET_KEY} | N/A | +| stellar.app_config.app.stellarNetwork | TESTNET OR PUBNET | n | TESTNET | +| stellar.app_config.app.logLevel | TRACE,DEBUG,INFO,WARN,ERROR,FATAL | n | INFO | +| stellar.anchor.data_access.type | database access type | yes | data-spring-jdbc | +| stellar.anchor.data_access.setttings | values config root-level key for jdbc config | yes | data-spring-jdbc-sqlite | +| stellar.toml.documentation.ORG_NAME | organization name to configure stellar.toml | yes | My Organization | +| stellar.toml.documentation.ORG_URL | your organization URL to configure stellar.toml | yes | https:/myorg.org | +| stellar.toml.documentation.ORG_DESCRIPTION | your organization description to configure stellar.toml | yes | https://mylogo.png | +| stellar.toml.documentation.ORG_SUPPORT_EMAIL | your organization support email address to configure stellar.toml | yes | myname@myorg.org | +| stellar.app_config.app.integration_auth.auth_type | Anchor Platform to Anchor Backend Authentication type JWT_TOKEN, API_KEY or NONE | n | NONE | +| stellar.app_config.app.integration_auth.platformToAnchorSecret | secret value | n | ${PLATFORM_TO_ANCHOR_SECRET} | +| stellar.app_config.app.integration_auth.anchorToPlatformSecret | secret value | n | ${ANCHOR_TO_PLATFORM_SECRET} | +| stellar.app_config.app.integration_auth.expirationMilliseconds | integration auth credential expiration ms | n | 30000 | +| stellar.app_config.app.anchor_callback | endpoint to retrieve unique address & memo for sep 31 post /transaction | n | N/A | +| sep1.enabled | sep1 true if service enabled | yes | true | +| sep10.enabled | sep1 true if service enabled | yes | true | +| sep10.homeDomain | a domain hosting a SEP-1 stellar.toml | n | N/A | +| sep10.signingSeed | secret key of Stellar Account used for auth. (public key in stellar.toml ACCOUNTS) | y | N/A | +| sep12.enabled | sep1 true if service enabled | yes | true | +| sep12.customerIntegrationEndpoint | URL of SEP 12 KYC Endpoint | no | N/A | +| sep31.enabled | sep1 true if service enabled | yes | true | +| sep31.feeIntegrationEndPoint | URL of Fees Endpoint | no | N/A | +| sep38.enabled | sep1 true if service enabled | yes | true | +| sep38.quoteIntegrationEndPoint | URL of Quotes Endpoint | no | N/A | +| event.enabled | sep1 true if event service enabled | yes | true | +| event.publisherType | kafka | sqs | no | kafka | +| kafka_publisher.bootstrapServer | kafka broker host:port | no | N/A | +| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | +| assets | see [Assets Configuration](#assets-configuration) | yes | [] | diff --git a/helm-charts/sep-service/templates/deployment.yaml b/helm-charts/sep-service/templates/deployment.yaml index da0e920161..88093ace6c 100644 --- a/helm-charts/sep-service/templates/deployment.yaml +++ b/helm-charts/sep-service/templates/deployment.yaml @@ -30,7 +30,9 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" - args: ["--sep-server"] + args: + - --sep-server + - --stellar-observer imagePullPolicy: {{ .Values.image.pullPolicy }} startupProbe: httpGet: diff --git a/integration-tests/docker-compose-configs/docker-compose.base.yaml b/integration-tests/docker-compose-configs/docker-compose.base.yaml index 1f72e06cf8..86cc242983 100644 --- a/integration-tests/docker-compose-configs/docker-compose.base.yaml +++ b/integration-tests/docker-compose-configs/docker-compose.base.yaml @@ -6,6 +6,27 @@ services: context: ../../ dockerfile: integration-tests/docker-compose-configs/Dockerfile command: "--sep-server" + environment: + - STELLAR_ANCHOR_CONFIG=file:/config/anchor-platform-config.yaml + - LOG_APPENDER=console_appender + volumes: + # add mounts for the new config directory + - ./anchor-platform-default-configs:/config_defaults + tmpfs: + - /config + depends_on: + - kafka + - db + - stellar-observer + extra_hosts: + - "host.docker.internal:host-gateway" + + stellar-observer: + image: anchor-platform + build: + context: ../../ + dockerfile: integration-tests/docker-compose-configs/Dockerfile + command: "--stellar-observer" environment: - STELLAR_ANCHOR_CONFIG=file:/config/anchor-platform-config.yaml - LOG_APPENDER=console_appender diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt index 9ac8016554..2839cf8410 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt @@ -19,7 +19,7 @@ import org.stellar.anchor.api.callback.GetRateRequest.Type.* import org.stellar.anchor.api.exception.NotFoundException import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerRequest import org.stellar.anchor.api.sep.sep12.Sep12PutCustomerRequest -import org.stellar.anchor.api.sep.sep38.Sep38Context.* +import org.stellar.anchor.api.sep.sep38.Sep38Context.SEP31 import org.stellar.anchor.auth.AuthHelper import org.stellar.anchor.auth.JwtService import org.stellar.anchor.config.AppConfig @@ -29,7 +29,6 @@ import org.stellar.anchor.config.Sep38Config import org.stellar.anchor.platform.callback.RestCustomerIntegration import org.stellar.anchor.platform.callback.RestFeeIntegration import org.stellar.anchor.platform.callback.RestRateIntegration -import org.stellar.anchor.reference.AnchorReferenceServer import org.stellar.anchor.util.GsonUtils import org.stellar.anchor.util.Sep1Helper @@ -72,23 +71,17 @@ class AnchorPlatformIntegrationTest { const val fiatUSD = "iso4217:USD" const val stellarUSDC = "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" private lateinit var platformServerContext: ConfigurableApplicationContext - init { - val props = System.getProperties() - props.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") - } @BeforeAll @JvmStatic fun setup() { - platformServerContext = - AnchorPlatformServer.start( - SEP_SERVER_PORT, - "/", - mapOf("stellar.anchor.config" to "classpath:/integration-test.anchor-config.yaml"), - true - ) + System.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") + ServiceRunner.startAnchorReferenceServer() - AnchorReferenceServer.start(REFERENCE_SERVER_PORT, "/") + SystemUtil.setEnv("STELLAR_ANCHOR_CONFIG", "classpath:/integration-test.anchor-config.yaml") + platformServerContext = ServiceRunner.startSepServer() + ServiceRunner.startStellarObserver() + SystemUtil.setEnv("STELLAR_ANCHOR_CONFIG", null) } } @@ -325,7 +318,7 @@ class AnchorPlatformIntegrationTest { "sep38.quoteIntegrationEndPoint" to "http://localhost:8081", "payment-gateway.circle.name" to "circle", "payment-gateway.circle.enabled" to "true", - "spring.jpa.properties.hibernate.dialect" to "org.hibernate.dialect.H2Dialect", + "spring.jpa.database-platform" to "org.stellar.anchor.platform.sqlite.SQLiteDialect", "logging.level.root" to "INFO" ) diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt index 88a6de40cc..ca3cd097ce 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt @@ -35,8 +35,7 @@ import org.stellar.anchor.util.OkHttpUtil class ApiKeyAuthIntegrationTest { companion object { init { - val props = System.getProperties() - props.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") + System.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") } private val gson = GsonUtils.getInstance() diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt index c774796c1d..e87160798e 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt @@ -86,15 +86,13 @@ fun testHappyPath() { fun testHealth() { val response = platformApiClient.health(listOf("all")) - assertEquals(response["number_of_checks"], 1.0) + assertEquals(5, response.size) assertNotNull(response["checks"]) - val checks = response["checks"] as Map<*, *> - val spo = checks["stellar_payment_observer"] as Map<*, *> - assertEquals(spo["status"], "green") - val streams = spo["streams"] as List> - assertEquals(streams[0]["thread_shutdown"], false) - assertEquals(streams[0]["thread_terminated"], false) - assertEquals(streams[0]["stopped"], false) + assertEquals(0.0, response["number_of_checks"]) + assertNotNull(response["started_at"]) + assertNotNull(response["elapsed_time_ms"]) + assertNotNull(response["number_of_checks"]) + assertNotNull(response["version"]) } fun testSep31UnhappyPath() { @@ -129,7 +127,8 @@ fun testSep31UnhappyPath() { assertTrue(getTxResponse.amountIn.asset.contains(txnRequest.assetCode)) assertEquals(31, getTxResponse.sep) assertNull(getTxResponse.completedAt) - assertTrue(getTxResponse.updatedAt > getTxResponse.startedAt) + assertNotNull(getTxResponse.startedAt) + assertTrue(getTxResponse.updatedAt >= getTxResponse.startedAt) // Modify the customer by erasing its clabe_number to simulate an invalid clabe_number sep12Client.invalidateCustomerClabe(receiverCustomer.id) @@ -156,6 +155,7 @@ fun testSep31UnhappyPath() { assertEquals(TransactionEvent.Status.PENDING_CUSTOMER_INFO_UPDATE.status, patchedTx.status) assertEquals(31, patchedTx.sep) assertEquals("The receiving customer clabe_number is invalid!", patchedTx.message) + assertTrue(patchedTx.updatedAt > patchedTx.startedAt) // GET SEP-31 transaction should return PENDING_CUSTOMER_INFO_UPDATE with a message var gotSep31TxResponse = sep31Client.getTransaction(postTxResponse.id) diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SystemUtil.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SystemUtil.kt new file mode 100644 index 0000000000..4b42896a41 --- /dev/null +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SystemUtil.kt @@ -0,0 +1,24 @@ +package org.stellar.anchor.platform + +import java.lang.reflect.Field + +class SystemUtil { + companion object { + fun setEnv(key: String, value: String?) { + try { + val env = System.getenv() + val cl: Class<*> = env.javaClass + val field: Field = cl.getDeclaredField("m") + field.isAccessible = true + val writableEnv = field.get(env) as MutableMap + if (value == null) { + writableEnv.remove(key) + } else { + writableEnv[key] = value + } + } catch (e: Exception) { + throw IllegalStateException("Failed to set environment variable", e) + } + } + } +} diff --git a/integration-tests/src/test/resources/integration-test.anchor-config.yaml b/integration-tests/src/test/resources/integration-test.anchor-config.yaml index d4823170de..5686c27fcb 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -8,7 +8,7 @@ stellar: settings: app-config # The location of the configuration data data-access: type: data-spring-jdbc # Activate [config-spring-jdbc] module. - settings: data-spring-jdbc-h2 # The location of the configuration data in this file. + settings: data-spring-jdbc-sqlite # The location of the configuration data in this file. logging: type: logging-logback settings: logging-logback-settings @@ -143,6 +143,8 @@ data-spring-jdbc-sqlite: spring.jpa.hibernate.show_sql: false spring.datasource.url: jdbc:sqlite:anchor-proxy.db spring.datasource.driver-class-name: org.sqlite.JDBC + spring.datasource.max-active: 2 + spring.datasource.initial-size: 2 spring.datasource.username: admin spring.datasource.password: admin spring.mvc.converters.preferred-json-mapper: gson diff --git a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java index 17a8d816e5..ff040bc33e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -9,6 +9,7 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.stellar.anchor.platform.configurator.DataAccessConfigurator; @@ -16,6 +17,7 @@ import org.stellar.anchor.platform.configurator.PropertiesReader; import org.stellar.anchor.platform.configurator.SpringFrameworkConfigurator; +@Profile("default") @SpringBootApplication @EnableJpaRepositories(basePackages = {"org.stellar.anchor.platform.data"}) @EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) @@ -60,7 +62,7 @@ public static ConfigurableApplicationContext start( return springApplication.run(); } - public static void start(int port, String contextPath) { - start(port, contextPath, null, false); + public static ConfigurableApplicationContext start(int port, String contextPath) { + return start(port, contextPath, null, false); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java b/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java index 47505943f4..0f51f7ffd5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java @@ -6,6 +6,7 @@ import okhttp3.OkHttpClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.stellar.anchor.api.exception.ServerErrorException; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.asset.AssetService; @@ -23,6 +24,7 @@ @Configuration public class PaymentConfig { @Bean + @Profile("stellar-observer") @SneakyThrows public StellarPaymentObserver stellarPaymentObserverService( AssetService assetService, diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java new file mode 100644 index 0000000000..70748908cf --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java @@ -0,0 +1,62 @@ +package org.stellar.anchor.platform; + +import static org.springframework.boot.Banner.Mode.OFF; + +import java.util.Map; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.stellar.anchor.platform.configurator.DataAccessConfigurator; +import org.stellar.anchor.platform.configurator.PlatformAppConfigurator; +import org.stellar.anchor.platform.configurator.PropertiesReader; +import org.stellar.anchor.platform.configurator.SpringFrameworkConfigurator; + +@Profile("stellar-observer") +@SpringBootApplication +@EnableJpaRepositories(basePackages = {"org.stellar.anchor.platform.data"}) +@EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) +@EnableConfigurationProperties +public class StellarObservingService implements WebMvcConfigurer { + + public static ConfigurableApplicationContext start(Map environment) { + SpringApplicationBuilder builder = + new SpringApplicationBuilder(StellarObservingService.class) + .bannerMode(OFF) + .web(WebApplicationType.NONE) + .properties( + // TODO: update when the ticket + // https://github.com/stellar/java-stellar-anchor-sdk/issues/297 is completed. + "spring.mvc.converters.preferred-json-mapper=gson", + // this allows a developer to use a .env file for local development + "spring.config.import=optional:classpath:example.env[.properties]", + "spring.profiles.active=stellar-observer"); + + if (environment != null) { + builder.properties(environment); + } + + SpringApplication springApplication = builder.build(); + + // Reads the configuration from sources, such as yaml + springApplication.addInitializers(new PropertiesReader()); + // Configure SEPs + springApplication.addInitializers(new PlatformAppConfigurator()); + // Configure databases + springApplication.addInitializers(new DataAccessConfigurator()); + // Configure spring framework + springApplication.addInitializers(new SpringFrameworkConfigurator()); + + return springApplication.run(); + } + + public static ConfigurableApplicationContext start() { + return start(null); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index 71bd4c6ff3..2e8cb8e5b3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -11,6 +11,7 @@ import java.util.Objects; import java.util.UUID; import org.apache.commons.codec.DecoderException; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.SepException; @@ -29,6 +30,7 @@ import org.stellar.sdk.xdr.MemoType; @Component +@Profile("stellar-observer") public class PaymentOperationToEventListener implements PaymentListener { final Sep31TransactionStore transactionStore; final EventPublishService eventService; diff --git a/platform/src/main/resources/anchor-config-defaults.yaml b/platform/src/main/resources/anchor-config-defaults.yaml index 9a53bb36aa..07f0346224 100644 --- a/platform/src/main/resources/anchor-config-defaults.yaml +++ b/platform/src/main/resources/anchor-config-defaults.yaml @@ -13,7 +13,7 @@ stellar: # # Sets how the application configuration values are read. # - # If `type` is `config-spring-property`, the platform server will use the Spring's `@ConfigruationProperties` to + # If `type` is `config-spring-property`, the platform server will use the Spring's `@ConfigurationProperties` to # read populate the configuration values. # # `config-spring-property` is the only `type` value supported in MVP. @@ -30,8 +30,9 @@ stellar: # data-access: type: data-spring-jdbc - settings: data-spring-jdbc-h2 # The absolute location of the configuration data in this yaml file - #settings: data-spring-jdbc-local-postgres # use local postgres instance + settings: data-spring-jdbc-sqlite # The absolute location of the configuration data in this yaml file + #settings: data-spring-jdbc-h2 # use in-memory database. It cannot be shared between server and observer though. + #settings: data-spring-jdbc-local-postgres # use local postgres instance #settings: data-spring-jdbc-aws-aurora-postgres # use AWS Aurora Postgres with IAM authentication # # Sets how the generates the logging information @@ -294,8 +295,8 @@ data-spring-jdbc-sqlite: spring.jpa.hibernate.show_sql: false spring.datasource.url: jdbc:sqlite:anchor-proxy.db spring.datasource.driver-class-name: org.sqlite.JDBC - spring.datasource.max-active: 1 # For SQLite, set this to 1 to avoid database file lock exception. - spring.datasource.initial-size: 1 # For SQLite, set this to 1 to avoid database file lock exception. + spring.datasource.max-active: 2 # For SQLite, set this to 2 to avoid database file lock exception, while letting both sep-server and stellar-observer use it. + spring.datasource.initial-size: 2 # For SQLite, set this to 2 to avoid database file lock exception, while letting both sep-server and stellar-observer use it. spring.datasource.username: ${SQLITE_USERNAME} spring.datasource.password: ${SQLITE_PASSWORD} spring.mvc.converters.preferred-json-mapper: gson diff --git a/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java b/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java index d5721ba01e..471dfed1de 100644 --- a/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java +++ b/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform; import org.apache.commons.cli.*; +import org.springframework.context.ConfigurableApplicationContext; import org.stellar.anchor.reference.AnchorReferenceServer; public class ServiceRunner { @@ -13,6 +14,8 @@ public static void main(String[] args) { options.addOption("h", "help", false, "Print this message."); options.addOption("a", "all", false, "Start all servers."); options.addOption("s", "sep-server", false, "Start SEP endpoint server."); + options.addOption( + "o", "stellar-observer", false, "Start Observer that streams from the Stellar blockchain."); options.addOption("r", "anchor-reference-server", false, "Start anchor reference server."); CommandLineParser parser = new DefaultParser(); @@ -25,6 +28,11 @@ public static void main(String[] args) { anyServerStarted = true; } + if (cmd.hasOption("stellar-observer") || cmd.hasOption("all")) { + startStellarObserver(); + anyServerStarted = true; + } + if (cmd.hasOption("anchor-reference-server") || cmd.hasOption("all")) { startAnchorReferenceServer(); anyServerStarted = true; @@ -38,7 +46,7 @@ public static void main(String[] args) { } } - static void startSepServer() { + static ConfigurableApplicationContext startSepServer() { String strPort = System.getProperty("SEP_SERVER_PORT"); int port = DEFAULT_SEP_SERVER_PORT; if (strPort != null) { @@ -48,7 +56,11 @@ static void startSepServer() { if (contextPath == null) { contextPath = DEFAULT_CONTEXTPATH; } - AnchorPlatformServer.start(port, contextPath); + return AnchorPlatformServer.start(port, contextPath); + } + + static void startStellarObserver() { + StellarObservingService.start(); } static void startAnchorReferenceServer() { From 95a1185c79f2d053456e53d22e08c06ade2099d4 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Thu, 15 Sep 2022 18:58:32 -0300 Subject: [PATCH 47/65] Release 1.1.0 to develop (#555) ## Release Version 1.1.0 * SDK support for [SEP-1], [SEP-10], [SEP-12], [SEP-31] & [SEP-38]. * API support for [SEP-1], [SEP-10], [SEP-12], [SEP-31] & [SEP-38]. * Database support for H2, SQLite, Postgres & Aurora Postgres. * Queue Publishing support for Kafka and SQS. * Stellar network monitoring for incoming [SEP-31] payments. * End-to-end tests through docker-compose. * Updated documentation. * Deployment examples with k8s, helm-charts, and fargate. --- .../ISSUE_TEMPLATE/release_a_new_version.md | 7 +++---- .github/workflows/basic_tests.yml | 3 +++ .github/workflows/release_release.yml | 2 +- CHANGELOG.md | 19 ++++++++++++++++++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_a_new_version.md b/.github/ISSUE_TEMPLATE/release_a_new_version.md index 6d1413fa5a..046c9da372 100644 --- a/.github/ISSUE_TEMPLATE/release_a_new_version.md +++ b/.github/ISSUE_TEMPLATE/release_a_new_version.md @@ -23,9 +23,8 @@ labels: release - [ ] `release/0.1.0 -> main`: this should require two approvals. - [ ] `release/0.1.0 -> develop`: ideally, this should be merged after the `main` branch is merged. - [ ] Create a new release on GitHub with the name `0.1.0` and the changes from the [CHANGELOG.md] file. - - **Work-In-Progress:** The release should trigger the release process on GitHub that will: - 1. Publish a new version of the docker image to Docker Hub. - 2. Publish a new version of the SDK to jitpack and - 3. Automatically upload the jar file to the GH release. + - [ ] The release will automatically publish a new version of the docker image to Docker Hub. + - [ ] You'll need to manually publish a new version of the SDK to jitpack and + - [ ] You'll need to manually upload the jar file from jitpack to the GH release. [CHANGELOG.md]: ../../CHANGELOG.md \ No newline at end of file diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index bb4ae96eda..aba2925b6b 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -5,6 +5,9 @@ name: Basic Tests on: pull_request: + branches-ignore: # ignoring these patterns because this workflow is already being manually called from `release_release.yml`. + - 'release/**' + - 'releases/**' workflow_call: # Allows this workflow to be called from another workflow jobs: diff --git a/.github/workflows/release_release.yml b/.github/workflows/release_release.yml index 61d56113a3..c95f3e925c 100644 --- a/.github/workflows/release_release.yml +++ b/.github/workflows/release_release.yml @@ -32,7 +32,7 @@ jobs: uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b with: push: true - tags: stellar/anchor-platform:${{ steps.get_version.outputs.VERSION }}-edge + tags: stellar/anchor-platform:${{ steps.get_version.outputs.VERSION }}-rc file: Dockerfile complete: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3317a3a914..5941b8ce9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 1.1.0 + +* SDK support for [SEP-1], [SEP-10], [SEP-12], [SEP-31] & [SEP-38]. +* API support for [SEP-1], [SEP-10], [SEP-12], [SEP-31] & [SEP-38]. +* Database support for H2, SQLite, Postgres & Aurora Postgres. +* Queue Publishing support for Kafka and SQS. +* Stellar network monitoring for incoming [SEP-31] payments. +* End-to-end tests through docker-compose. +* Updated documentation. +* Deployment examples with k8s, helm-charts, and fargate. + ## Unreleased -- Add the [Release Checklist](.github/ISSUE_TEMPLATE/release_a_new_version.md). \ No newline at end of file +- Add the [Release Checklist](.github/ISSUE_TEMPLATE/release_a_new_version.md). + +[SEP-1]: https://stellar.org/protocol/sep-1 +[SEP-10]: https://stellar.org/protocol/sep-10 +[SEP-12]: https://stellar.org/protocol/sep-12 +[SEP-31]: https://stellar.org/protocol/sep-31 +[SEP-38]: https://stellar.org/protocol/sep-38 \ No newline at end of file From 077d342ec751ae91141c25a934f4d312d52eb3a0 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Thu, 15 Sep 2022 19:46:30 -0300 Subject: [PATCH 48/65] hotfix: fix how the GH Action gets the tag name (#558) ### What Fix the way the Action tries to grab the Release tag name. ### Why the previous way was not working: https://github.com/stellar/java-stellar-anchor-sdk/actions/runs/3063883688 --- .github/workflows/basic_tests.yml | 3 --- .github/workflows/release_main.yml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index aba2925b6b..bb4ae96eda 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -5,9 +5,6 @@ name: Basic Tests on: pull_request: - branches-ignore: # ignoring these patterns because this workflow is already being manually called from `release_release.yml`. - - 'release/**' - - 'releases/**' workflow_call: # Allows this workflow to be called from another workflow jobs: diff --git a/.github/workflows/release_main.yml b/.github/workflows/release_main.yml index 63e97d6b79..19755b52b5 100644 --- a/.github/workflows/release_main.yml +++ b/.github/workflows/release_main.yml @@ -32,7 +32,7 @@ jobs: uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b with: push: true - tags: stellar/anchor-platform:${{ GITHUB_REF_NAME }},stellar/anchor-platform:latest + tags: stellar/anchor-platform:${{ github.event.release.tag_name }},stellar/anchor-platform:latest file: Dockerfile complete: From 0d451b03347042bdafa8982bbb6ce9e8c488ed3f Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Tue, 20 Sep 2022 18:02:34 -0300 Subject: [PATCH 49/65] Feature: execute the CI when we merge something into the `main` branch (#571) ### What Execute the CI when we merge something into the `main` branch. ### Why To make sure the main branch is being fully tested, at least as much as the develop branch. --- .github/workflows/basic_tests.yml | 13 ++++++++++--- .../{release_develop.yml => branch_develop.yml} | 4 ++-- .../{release_release.yml => branch_release.yml} | 4 ++-- .../workflows/{release_main.yml => published.yml} | 7 ++----- 4 files changed, 16 insertions(+), 12 deletions(-) rename .github/workflows/{release_develop.yml => branch_develop.yml} (90%) rename .github/workflows/{release_release.yml => branch_release.yml} (92%) rename .github/workflows/{release_main.yml => published.yml} (77%) diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index bb4ae96eda..217cdc2c6a 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -6,6 +6,9 @@ name: Basic Tests on: pull_request: workflow_call: # Allows this workflow to be called from another workflow + push: + branches: + - main jobs: build_and_test: @@ -44,6 +47,7 @@ jobs: name: Validate SEPs (1, 10, 12, 31, 38) env: PR_NUMBER: ${{github.event.pull_request.number}} + BRANCH_NAME: ${{github.ref}} # e.g. refs/heads/main steps: - uses: actions/checkout@v3 @@ -52,10 +56,13 @@ jobs: id: endpoint-finder run: | export HOME_DOMAIN=https://anchor-sep-server-dev.stellar.org - if [ ! -z "$PR_NUMBER" ] - then - export HOME_DOMAIN=https://anchor-sep-pr$PR_NUMBER.previews.kube001.services.stellar-ops.com + + if [[ ! -z "$PR_NUMBER" ]]; then + export HOME_DOMAIN=https://anchor-sep-pr$PR_NUMBER.previews.kube001.services.stellar-ops.com + elif [[ $BRANCH_NAME = refs/heads/main ]]; then + export HOME_DOMAIN=https://anchor-sep-server-prd.stellar.org fi + echo HOME_DOMAIN=$HOME_DOMAIN echo "::set-output name=HOME_DOMAIN::$HOME_DOMAIN" diff --git a/.github/workflows/release_develop.yml b/.github/workflows/branch_develop.yml similarity index 90% rename from .github/workflows/release_develop.yml rename to .github/workflows/branch_develop.yml index ff21bac870..dc40e21405 100644 --- a/.github/workflows/release_develop.yml +++ b/.github/workflows/branch_develop.yml @@ -1,4 +1,4 @@ -name: "Release from the Develop Branch" +name: "develop Branch" on: push: @@ -10,9 +10,9 @@ jobs: uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests build_and_push_docker_image: + name: Push to DockerHub # stellar/anchor-platform:{sha} and stellar/anchor-platform:edge needs: [tests] runs-on: ubuntu-latest - name: Push stellar/anchor-platform:sha to DockerHub steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/release_release.yml b/.github/workflows/branch_release.yml similarity index 92% rename from .github/workflows/release_release.yml rename to .github/workflows/branch_release.yml index c95f3e925c..0660d6d290 100644 --- a/.github/workflows/release_release.yml +++ b/.github/workflows/branch_release.yml @@ -1,4 +1,4 @@ -name: "Release from the release/** Branch" +name: "release/** Branch" on: push: @@ -11,9 +11,9 @@ jobs: uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests build_and_push_docker_image: + name: Push to DockerHub # stellar/anchor-platform:sha needs: [tests] runs-on: ubuntu-latest - name: Push stellar/anchor-platform:sha to DockerHub steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/release_main.yml b/.github/workflows/published.yml similarity index 77% rename from .github/workflows/release_main.yml rename to .github/workflows/published.yml index 19755b52b5..9bd9b7325c 100644 --- a/.github/workflows/release_main.yml +++ b/.github/workflows/published.yml @@ -1,7 +1,4 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Release +name: Release Published on: release: @@ -16,9 +13,9 @@ jobs: uses: ./.github/workflows/end_to_end_tests.yml # execute the callable end_to_end_tests.yml build_and_push_docker_image: + name: Push to DockerHub # stellar/anchor-platform:{VERSION} needs: [tests] runs-on: ubuntu-latest - name: Push stellar/anchor-platform:VERSION to DockerHub steps: - uses: actions/checkout@v3 From 33ee6f0a2089d61881bee1e55e34cec6c9a46bcf Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 22 Sep 2022 14:54:07 -0700 Subject: [PATCH 50/65] Configuration management refactoring (#516) Configuration Management Refactoring --- .gitignore | 5 +- .../config/IntegrationAuthSettings.java | 2 +- .../anchor/reference/event/KafkaListener.java | 14 +- .../reference/service/HealthCheckService.java | 5 + .../api/exception/InvalidConfigException.java | 24 + .../anchor/asset/JsonAssetService.java | 1 - .../org/stellar/anchor/auth/AuthHelper.java | 1 - .../org/stellar/anchor/auth/AuthInfo.java | 10 + .../org/stellar/anchor/auth/AuthType.java | 7 + .../org/stellar/anchor/auth/JwtService.java | 13 +- .../org/stellar/anchor/config/AppConfig.java | 5 - .../stellar/anchor/config/AssetsConfig.java | 12 + .../anchor/config/EventTypeToQueueConfig.java | 7 + .../anchor/config/IntegrationAuthConfig.java | 19 - .../stellar/anchor/config/MetricConfig.java | 2 +- ...Config.java => PaymentObserverConfig.java} | 2 +- ...{KafkaConfig.java => PublisherConfig.java} | 10 +- .../stellar/anchor/config/SecretConfig.java | 11 + .../stellar/anchor/config/Sep10Config.java | 3 - .../org/stellar/anchor/config/Sep1Config.java | 6 +- .../stellar/anchor/config/Sep38Config.java | 2 - .../org/stellar/anchor/config/SqsConfig.java | 18 - .../anchor/event/KafkaEventService.java | 5 +- .../anchor/event/NoopEventService.java | 13 + .../stellar/anchor/event/SqsEventService.java | 4 +- .../healthcheck/HealthCheckProcessor.java | 4 +- .../anchor/healthcheck/HealthCheckable.java | 20 +- .../org/stellar/anchor/sep1/Sep1Service.java | 59 +-- .../stellar/anchor/sep10/Sep10Service.java | 14 +- .../java/org/stellar/anchor/util/NetUtil.java | 11 + .../stellar/anchor/util/ReflectionUtil.java | 1 + .../stellar/anchor/util/ResourceReader.java | 20 - .../org/stellar/anchor/util/StringHelper.java | 18 + .../org/stellar/anchor/auth/AuthHelperTest.kt | 8 +- .../org/stellar/anchor/auth/JwtServiceTest.kt | 22 +- .../anchor/filter/JwtTokenFilterTest.kt | 7 +- .../stellar/anchor/sep1/Sep1ServiceTest.kt | 9 - .../stellar/anchor/sep10/Sep10ServiceTest.kt | 39 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 6 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 6 +- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 8 +- .../kotlin/org/stellar/anchor/util/LogTest.kt | 9 - .../org/stellar/anchor/util/NetUtilTest.kt | 31 +- .../stellar/anchor/util/StringHelperTest.kt | 34 ++ helm-charts/sep-service/Chart.yaml | 2 +- helm-charts/sep-service/README.md | 229 ++------ helm-charts/sep-service/example_values.yaml | 181 +++---- helm-charts/sep-service/templates/_common.tpl | 24 +- .../sep-service/templates/configmap.yaml | 275 +--------- .../sep-service/templates/deployment.yaml | 39 +- .../sep-service/templates/ingress.yaml | 5 +- .../sep-service/templates/service.yaml | 11 +- .../platform/AnchorPlatformIntegrationTest.kt | 93 ++-- .../platform/ApiKeyAuthIntegrationTest.kt | 58 +-- .../stellar/anchor/platform/PlatformTests.kt | 3 +- .../anchor/platform/RestFeeIntegrationTest.kt | 6 +- .../platform/RestRateIntegrationTest.kt | 16 +- .../org/stellar/anchor/platform/Sep38Tests.kt | 15 +- .../stellar/anchor/platform/SepTestSuite.kt | 6 +- .../resources/anchor-config-integration.yaml | 309 +++++++++++ .../integration-test.anchor-config.yaml | 427 +++++++++------ .../src/test/resources/integration.env | 4 + .../anchor/platform/AnchorPlatformServer.java | 23 +- ...ationConfig.java => CallbackApiBeans.java} | 75 ++- .../platform/ConfigManagementBeans.java | 125 +++++ .../platform/ConfigManagementConfig.java | 100 ---- .../{EventsConfig.java => EventsBeans.java} | 12 +- .../{HelperConfig.java => HelperBeans.java} | 2 +- .../{MetricsConfig.java => MetricsBeans.java} | 2 +- .../{PaymentConfig.java => PaymentBeans.java} | 6 +- .../anchor/platform/PlatformApiBeans.java | 48 ++ ...gerConfig.java => RequestLoggerBeans.java} | 2 +- .../{SepConfig.java => SepServiceBeans.java} | 73 +-- .../platform/StellarObservingService.java | 15 +- .../platform/config/CallbackApiConfig.java | 55 ++ .../platform/config/PlatformApiConfig.java | 44 ++ .../platform/config/PropertyAppConfig.java | 25 +- .../platform/config/PropertyAssetsConfig.java | 20 + .../platform/config/PropertyEventConfig.java | 46 +- .../PropertyEventTypeToQueueConfig.java | 32 ++ .../config/PropertyIntegrationAuthConfig.java | 51 -- .../platform/config/PropertyKafkaConfig.java | 32 -- .../platform/config/PropertyMetricConfig.java | 23 +- ...ava => PropertyPaymentObserverConfig.java} | 8 +- .../config/PropertyPublisherConfig.java | 55 ++ .../platform/config/PropertySecretConfig.java | 20 + .../platform/config/PropertySep10Config.java | 27 +- .../platform/config/PropertySep12Config.java | 16 +- .../platform/config/PropertySep1Config.java | 91 +++- .../platform/config/PropertySep24Config.java | 20 +- .../platform/config/PropertySep31Config.java | 6 +- .../platform/config/PropertySep38Config.java | 13 +- .../platform/config/PropertySqsConfig.java | 29 -- .../configurator/AbstractConfigurator.java | 19 - .../configurator/ConfigEnvironment.java | 43 ++ .../platform/configurator/ConfigHelper.java | 98 ++++ .../platform/configurator/ConfigManager.java | 195 +++++++ .../platform/configurator/ConfigMap.java | 108 ++++ .../platform/configurator/ConfigReader.java | 85 +++ .../configurator/DataAccessConfigurator.java | 19 - .../configurator/DataConfigAdapter.java | 183 +++++++ .../configurator/LogConfigAdapter.java | 47 ++ .../configurator/PlatformAppConfigurator.java | 19 - .../configurator/PropertiesReader.java | 96 ---- .../platform/configurator/SecretManager.java | 46 ++ .../configurator/SpringConfigAdapter.java | 65 +++ .../SpringFrameworkConfigurator.java | 16 - .../circle/CirclePaymentObserverService.java | 4 +- .../stellar/StellarPaymentObserver.java | 8 +- .../platform/service/HealthCheckService.java | 10 + .../service/MetricEmitterService.java | 2 +- .../service/PropertyAssetsService.java | 56 ++ .../service/ResourceReaderAssetService.java | 14 - .../service/SpringResourceReader.java | 35 -- .../resources/anchor-config-defaults.yaml | 350 ------------- .../config/anchor-config-default-values.yaml | 489 ++++++++++++++++++ .../config/anchor-config-schema-v1.yaml | 59 +++ platform/src/main/resources/example.env | 24 +- .../org/stellar/anchor/Sep1ServiceTest.kt | 71 +++ ...ymentConfigTest.kt => PaymentBeansTest.kt} | 22 +- .../callback/PlatformIntegrationHelperTest.kt | 9 +- .../anchor/platform/config/AppConfigTest.kt | 33 +- .../anchor/platform/config/EventConfigTest.kt | 26 +- .../anchor/platform/config/Sep12ConfigTest.kt | 17 +- .../anchor/platform/config/Sep1ConfigTest.kt | 98 +++- .../anchor/platform/config/Sep31ConfigTest.kt | 10 +- .../anchor/platform/config/Sep38ConfigTest.kt | 13 - .../configurator/ConfigManagerTest.kt | 98 ++++ .../platform/configurator/ConfigMapTest.kt | 53 ++ .../platform/configurator/ConfiguratorTest.kt | 87 ---- .../CirclePaymentObserverServiceTest.kt | 4 +- .../service/SpringResourceReaderTest.kt | 33 -- .../platform/config/sep1-stellar-test.toml | 26 + .../configurator/def/config-def-v1.yaml | 6 + .../configurator/def/config-def-v2.yaml | 10 + .../configurator/def/config-def-v3.yaml | 9 + .../configurator/def/config-defaults-v3.yaml | 8 + .../platform/configurator/scene-1/test.yaml | 6 + .../platform/configurator/scene-1/wanted.yaml | 8 + .../configurator/scene-2/test.bad.yaml | 9 + .../platform/configurator/scene-3/test.yaml | 5 + .../platform/configurator/scene-3/wanted.yaml | 8 + .../anchor/platform/ServiceRunner.java | 21 +- 143 files changed, 3807 insertions(+), 2299 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidConfigException.java create mode 100644 core/src/main/java/org/stellar/anchor/auth/AuthInfo.java create mode 100644 core/src/main/java/org/stellar/anchor/auth/AuthType.java create mode 100644 core/src/main/java/org/stellar/anchor/config/AssetsConfig.java create mode 100644 core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java delete mode 100644 core/src/main/java/org/stellar/anchor/config/IntegrationAuthConfig.java rename core/src/main/java/org/stellar/anchor/config/{CirclePaymentObserverConfig.java => PaymentObserverConfig.java} (65%) rename core/src/main/java/org/stellar/anchor/config/{KafkaConfig.java => PublisherConfig.java} (60%) create mode 100644 core/src/main/java/org/stellar/anchor/config/SecretConfig.java delete mode 100644 core/src/main/java/org/stellar/anchor/config/SqsConfig.java create mode 100644 core/src/main/java/org/stellar/anchor/event/NoopEventService.java delete mode 100644 core/src/main/java/org/stellar/anchor/util/ResourceReader.java create mode 100644 core/src/test/kotlin/org/stellar/anchor/util/StringHelperTest.kt create mode 100644 integration-tests/src/test/resources/anchor-config-integration.yaml create mode 100644 integration-tests/src/test/resources/integration.env rename platform/src/main/java/org/stellar/anchor/platform/{IntegrationConfig.java => CallbackApiBeans.java} (55%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java rename platform/src/main/java/org/stellar/anchor/platform/{EventsConfig.java => EventsBeans.java} (67%) rename platform/src/main/java/org/stellar/anchor/platform/{HelperConfig.java => HelperBeans.java} (91%) rename platform/src/main/java/org/stellar/anchor/platform/{MetricsConfig.java => MetricsBeans.java} (95%) rename platform/src/main/java/org/stellar/anchor/platform/{PaymentConfig.java => PaymentBeans.java} (96%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java rename platform/src/main/java/org/stellar/anchor/platform/{RequestLoggerConfig.java => RequestLoggerBeans.java} (94%) rename platform/src/main/java/org/stellar/anchor/platform/{SepConfig.java => SepServiceBeans.java} (73%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PlatformApiConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyIntegrationAuthConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyKafkaConfig.java rename platform/src/main/java/org/stellar/anchor/platform/config/{PropertyCirclePaymentObserverConfig.java => PropertyPaymentObserverConfig.java} (63%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertySqsConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/AbstractConfigurator.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/DataAccessConfigurator.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/LogConfigAdapter.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/PlatformAppConfigurator.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/PropertiesReader.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/SpringConfigAdapter.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/SpringFrameworkConfigurator.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/ResourceReaderAssetService.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/SpringResourceReader.java delete mode 100644 platform/src/main/resources/anchor-config-defaults.yaml create mode 100644 platform/src/main/resources/config/anchor-config-default-values.yaml create mode 100644 platform/src/main/resources/config/anchor-config-schema-v1.yaml create mode 100644 platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt rename platform/src/test/kotlin/org/stellar/anchor/platform/{PaymentConfigTest.kt => PaymentBeansTest.kt} (86%) create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigMapTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfiguratorTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/SpringResourceReaderTest.kt create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/config/sep1-stellar-test.toml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v1.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v2.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v3.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-defaults-v3.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/test.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/wanted.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-2/test.bad.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/test.yaml create mode 100644 platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/wanted.yaml diff --git a/.gitignore b/.gitignore index a57a1905c6..10ca0ea02c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ target/ */out/** *.class .jpb -*/bin/* \ No newline at end of file +*/bin/* +*/generated/* +*.tgz + diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/IntegrationAuthSettings.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/IntegrationAuthSettings.java index b18fb35579..30a5158060 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/IntegrationAuthSettings.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/config/IntegrationAuthSettings.java @@ -3,7 +3,7 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; -import org.stellar.anchor.config.IntegrationAuthConfig.AuthType; +import org.stellar.anchor.auth.AuthType; @Data @Configuration diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/KafkaListener.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/KafkaListener.java index a35426ff1e..46ec30d9f8 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/KafkaListener.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/KafkaListener.java @@ -2,6 +2,8 @@ import static org.stellar.anchor.api.platform.HealthCheckStatus.GREEN; import static org.stellar.anchor.api.platform.HealthCheckStatus.RED; +import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.ALL; +import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.KAFKA; import com.google.gson.annotations.SerializedName; import java.time.Duration; @@ -43,8 +45,8 @@ Consumer createKafkaConsumer() { props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaListenerSettings.getBootStrapServer()); props.put(ConsumerConfig.GROUP_ID_CONFIG, "group_one1"); - // props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); // start reading from - // earliest available message + // props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + // start reading from the earliest available message props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); props.put(JsonDeserializer.TRUSTED_PACKAGES, "*"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); @@ -114,7 +116,7 @@ public void destroy() { @Override public int compareTo(@NotNull HealthCheckable other) { - return other.getName().compareTo(other.getName()); + return this.getName().compareTo(other.getName()); } @Override @@ -123,8 +125,8 @@ public String getName() { } @Override - public List getTags() { - return List.of("all", "kafka"); + public List getTags() { + return List.of(ALL, KAFKA); } @Override @@ -162,7 +164,7 @@ boolean validateKafka() { class KafkaHealthCheckResult implements HealthCheckResult { transient String name; - List statuses = List.of(GREEN.getName(), RED.getName()); + List statuses; String status; diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/HealthCheckService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/HealthCheckService.java index 65304354d9..c628e956d2 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/HealthCheckService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/HealthCheckService.java @@ -1,5 +1,7 @@ package org.stellar.anchor.reference.service; +import static org.stellar.anchor.util.Log.*; + import java.util.List; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; @@ -13,6 +15,9 @@ public class HealthCheckService { HealthCheckProcessor processor; public HealthCheckService(List checkables) { + checkables.forEach( + checkable -> + debug(String.format("[%s] is added to health check list.", checkable.getName()))); processor = new HealthCheckProcessor(checkables); } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidConfigException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidConfigException.java new file mode 100644 index 0000000000..f9a315861d --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidConfigException.java @@ -0,0 +1,24 @@ +package org.stellar.anchor.api.exception; + +import java.util.Arrays; +import java.util.List; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +@Getter +public class InvalidConfigException extends AnchorException { + List messages; + + public InvalidConfigException(String... messages) { + this(Arrays.asList(messages), null); + } + + public InvalidConfigException(List messages) { + this(messages, null); + } + + public InvalidConfigException(List messages, Exception cause) { + super(StringUtils.join(messages, System.lineSeparator()), cause); + this.messages = messages; + } +} diff --git a/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java b/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java index 258b9b1a50..f32432e7fb 100644 --- a/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java +++ b/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java @@ -15,7 +15,6 @@ public JsonAssetService(String assetJson) { } public List listAllAssets() { - // we should make a copy to prevent mutation. return new ArrayList<>(assets.assets); } diff --git a/core/src/main/java/org/stellar/anchor/auth/AuthHelper.java b/core/src/main/java/org/stellar/anchor/auth/AuthHelper.java index d97df7d20b..2aca9abbe3 100644 --- a/core/src/main/java/org/stellar/anchor/auth/AuthHelper.java +++ b/core/src/main/java/org/stellar/anchor/auth/AuthHelper.java @@ -2,7 +2,6 @@ import java.util.Calendar; import javax.annotation.Nullable; -import org.stellar.anchor.config.IntegrationAuthConfig.AuthType; import org.stellar.anchor.util.AuthHeader; public class AuthHelper { diff --git a/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java b/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java new file mode 100644 index 0000000000..25e9ca63de --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java @@ -0,0 +1,10 @@ +package org.stellar.anchor.auth; + +import lombok.Data; + +@Data +public class AuthInfo { + AuthType type; + String secret; + String expirationMilliseconds; +} diff --git a/core/src/main/java/org/stellar/anchor/auth/AuthType.java b/core/src/main/java/org/stellar/anchor/auth/AuthType.java new file mode 100644 index 0000000000..9cd51ce6b6 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/auth/AuthType.java @@ -0,0 +1,7 @@ +package org.stellar.anchor.auth; + +public enum AuthType { + NONE, + API_KEY, + JWT_TOKEN +} diff --git a/core/src/main/java/org/stellar/anchor/auth/JwtService.java b/core/src/main/java/org/stellar/anchor/auth/JwtService.java index 0b57fdf696..b3981af724 100644 --- a/core/src/main/java/org/stellar/anchor/auth/JwtService.java +++ b/core/src/main/java/org/stellar/anchor/auth/JwtService.java @@ -4,18 +4,23 @@ import io.jsonwebtoken.impl.DefaultJwsHeader; import java.nio.charset.StandardCharsets; import java.util.Calendar; +import lombok.Getter; import org.apache.commons.codec.binary.Base64; -import org.stellar.anchor.config.AppConfig; +import org.stellar.anchor.config.SecretConfig; +@Getter public class JwtService { final String jwtKey; - public JwtService(AppConfig appConfig) { - this(appConfig.getJwtSecretKey()); + public JwtService(SecretConfig secretConfig) { + this(secretConfig.getSep10JwtSecretKey()); } public JwtService(String secretKey) { - this.jwtKey = Base64.encodeBase64String(secretKey.getBytes(StandardCharsets.UTF_8)); + this.jwtKey = + (secretKey == null) + ? null + : Base64.encodeBase64String(secretKey.getBytes(StandardCharsets.UTF_8)); } public String encode(JwtToken token) { diff --git a/core/src/main/java/org/stellar/anchor/config/AppConfig.java b/core/src/main/java/org/stellar/anchor/config/AppConfig.java index b370faa888..c4857fc7e3 100644 --- a/core/src/main/java/org/stellar/anchor/config/AppConfig.java +++ b/core/src/main/java/org/stellar/anchor/config/AppConfig.java @@ -9,10 +9,5 @@ public interface AppConfig { String getHorizonUrl(); - @Secret - String getJwtSecretKey(); - - String getAssets(); - List getLanguages(); } diff --git a/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java b/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java new file mode 100644 index 0000000000..ee702fc691 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java @@ -0,0 +1,12 @@ +package org.stellar.anchor.config; + +public interface AssetsConfig { + AssetConfigType getType(); + + String getValue(); + + enum AssetConfigType { + JSON, + YAML + } +} diff --git a/core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java b/core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java new file mode 100644 index 0000000000..38363f88cd --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java @@ -0,0 +1,7 @@ +package org.stellar.anchor.config; + +import java.util.Map; + +public interface EventTypeToQueueConfig { + Map getEventTypeToQueueMap(); +} diff --git a/core/src/main/java/org/stellar/anchor/config/IntegrationAuthConfig.java b/core/src/main/java/org/stellar/anchor/config/IntegrationAuthConfig.java deleted file mode 100644 index 936c92ab62..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/IntegrationAuthConfig.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.stellar.anchor.config; - -public interface IntegrationAuthConfig { - AuthType getAuthType(); - - @Secret - String getPlatformToAnchorSecret(); - - @Secret - String getAnchorToPlatformSecret(); - - Long getExpirationMilliseconds(); - - enum AuthType { - NONE, - API_KEY, - JWT_TOKEN - } -} diff --git a/core/src/main/java/org/stellar/anchor/config/MetricConfig.java b/core/src/main/java/org/stellar/anchor/config/MetricConfig.java index 6e9341c3c2..e6cb312463 100644 --- a/core/src/main/java/org/stellar/anchor/config/MetricConfig.java +++ b/core/src/main/java/org/stellar/anchor/config/MetricConfig.java @@ -1,7 +1,7 @@ package org.stellar.anchor.config; public interface MetricConfig { - boolean isOptionalMetricsEnabled(); + boolean isExtrasEnabled(); Integer getRunInterval(); } diff --git a/core/src/main/java/org/stellar/anchor/config/CirclePaymentObserverConfig.java b/core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java similarity index 65% rename from core/src/main/java/org/stellar/anchor/config/CirclePaymentObserverConfig.java rename to core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java index 2e88d4a2d4..60bf11a43f 100644 --- a/core/src/main/java/org/stellar/anchor/config/CirclePaymentObserverConfig.java +++ b/core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java @@ -1,6 +1,6 @@ package org.stellar.anchor.config; -public interface CirclePaymentObserverConfig { +public interface PaymentObserverConfig { boolean isEnabled(); String getTrackedWallet(); diff --git a/core/src/main/java/org/stellar/anchor/config/KafkaConfig.java b/core/src/main/java/org/stellar/anchor/config/PublisherConfig.java similarity index 60% rename from core/src/main/java/org/stellar/anchor/config/KafkaConfig.java rename to core/src/main/java/org/stellar/anchor/config/PublisherConfig.java index a9e1ecefe7..98724ddbc0 100644 --- a/core/src/main/java/org/stellar/anchor/config/KafkaConfig.java +++ b/core/src/main/java/org/stellar/anchor/config/PublisherConfig.java @@ -3,7 +3,7 @@ import java.util.Map; import org.springframework.validation.BindException; -public interface KafkaConfig { +public interface PublisherConfig { String getBootstrapServer(); boolean isUseSingleQueue(); @@ -12,5 +12,11 @@ public interface KafkaConfig { Map getEventTypeToQueue(); - BindException validate(); + BindException validate(String publisherType); + + String getRegion(); + + String getAccessKey(); + + String getSecretKey(); } diff --git a/core/src/main/java/org/stellar/anchor/config/SecretConfig.java b/core/src/main/java/org/stellar/anchor/config/SecretConfig.java new file mode 100644 index 0000000000..b6e143ac3a --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/config/SecretConfig.java @@ -0,0 +1,11 @@ +package org.stellar.anchor.config; + +public interface SecretConfig { + String getSep10JwtSecretKey(); + + String getSep10SigningSeed(); + + String getCallbackApiSecret(); + + String getPlatformApiSecret(); +} diff --git a/core/src/main/java/org/stellar/anchor/config/Sep10Config.java b/core/src/main/java/org/stellar/anchor/config/Sep10Config.java index a3fc841236..f8646e7dca 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep10Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep10Config.java @@ -9,9 +9,6 @@ public interface Sep10Config { Boolean getEnabled(); - @Secret - String getSigningSeed(); - Integer getAuthTimeout(); Integer getJwtTimeout(); diff --git a/core/src/main/java/org/stellar/anchor/config/Sep1Config.java b/core/src/main/java/org/stellar/anchor/config/Sep1Config.java index 7ab0994614..b199f3f78f 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep1Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep1Config.java @@ -2,7 +2,9 @@ @SuppressWarnings("unused") public interface Sep1Config { - String getStellarFile(); - boolean isEnabled(); + + String getType(); + + String getValue(); } diff --git a/core/src/main/java/org/stellar/anchor/config/Sep38Config.java b/core/src/main/java/org/stellar/anchor/config/Sep38Config.java index f57bfe29a7..f69c1016b1 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep38Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep38Config.java @@ -2,6 +2,4 @@ public interface Sep38Config { boolean isEnabled(); - - String getQuoteIntegrationEndPoint(); } diff --git a/core/src/main/java/org/stellar/anchor/config/SqsConfig.java b/core/src/main/java/org/stellar/anchor/config/SqsConfig.java deleted file mode 100644 index a23a19a011..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/SqsConfig.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.stellar.anchor.config; - -import java.util.Map; -import org.springframework.validation.BindException; - -public interface SqsConfig { - String getRegion(); - - Boolean isUseSingleQueue(); - - Map getEventTypeToQueue(); - - String getAccessKey(); - - String getSecretKey(); - - BindException validate(); -} diff --git a/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java b/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java index afa32451ce..7e06ec4318 100644 --- a/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java +++ b/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java @@ -10,7 +10,7 @@ import org.apache.kafka.common.header.internals.RecordHeader; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.kafka.support.serializer.JsonSerializer; -import org.stellar.anchor.config.KafkaConfig; +import org.stellar.anchor.config.PublisherConfig; import org.stellar.anchor.event.models.AnchorEvent; import org.stellar.anchor.util.Log; @@ -19,7 +19,7 @@ public class KafkaEventService implements EventPublishService { final Map eventTypeToQueue; final boolean useSingleQueue; - public KafkaEventService(KafkaConfig kafkaConfig) { + public KafkaEventService(PublisherConfig kafkaConfig) { Log.debugF("kafkaConfig: {}", kafkaConfig); Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServer()); @@ -36,6 +36,7 @@ public KafkaEventService(KafkaConfig kafkaConfig) { this.producer = new KafkaProducer<>(props); this.eventTypeToQueue = kafkaConfig.getEventTypeToQueue(); + this.useSingleQueue = kafkaConfig.isUseSingleQueue(); } diff --git a/core/src/main/java/org/stellar/anchor/event/NoopEventService.java b/core/src/main/java/org/stellar/anchor/event/NoopEventService.java new file mode 100644 index 0000000000..5f32e8f453 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/event/NoopEventService.java @@ -0,0 +1,13 @@ +package org.stellar.anchor.event; + +import static org.stellar.anchor.util.Log.debugF; + +import org.stellar.anchor.event.models.AnchorEvent; + +public class NoopEventService implements EventPublishService { + @Override + public void publish(AnchorEvent event) { + // noop + debugF("Event ID={} is published to NOOP class.", event.getEventId()); + } +} diff --git a/core/src/main/java/org/stellar/anchor/event/SqsEventService.java b/core/src/main/java/org/stellar/anchor/event/SqsEventService.java index 098848e611..228ea55c75 100644 --- a/core/src/main/java/org/stellar/anchor/event/SqsEventService.java +++ b/core/src/main/java/org/stellar/anchor/event/SqsEventService.java @@ -9,7 +9,7 @@ import com.google.gson.Gson; import io.micrometer.core.instrument.Metrics; import java.util.Map; -import org.stellar.anchor.config.SqsConfig; +import org.stellar.anchor.config.PublisherConfig; import org.stellar.anchor.event.models.AnchorEvent; import org.stellar.anchor.util.Log; @@ -18,7 +18,7 @@ public class SqsEventService implements EventPublishService { final Map eventTypeToQueue; final boolean useSingleQueue; - public SqsEventService(SqsConfig sqsConfig) { + public SqsEventService(PublisherConfig sqsConfig) { this.sqsClient = AmazonSQSAsyncClientBuilder.standard() .withRegion(sqsConfig.getRegion()) diff --git a/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckProcessor.java b/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckProcessor.java index d568c16606..a5a9c20778 100644 --- a/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckProcessor.java +++ b/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckProcessor.java @@ -9,9 +9,9 @@ public class HealthCheckProcessor { public HealthCheckProcessor(List checkables) { for (HealthCheckable checkable : checkables) { - for (String tag : checkable.getTags()) { + for (HealthCheckable.Tags tag : checkable.getTags()) { List checksOfTag = - mapCheckable.computeIfAbsent(tag, k -> new ArrayList<>()); + mapCheckable.computeIfAbsent(tag.toString(), k -> new ArrayList<>()); checksOfTag.add(checkable); } } diff --git a/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckable.java b/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckable.java index 279941085c..4746ac82a8 100644 --- a/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckable.java +++ b/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckable.java @@ -22,7 +22,7 @@ public interface HealthCheckable extends Comparable { * * @return the tags */ - List getTags(); + List getTags(); /** * Perform the check. @@ -30,4 +30,22 @@ public interface HealthCheckable extends Comparable { * @return The result specific to the service. */ HealthCheckResult check(); + + enum Tags { + ALL("all"), + KAFKA("kafka"), + EVENT("evnet"), + CONFIG("config"); + + private String name; + + Tags(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } } diff --git a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java index b7b1cdf6f3..6818351f41 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -1,51 +1,46 @@ package org.stellar.anchor.sep1; -import static org.stellar.anchor.util.Log.*; +import static org.stellar.anchor.util.Log.debug; +import static org.stellar.anchor.util.Log.debugF; import java.io.IOException; -import org.stellar.anchor.api.exception.SepNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; import org.stellar.anchor.config.Sep1Config; -import org.stellar.anchor.util.FileUtil; import org.stellar.anchor.util.Log; -import org.stellar.anchor.util.ResourceReader; +import org.stellar.anchor.util.NetUtil; public class Sep1Service { - private final Sep1Config sep1Config; - private ResourceReader resourceReader; + private String tomlValue; /** * Construct the Sep1Service that reads the stellar.toml content from Java resource. * * @param sep1Config The Sep1 configuration. */ - public Sep1Service(Sep1Config sep1Config) { - debug("sep1Config:", sep1Config); - this.sep1Config = sep1Config; - Log.info("Sep1Service initialized."); - } - - /** - * Construct the Sep1Service that reads the stellar.toml content by the resourceReader. - * - * @param sep1Config The Sep1 configuration. - * @param resourceReader The resource reader that reads the sep1 contents. - */ - public Sep1Service(Sep1Config sep1Config, ResourceReader resourceReader) { - this.sep1Config = sep1Config; - this.resourceReader = resourceReader; - } + public Sep1Service(Sep1Config sep1Config) throws IOException { + if (sep1Config.isEnabled()) { + debug("sep1Config:", sep1Config); + switch (sep1Config.getType().toLowerCase()) { + case "string": + debug("reading stellar.toml from config[sep1.toml.value]"); + tomlValue = sep1Config.getValue(); + break; + case "file": + debugF("reading stellar.toml from {}", sep1Config.getValue()); + tomlValue = Files.readString(Path.of(sep1Config.getValue())); + break; + case "url": + debugF("reading stellar.toml from {}", sep1Config.getValue()); + tomlValue = NetUtil.fetch(sep1Config.getValue()); + break; + } - public String getStellarToml() throws IOException, SepNotFoundException { - infoF("reading SEP1 TOML: {}", sep1Config.getStellarFile()); - if (resourceReader == null) { - debugF("reading SEP1 TOML from file system. path={}", sep1Config.getStellarFile()); - return FileUtil.getResourceFileAsString(sep1Config.getStellarFile()); + Log.info("Sep1Service initialized."); } + } - debugF( - "reading SEP1 TOML from resource reader({}). path={}", - resourceReader.getClass().getSimpleName(), - sep1Config.getStellarFile()); - return resourceReader.readResourceAsString(sep1Config.getStellarFile()); + public String getStellarToml() { + return tomlValue; } } diff --git a/core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java b/core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java index b7fa8416aa..02d8c16c6b 100644 --- a/core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java +++ b/core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java @@ -17,6 +17,7 @@ import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.AppConfig; +import org.stellar.anchor.config.SecretConfig; import org.stellar.anchor.config.Sep10Config; import org.stellar.anchor.horizon.Horizon; import org.stellar.anchor.util.Log; @@ -29,20 +30,27 @@ /** The Sep-10 protocol service. */ public class Sep10Service { final AppConfig appConfig; + final SecretConfig secretConfig; final Sep10Config sep10Config; final Horizon horizon; final JwtService jwtService; final String serverAccountId; public Sep10Service( - AppConfig appConfig, Sep10Config sep10Config, Horizon horizon, JwtService jwtService) { + AppConfig appConfig, + SecretConfig secretConfig, + Sep10Config sep10Config, + Horizon horizon, + JwtService jwtService) { debug("appConfig:", appConfig); debug("sep10Config:", sep10Config); this.appConfig = appConfig; + this.secretConfig = secretConfig; this.sep10Config = sep10Config; this.horizon = horizon; this.jwtService = jwtService; - this.serverAccountId = KeyPair.fromSecretSeed(sep10Config.getSigningSeed()).getAccountId(); + this.serverAccountId = + KeyPair.fromSecretSeed(secretConfig.getSep10SigningSeed()).getAccountId(); Log.info("Sep10Service initialized."); } @@ -143,7 +151,7 @@ public ChallengeResponse createChallenge(ChallengeRequest challengeRequest) thro debugF("SIGNING_KEY from client_domain fetched: {}", clientSigningKey); } - KeyPair signer = KeyPair.fromSecretSeed(sep10Config.getSigningSeed()); + KeyPair signer = KeyPair.fromSecretSeed(secretConfig.getSep10SigningSeed()); long now = System.currentTimeMillis() / 1000L; Transaction txn = Sep10Challenge.newChallenge( diff --git a/core/src/main/java/org/stellar/anchor/util/NetUtil.java b/core/src/main/java/org/stellar/anchor/util/NetUtil.java index 7faee80f14..462a60812f 100644 --- a/core/src/main/java/org/stellar/anchor/util/NetUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/NetUtil.java @@ -1,6 +1,7 @@ package org.stellar.anchor.util; import java.io.IOException; +import java.net.URL; import java.util.Objects; import okhttp3.Call; import okhttp3.Request; @@ -15,6 +16,16 @@ public static String fetch(String url) throws IOException { return Objects.requireNonNull(response.body()).string(); } + public static boolean isUrlValid(String url) { + /* Try creating a valid URL */ + try { + new URL(url).toURI(); + return true; + } catch (Exception e) { + return false; + } + } + static Call getCall(Request request) { return OkHttpUtil.buildClient().newCall(request); } diff --git a/core/src/main/java/org/stellar/anchor/util/ReflectionUtil.java b/core/src/main/java/org/stellar/anchor/util/ReflectionUtil.java index f677f6ecd8..55cbf3766d 100644 --- a/core/src/main/java/org/stellar/anchor/util/ReflectionUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/ReflectionUtil.java @@ -4,6 +4,7 @@ import org.stellar.sdk.requests.SSEStream; public class ReflectionUtil { + @SuppressWarnings("unchecked") public static T getField(Object target, String fieldName, T defaultValue) { try { // populate executorService information diff --git a/core/src/main/java/org/stellar/anchor/util/ResourceReader.java b/core/src/main/java/org/stellar/anchor/util/ResourceReader.java deleted file mode 100644 index 7d4b36b882..0000000000 --- a/core/src/main/java/org/stellar/anchor/util/ResourceReader.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.stellar.anchor.util; - -/** The interface to reads resource external to the application. */ -public interface ResourceReader { - /** - * Reads the specified resource as a string. - * - * @param path The location of the resource. - * @return The content of the resource. - */ - String readResourceAsString(String path); - - /** - * Checks whether the resource exists. - * - * @param path The location of the resource. - * @return True if the resource is found, False otherwise. - */ - boolean checkResourceExists(String path); -} diff --git a/core/src/main/java/org/stellar/anchor/util/StringHelper.java b/core/src/main/java/org/stellar/anchor/util/StringHelper.java index 28fe68e0a4..6a2f71d4e8 100644 --- a/core/src/main/java/org/stellar/anchor/util/StringHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/StringHelper.java @@ -6,4 +6,22 @@ public class StringHelper { public static boolean isEmpty(String value) { return Objects.toString(value, "").isEmpty(); } + + public static boolean isNotEmpty(String value) { + return !isEmpty(value); + } + + /** + * Convert camelCase string to an under-scored snake-case string. + * + * @param camel the camel case string. + * @return under-scored snake-case string + */ + public static String camelToSnake(String camel) { + return camel + .replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2") + .replaceAll("([a-z])([A-Z])", "$1_$2") + .replaceAll("-", "_") + .toLowerCase(); + } } diff --git a/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt index 9f5534d6e2..c1382078f4 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/AuthHelperTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertNull import org.junit.jupiter.api.AfterEach import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource -import org.stellar.anchor.config.IntegrationAuthConfig.AuthType +import org.stellar.anchor.auth.AuthType.* import org.stellar.anchor.util.AuthHeader class AuthHelperTest { @@ -25,7 +25,7 @@ class AuthHelperTest { @EnumSource(AuthType::class) fun `test AuthHeader creation based on the AuthType`(authType: AuthType) { when (authType) { - AuthType.JWT_TOKEN -> { + JWT_TOKEN -> { // Mock calendar to guarantee the jwt token format val calendarSingleton = Calendar.getInstance() val currentTimeMilliseconds = calendarSingleton.timeInMillis @@ -51,13 +51,13 @@ class AuthHelperTest { AuthHeader("Authorization", "Bearer ${jwtService.encode(wantJwtToken)}") assertEquals(wantAuthHeader, gotAuthHeader) } - AuthType.API_KEY -> { + API_KEY -> { val authHelper = AuthHelper.forApiKey("secret") val gotAuthHeader = authHelper.createAuthHeader() val wantAuthHeader = AuthHeader("X-Api-Key", "secret") assertEquals(wantAuthHeader, gotAuthHeader) } - AuthType.NONE -> { + NONE -> { val authHelper = AuthHelper.forNone() val authHeader = authHelper.createAuthHeader() assertNull(authHeader) diff --git a/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt index 40b825cbe4..461d43c8e8 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt @@ -11,7 +11,7 @@ import org.apache.commons.codec.binary.Base64 import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.stellar.anchor.config.AppConfig +import org.stellar.anchor.config.SecretConfig internal class JwtServiceTest { companion object { @@ -25,10 +25,10 @@ internal class JwtServiceTest { @Test fun `test apply JWT encoding and decoding and make sure the original values are not changed`() { - val appConfig = mockk() - every { appConfig.jwtSecretKey } returns "jwt_secret" + val secretConfig = mockk() + every { secretConfig.sep10JwtSecretKey } returns "jwt_secret" - val jwtService = JwtService(appConfig) + val jwtService = JwtService(secretConfig) val token = JwtToken.of( TEST_ISS, @@ -56,22 +56,22 @@ internal class JwtServiceTest { @Test fun `make sure decoding bad cipher test throws an error`() { - val appConfig = mockk() - every { appConfig.jwtSecretKey } returns "jwt_secret" + val secretConfig = mockk() + every { secretConfig.sep10JwtSecretKey } returns "jwt_secret" - val jwtService = JwtService(appConfig) + val jwtService = JwtService(secretConfig) assertThrows { jwtService.decode("This is a bad cipher") } } @Test fun `make sure JwtService only decodes HS256`() { - val appConfig = mockk() - every { appConfig.jwtSecretKey } returns "jwt_secret" + val secretConfig = mockk() + every { secretConfig.sep10JwtSecretKey } returns "jwt_secret" - val jwtService = JwtService(appConfig) + val jwtService = JwtService(secretConfig) val jwtKey = - Base64.encodeBase64String(appConfig.jwtSecretKey.toByteArray(StandardCharsets.UTF_8)) + Base64.encodeBase64String(secretConfig.sep10JwtSecretKey.toByteArray(StandardCharsets.UTF_8)) val builder = Jwts.builder() diff --git a/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt b/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt index e3232240fb..0986d23e84 100644 --- a/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/filter/JwtTokenFilterTest.kt @@ -16,6 +16,7 @@ import org.stellar.anchor.TestHelper.Companion.createJwtToken import org.stellar.anchor.auth.JwtService import org.stellar.anchor.auth.JwtToken import org.stellar.anchor.config.AppConfig +import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.filter.JwtTokenFilter.APPLICATION_JSON_VALUE import org.stellar.anchor.filter.JwtTokenFilter.JWT_TOKEN @@ -26,6 +27,7 @@ internal class JwtTokenFilterTest { } private lateinit var appConfig: AppConfig + private lateinit var secretConfig: SecretConfig private lateinit var jwtService: JwtService private lateinit var sep10TokenFilter: JwtTokenFilter private lateinit var request: HttpServletRequest @@ -35,8 +37,9 @@ internal class JwtTokenFilterTest { @BeforeEach fun setup() { this.appConfig = mockk(relaxed = true) - every { appConfig.jwtSecretKey } returns "secret" - this.jwtService = JwtService(appConfig) + this.secretConfig = mockk(relaxed = true) + every { secretConfig.sep10JwtSecretKey } returns "secret" + this.jwtService = JwtService(secretConfig) this.sep10TokenFilter = JwtTokenFilter(jwtService) this.request = mockk(relaxed = true) this.response = mockk(relaxed = true) diff --git a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt index c9a3555e15..2fb8418cc4 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -1,9 +1,6 @@ package org.stellar.anchor.sep1 -import io.mockk.every import io.mockk.mockk -import io.mockk.verify -import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.stellar.anchor.config.Sep1Config @@ -13,11 +10,5 @@ internal class Sep1ServiceTest { @Test fun `simple test if the toml file is read`() { val sep1Config = mockk() - every { sep1Config.stellarFile } returns "test_stellar.toml" - - val sep1Service = Sep1Service(sep1Config) - - assertNotNull(sep1Service.stellarToml) - verify(exactly = 3) { sep1Config.stellarFile } } } diff --git a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt index 18c69462b1..bc04dfe7a9 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -35,6 +35,7 @@ import org.stellar.anchor.api.sep.sep10.ChallengeRequest import org.stellar.anchor.api.sep.sep10.ValidationRequest import org.stellar.anchor.auth.JwtService import org.stellar.anchor.config.AppConfig +import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep10Config import org.stellar.anchor.horizon.Horizon import org.stellar.anchor.util.FileUtil @@ -73,6 +74,7 @@ internal class Sep10ServiceTest { } @MockK(relaxed = true) private lateinit var appConfig: AppConfig + @MockK(relaxed = true) private lateinit var secretConfig: SecretConfig @MockK(relaxed = true) private lateinit var sep10Config: Sep10Config @MockK(relaxed = true) private lateinit var horizon: Horizon @@ -90,7 +92,6 @@ internal class Sep10ServiceTest { @BeforeEach fun setUp() { MockKAnnotations.init(this, relaxUnitFun = true) - every { sep10Config.signingSeed } returns TEST_SIGNING_SEED every { sep10Config.homeDomain } returns TEST_HOME_DOMAIN every { sep10Config.clientAttributionDenyList } returns listOf("") every { sep10Config.clientAttributionAllowList } returns listOf(TEST_CLIENT_DOMAIN) @@ -99,15 +100,17 @@ internal class Sep10ServiceTest { every { appConfig.stellarNetworkPassphrase } returns TEST_NETWORK_PASS_PHRASE every { appConfig.hostUrl } returns TEST_HOST_URL - every { appConfig.jwtSecretKey } returns TEST_JWT_SECRET + + every { secretConfig.sep10SigningSeed } returns TEST_SIGNING_SEED + every { secretConfig.sep10JwtSecretKey } returns TEST_JWT_SECRET mockkStatic(NetUtil::class) mockkStatic(Sep10Challenge::class) every { NetUtil.fetch(any()) } returns TEST_CLIENT_TOML - this.jwtService = spyk(JwtService(appConfig)) - this.sep10Service = Sep10Service(appConfig, sep10Config, horizon, jwtService) + this.jwtService = spyk(JwtService(secretConfig)) + this.sep10Service = Sep10Service(appConfig, secretConfig, sep10Config, horizon, jwtService) } @AfterEach @@ -155,7 +158,9 @@ internal class Sep10ServiceTest { val transaction = TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, Network.TESTNET) - .addTimeBounds(TimeBounds.expiresAfter(900)) + .addPreconditions( + TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() + ) .setBaseFee(100) .addOperation(op1DomainNameMandatory) .addOperation(op2WebAuthDomainMandatory) @@ -168,11 +173,11 @@ internal class Sep10ServiceTest { transaction.sign(clientSecondaryKP) // 2 ------ Create Services - every { sep10Config.signingSeed } returns String(serverKP.secretSeed) + every { secretConfig.sep10SigningSeed } returns String(serverKP.secretSeed) every { appConfig.horizonUrl } returns "https://horizon-testnet.stellar.org" every { appConfig.stellarNetworkPassphrase } returns TEST_NETWORK_PASS_PHRASE val horizon = Horizon(appConfig) - this.sep10Service = Sep10Service(appConfig, sep10Config, horizon, jwtService) + this.sep10Service = Sep10Service(appConfig, secretConfig, sep10Config, horizon, jwtService) // 3 ------ Setup multisig val httpRequest = @@ -187,7 +192,9 @@ internal class Sep10ServiceTest { val clientAccount = horizon.server.accounts().account(clientMasterKP.accountId) val multisigTx = TransactionBuilder(AccountConverter.enableMuxed(), clientAccount, Network.TESTNET) - .addTimeBounds(TimeBounds.expiresAfter(900)) + .addPreconditions( + TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() + ) .setBaseFee(300) .addOperation( SetOptionsOperation.Builder() @@ -244,7 +251,9 @@ internal class Sep10ServiceTest { val transaction = TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, Network.TESTNET) - .addTimeBounds(TimeBounds.expiresAfter(900)) + .addPreconditions( + TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() + ) .setBaseFee(100) .addOperation(op1DomainNameMandatory) .addOperation(op2WebAuthDomainMandatory) @@ -256,11 +265,11 @@ internal class Sep10ServiceTest { transaction.sign(clientKP) // 2 ------ Create Services - every { sep10Config.signingSeed } returns String(serverKP.secretSeed) + every { secretConfig.sep10SigningSeed } returns String(serverKP.secretSeed) every { appConfig.horizonUrl } returns "https://horizon-testnet.stellar.org" every { appConfig.stellarNetworkPassphrase } returns TEST_NETWORK_PASS_PHRASE val horizon = Horizon(appConfig) - this.sep10Service = Sep10Service(appConfig, sep10Config, horizon, jwtService) + this.sep10Service = Sep10Service(appConfig, secretConfig, sep10Config, horizon, jwtService) // 3 ------ Run tests val validationRequest = ValidationRequest.of(transaction.toEnvelopeXdrBase64()) @@ -317,7 +326,9 @@ internal class Sep10ServiceTest { val transaction = TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, Network.TESTNET) - .addTimeBounds(TimeBounds.expiresAfter(900)) + .addPreconditions( + TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() + ) .setBaseFee(100) .addOperation(op1DomainNameMandatory) .addOperation(op2WebAuthDomainMandatory) @@ -329,11 +340,11 @@ internal class Sep10ServiceTest { transaction.sign(clientKP) // 2 ------ Create Services - every { sep10Config.signingSeed } returns String(serverKP.secretSeed) + every { secretConfig.sep10SigningSeed } returns String(serverKP.secretSeed) every { appConfig.horizonUrl } returns mockHorizonUrl every { appConfig.stellarNetworkPassphrase } returns TEST_NETWORK_PASS_PHRASE val horizon = Horizon(appConfig) - this.sep10Service = Sep10Service(appConfig, sep10Config, horizon, jwtService) + this.sep10Service = Sep10Service(appConfig, secretConfig, sep10Config, horizon, jwtService) // 3 ------ Run tests val validationRequest = ValidationRequest.of(transaction.toEnvelopeXdrBase64()) 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 ff57c11251..79828fe47d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -32,6 +32,7 @@ import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.auth.JwtService import org.stellar.anchor.auth.JwtToken import org.stellar.anchor.config.AppConfig +import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep24Config import org.stellar.anchor.util.DateUtil import org.stellar.anchor.util.GsonUtils @@ -46,6 +47,7 @@ internal class Sep24ServiceTest { } @MockK(relaxed = true) lateinit var appConfig: AppConfig + @MockK(relaxed = true) lateinit var secretConfig: SecretConfig @MockK(relaxed = true) lateinit var sep24Config: Sep24Config @@ -64,14 +66,14 @@ internal class Sep24ServiceTest { MockKAnnotations.init(this, relaxUnitFun = true) every { appConfig.stellarNetworkPassphrase } returns Constants.TEST_NETWORK_PASS_PHRASE every { appConfig.hostUrl } returns Constants.TEST_HOST_URL - every { appConfig.jwtSecretKey } returns Constants.TEST_JWT_SECRET + every { secretConfig.sep10JwtSecretKey } returns Constants.TEST_JWT_SECRET every { sep24Config.interactiveUrl } returns TEST_SEP24_INTERACTIVE_URL every { sep24Config.interactiveJwtExpiration } returns 1000 every { txnStore.newInstance() } returns PojoSep24Transaction() - jwtService = spyk(JwtService(appConfig)) + jwtService = spyk(JwtService(secretConfig)) sep24Service = Sep24Service(gson, appConfig, sep24Config, assetService, jwtService, txnStore) } 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 025b71e269..cbe89620ce 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -34,6 +34,7 @@ import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.auth.JwtService import org.stellar.anchor.config.AppConfig +import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep31Config import org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_RECEIVE import org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND @@ -271,6 +272,7 @@ class Sep31ServiceTest { @MockK(relaxed = true) private lateinit var txnStore: Sep31TransactionStore @MockK(relaxed = true) lateinit var appConfig: AppConfig + @MockK(relaxed = true) lateinit var secretConfig: SecretConfig @MockK(relaxed = true) lateinit var sep31Config: Sep31Config @MockK(relaxed = true) lateinit var sep31DepositInfoGenerator: Sep31DepositInfoGenerator @MockK(relaxed = true) lateinit var quoteStore: Sep38QuoteStore @@ -293,11 +295,11 @@ class Sep31ServiceTest { MockKAnnotations.init(this, relaxUnitFun = true) every { appConfig.stellarNetworkPassphrase } returns Constants.TEST_NETWORK_PASS_PHRASE every { appConfig.hostUrl } returns Constants.TEST_HOST_URL - every { appConfig.jwtSecretKey } returns Constants.TEST_JWT_SECRET + every { secretConfig.sep10JwtSecretKey } returns Constants.TEST_JWT_SECRET every { appConfig.languages } returns listOf("en") every { sep31Config.paymentType } returns STRICT_SEND every { txnStore.newTransaction() } returns PojoSep31Transaction() - jwtService = spyk(JwtService(appConfig)) + jwtService = spyk(JwtService(secretConfig)) sep31Service = Sep31Service( diff --git a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt index 6c4b026117..16af292f38 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -23,6 +23,7 @@ import org.stellar.anchor.api.sep.sep38.Sep38Context.* import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig +import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep38Config import org.stellar.anchor.event.EventPublishService import org.stellar.anchor.event.models.QuoteEvent @@ -32,10 +33,6 @@ class Sep38ServiceTest { override fun isEnabled(): Boolean { return true } - - override fun getQuoteIntegrationEndPoint(): String? { - return null - } } companion object { @@ -57,6 +54,7 @@ class Sep38ServiceTest { // sep10 related: @MockK(relaxed = true) private lateinit var appConfig: AppConfig + @MockK(relaxed = true) private lateinit var secretConfig: SecretConfig @BeforeEach fun setUp() { @@ -69,7 +67,7 @@ class Sep38ServiceTest { assertEquals(3, assets.size) // sep10 related: - every { appConfig.jwtSecretKey } returns "secret" + every { secretConfig.sep10JwtSecretKey } returns "secret" // store/db related: every { quoteStore.newInstance() } returns PojoSep38Quote() diff --git a/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt index 7c3cb063a3..9ee94ef0a7 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt @@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.slf4j.Logger import org.stellar.anchor.Constants.Companion.TEST_HOST_URL -import org.stellar.anchor.Constants.Companion.TEST_JWT_SECRET import org.stellar.anchor.Constants.Companion.TEST_NETWORK_PASS_PHRASE import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.PII @@ -99,14 +98,6 @@ internal class LogTest { return "https://horizon.stellar.org" } - override fun getJwtSecretKey(): String { - return TEST_JWT_SECRET - } - - override fun getAssets(): String { - return "test_assets_file" - } - override fun getLanguages(): MutableList { return mutableListOf("en") } diff --git a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt index 2ac1534f46..780049e4ed 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt @@ -5,9 +5,10 @@ import io.mockk.impl.annotations.MockK import okhttp3.Response import okhttp3.ResponseBody import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.stellar.anchor.util.NetUtil.isUrlValid internal class NetUtilTest { @MockK private lateinit var mockCall: okhttp3.Call @@ -60,4 +61,32 @@ internal class NetUtilTest { val request = OkHttpUtil.buildGetRequest("https://www.stellar.org") assertNotNull(NetUtil.getCall(request)) } + + @Test + fun `test good urls with isUrlValid()`() { + assertTrue(isUrlValid("http://www.stellar.org")) + assertTrue(isUrlValid("https://www.stellar.org/")) + assertTrue(isUrlValid("https://www.stellar.org/.well-known/stellar.toml")) + assertTrue(isUrlValid("https://www.stellar.org/sep1?q=123&p=false")) + assertTrue(isUrlValid("https://www.stellar.org/sep1?q=&p=false")) + assertTrue(isUrlValid("https://www.stellar.org/a/b/c")) + assertTrue(isUrlValid("https://www.stellar.org/a/")) + assertTrue(isUrlValid("http://192.168.100.1")) + assertTrue(isUrlValid("http://192.168.100.1/a/")) + assertTrue(isUrlValid("ftp://ftp.stellar.org")) + assertTrue(isUrlValid("ftp://ftp.stellar.org/a/b/c")) + assertTrue(isUrlValid("ftp://ftp.stellar.org/a/")) + assertTrue(isUrlValid("file:///home/johndoe/a.toml")) + } + + @Test + fun `test bad urls with isUrlValid()`() { + assertFalse(isUrlValid("http:// www.stellar.org")) + assertFalse(isUrlValid("https:// www.stellar.org")) + assertFalse(isUrlValid("https:// www.stellar.org/a /")) + assertFalse(isUrlValid("https:// www.stellar.org/a?p=123&q= false")) + assertFalse(isUrlValid("https://192.168.100 .1")) + assertFalse(isUrlValid("abc://www.stellar.org")) + assertFalse(isUrlValid("http:// www.stellar.org")) + } } diff --git a/core/src/test/kotlin/org/stellar/anchor/util/StringHelperTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/StringHelperTest.kt new file mode 100644 index 0000000000..0234223a9d --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/util/StringHelperTest.kt @@ -0,0 +1,34 @@ +package org.stellar.anchor.util + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.stellar.anchor.util.StringHelper.* + +class StringHelperTest { + @Test + fun `test string isEmpty()`() { + assertTrue(isEmpty(null)) + assertTrue(isEmpty("")) + assertFalse(isEmpty("-1")) + assertFalse(isEmpty("not empty")) + } + + @Test + fun `test camelToSnake()`() { + assertEquals("hello_world", camelToSnake("helloWorld")) + assertEquals("hello_world", camelToSnake("HelloWorld")) + assertEquals("hello_world", camelToSnake("hello_World")) + assertEquals("hello_world", camelToSnake("hello_WORLD")) + assertEquals("hello_world", camelToSnake("helloWORLD")) + assertEquals("hello.world", camelToSnake("hello.world")) + assertEquals("hello.world", camelToSnake("hello.World")) + assertEquals("helloworld", camelToSnake("Helloworld")) + + assertEquals("hello_world.good_morning", camelToSnake("hello-world.goodMorning")) + assertEquals("hello1.world", camelToSnake("hello1.World")) + + assertEquals("", camelToSnake("")) + assertThrows { camelToSnake(null) } + } +} diff --git a/helm-charts/sep-service/Chart.yaml b/helm-charts/sep-service/Chart.yaml index 936c1cfea4..3ceb131a19 100644 --- a/helm-charts/sep-service/Chart.yaml +++ b/helm-charts/sep-service/Chart.yaml @@ -7,4 +7,4 @@ maintainers: sources: - https://github.com/stellar/java-stellar-anchor-sdk name: sep -version: 0.3.87 +version: 0.4.0 diff --git a/helm-charts/sep-service/README.md b/helm-charts/sep-service/README.md index c482336973..adaf0128a7 100644 --- a/helm-charts/sep-service/README.md +++ b/helm-charts/sep-service/README.md @@ -6,8 +6,8 @@ This chart installs the Stellar Anchor Platform SEP Serviceon Kubernetes cluster ## Upgrading your Clusters To upgrade the Anchor Platform, you can use the helm upgrade command. example: -```sh -helm upgrade -f myvalues.yaml -f override.yaml --version 0.1.0 my-release stellar/anchorplatform +``` +helm upgrade -f myvalues.yaml -f override.yaml --version 0.4.0 my-release stellar/anchorplatform ``` ## Installing the Chart @@ -33,62 +33,42 @@ The command removes all the Kubernetes components associated with the operator a Helm Chart Configuration will be used to configure deployment via Helm Chart values substitution. If a parameter value is not provided, Anchor Platform will attempt to use a `classpath` [default values file](../../platform/src/main/resources/anchor-config-defaults.yaml). For further information on all available parameters and Anchor Platform configuration, please refer to the [Anchor Platform Configuration Guide](../../docs/00%20-%20Stellar%20Anchor%20Platform.md) ### Helm Chart Kubernetes Configuration - -The following table lists the configurable parameters of the Anchor Platform chart and their default values. These are also reflected in the example [values.yaml](./values.yaml). - -| Parameter | Description | Required? | Default Value | -|------------------------|------------------------------------------------------------------------------------------|-----------|-----------------| -| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | -| service.containerPort | ingress backend container port | yes | 8080 | -| service.servicePort | ingress backend service port | yes | 8080 | -| service.replicas | number of instances of anchor platform | yes | 1 | -| service.type | service type | yes | NodePort | -| service.name | name of service | yes | sep | -| image.repo | dockerhub image repository | yes | stellar | -| image.name | dockerhub image name | yes | anchor-platform | -| image.tag | dockerhub anchorplatform dag | yes | latest | -| deployment.replicas | number of instances | yes | 1 | -| deployment.envFrom | kubernetes secrets name | no | N/A | -| ingress.metadata | ingress metadata (list) | no | N/A | -| ingress.labels | ingress labels (list) | no | N/A | -| ingress.annotations | ingress annotations (list) | no | N/A | -| ingress.tls.host | tls certificate hostname | no | N/A | -| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | N/A | -| ingress.rules | ingress backend rules (list) | | | - -### Database - -Unless you are using sql-lite (default configuration) your values.yaml should contain database access configuration, it should contain both the stellar.anchor.data_access.type (currently only `data-spring-jdbc` is supported) and `stellar.anchor.data_access.setttings` which contains the name of the yaml key (nested under key `stellar`) containing your database configuration settings. For example, if you plan to use AWS Aurora, you would set the data_access type and settings along with the configuration for database access as follows: - -```yaml -stellar: - anchor: - data_access: - type: data-spring-jdbc - settings: data-spring-jdbc-aws-aurora-postgres - - data_spring_jdbc_aws_aurora_postgres: - spring.jpa.generate-ddl: true - spring.jpa.database: POSTGRESQL - spring.jpa.show-sql: false - spring.datasource.driver-class-name: org.postgresql.Driver - spring.datasource.type: org.stellar.anchor.platform.databaseintegration.IAMAuthDataSource - spring.datasource.url: jdbc:postgresql://database-aurora-iam-instance-1.chizvyczscs2.us-east-1.rds.amazonaws.com:5432/anchorplatform - spring.datasource.username: anchorplatform1 - spring.datasource.hikari.max-lifetime: 840000 # 14 minutes because IAM tokens are valid for 15min - spring.mvc.converters.preferred-json-mapper: gson - spring.liquibase.change-log: classpath:/db/changelog/db.changelog-master.yaml -``` +The following table lists the configurable parameters related to k8s configuration of the helm chart and their default values. +These are also reflected in the example [values.yaml](./values.yaml). + +| Parameter | Description | Required? | Default Value | +|-------------------------|------------------------------------------------------------------------------------------|---|-----------------| +| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | +| service.containerPort | ingress backend container port | yes | 8080 | +| service.servicePort | ingress backend service port | yes | 8080 | +| service.replicas | number of instances of anchor platform | yes | 1 | +| service.type | service type | yes | NodePort | +| service.name | name of service | yes | sep | +| image.repo | dockerhub image repository | yes | stellar | +| image.name | dockerhub image name | yes | anchor-platform | +| image.tag | dockerhub anchorplatform tag | yes | latest | +| image.pullPolicy | image pull policy | yes | always | +| deployment.annotations | deployment annotations | yes | 1 | +| deployment.envVars | k8s env vars (`env`, `envFrom`, `configMapRef`) | no | N/A | +| deployment.volumeMounts | additional k8s volumes | no | N/A | +| ingress.metadata | ingress metadata (list) | no | N/A | +| ingress.labels | ingress labels (list) | no | N/A | +| ingress.annotations | ingress annotations (list) | no | N/A | +| ingress.tls.host | tls certificate hostname | no | N/A | +| ingress.tls.secretName | k8s secret holding tls certificate if reqd | no | N/A | +| ingress.rules | ingress backend rules (list) | | | ### Secrets Configuration - -The following is an example kubernetes secrets manifest that will store base64 encoded secrets referenced using placeholders in the anchor platform configuration file. In the following example, by replacing configuration values with ${JWT_SECRET} and ${SEP10_SIGNING_SEED} anchor platform will read those values from environment variables injected by the kubernetes deployment. +The following is an example kubernetes secrets manifest that will store base64 encoded secrets referenced using placeholders in the Anchor Platform configuration file. +In the following example, Anchor Platform will read the specified values from environment variables injected by the kubernetes deployment. ```yaml apiVersion: v1 data: - JWT_SECRET: c2VjcmV0 - SEP10_SIGNING_SEED: U0FYM0FINjIyUjJYVDZEWFdXU1JJRENNTVVDQ01BVEJaNVU2WEtKV0RPN00yRUpVQkZDM0FXNVg= + SECRET.SEP10.JWT_SECRET: c2VjcmV0 + SECRET.SEP10.SIGNING_SEED: U0FYM0FINjIyUjJYVDZEWFdXU1JJRENNTVVDQ01BVEJaNVU2WEtKV0RPN00yRUpVQkZDM0FXNVg= + SECRET.CALLBACK_API.AUTH_SECRET: UExBVEZPUk1fVE9fQU5DSE9SX1NFQ1JFVAo= + SECRET.PLATFORM_API.AUTH_SECRET: QU5DSE9SX1RPX1BMQVRGT1JNX1NFQ1JFVAo= kind: Secret metadata: name: apsigningseed @@ -96,130 +76,25 @@ metadata: type: Opaque ``` -# Assets Configuration - -The following is an example of the `assets` configuration that can be set in the helm chart's `values.yaml`. -Not setting this section will pass in an empty assets list. - -```yaml -assets: - - "schema": "stellar" - "code": "USDC" - "issuer": "GYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" - "distribution_account": "GZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "significant_decimals": 2 - "deposit": - "enabled": true - "fee_minimum": 0 - "fee_percent": 0 - "min_amount": 0 - "max_amount": 10000 - "withdraw": - "enabled": true - "fee_fixed": 0 - "fee_percent": 0 - "min_amount": 0 - "max_amount": 10000 - "send": - "fee_fixed": 0 - "fee_percent": 0 - "min_amount": 0 - "max_amount": 10000 - "sep31": - "quotes_supported": true - "quotes_required": true - "sep12": - "sender": - "types": - "sep31-sender": - "description": "U.S. citizens limited to sending payments of less than $10,000 in value" - "sep31-large-sender": - "description": "U.S. citizens that do not have sending limits" - "sep31-foreign-sender": - "description": "non-U.S. citizens sending payments of less than $10,000 in value" - "receiver": - "types": - "sep31-receiver": - "description": "U.S. citizens receiving USD" - "sep31-foreign-receiver": - "description": "non-U.S. citizens receiving USD" - "fields": - "transaction": - "receiver_routing_number": - "description": "routing number of the destination bank account" - "receiver_account_number": - "description": "bank account number of the destination" - "type": - "description": "type of deposit to make" - "choices": - - "SEPA" - - "SWIFT" - "sep38": - "exchangeable_assets": - - "stellar:USDC:GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" - - "iso4217:USD" - "sep24_enabled": true - "sep31_enabled": true - "sep38_enabled": true -``` - -## Helm Chart Kubernetes Configuration - -The following table lists the configurable parameters of the Anchor Platform chart and their default values. These are also reflected in the example [values.yaml](./values.yaml). - -| Parameter | Description | Required? | Default Value | -|------------------------|------------------------------------------------------------------------------------------|-----------|-----------------| -| fullName | customize anchor platform k8s resource names eg < fullName >-configmap-< service.name > | yes | anchor-platform | -| service.containerPort | ingress backend container port | yes | 8080 | -| service.servicePort | ingress backend service port | yes | 8080 | -| service.replicas | number of instances of anchor platform | yes | 1 | -| service.type | service type | yes | NodePort | -| service.name | name of service | yes | sep | -| image.repo | dockerhub image repository | yes | stellar | -| image.name | dockerhub image name | yes | anchor-platform | -| image.tag | dockerhub anchorplatform dag | yes | latest | -| deployment.replicas | number of instances | yes | 1 | -| deployment.envFrom | kubernetes secrets name | no | n/a | -| ingress.metadata | ingress metadata (list) | no | n/a | -| ingress.labels | ingress labels (list) | no | n/a | -| ingress.annotations | ingress annotations (list) | no | n/a | -| ingress.tls.host | tls certificate hostname | no | n/a | -| ingress.tls.secretName | k8 secret holding tls certificate if reqd | no | n/a | -| ingress.rules | ingress backend rulues (list) | | | - ## Helm Chart Stellar Anchor Platform Configuration +The following table lists additional configurable parameters for the Anchor Platform application +and their default values. For a full list of configuration options, see Anchor Platform's configuration documentation. + +| Parameter | Description | Default Value | +|--------------------------------------------------------|----------------------------------------------------------------------------------|-----------------------| +| assets_config | inline definition of assets file | | +| toml_config | inline defintion of SEP1 stellar toml file | | +| app_config.host_url | URL of the Anchor Platform SEP Service | http://localhost:8080 | +| app_config.data.type | database type: `h2` (in-memory), `sqlite`, `postgres`, `aurora` | h2 | +| app_config.callback_api.base_url | callback endpoint | localhost:8081 | +| app_config.callback_api.auth.type | Anchor Platform to Anchor Backend Authentication type JWT_TOKEN, API_KEY or NONE | NONE | +| app_config.logging.level | TRACE,DEBUG,INFO,WARN,ERROR,FATAL | INFO | +| app_config.sep1.enabled | whether sep1 service is enabled | false | +| app_config.sep10.enabled | whether sep10 service is enabled | false | +| app_config.sep12.enabled | whether sep12 service is enabled | false | +| app_config.sep31.enabled | whether sep31 service is enabled | false | +| app_config.sep38.enabled | whehter sep38 service is enabled | false | +| app_config.events.enabled | whether event service is enabled | false | +| app_config.events.publisher_type | message broker type: kafka or sqs | kafka | +| app_config.events.options.bootstrapServer | event broker host:port | localhost:29092 | -The following table lists the additional configurable parameters of the Anchor Platform chart and their default values. - -| Parameter | Description | Required? | Default Value | -|----------------------------------------------------------------|------------------------------------------------------------------------------------|-----------|------------------------------| -| stellar.app_config.app.hostUrl | URL of the Anchor Platform SEP Service | y | N/A | -| stellar.app_config.app.jwtSecretKey | web encryption key | y | ${JWT_SECRET_KEY} | N/A | -| stellar.app_config.app.stellarNetwork | TESTNET OR PUBNET | n | TESTNET | -| stellar.app_config.app.logLevel | TRACE,DEBUG,INFO,WARN,ERROR,FATAL | n | INFO | -| stellar.anchor.data_access.type | database access type | yes | data-spring-jdbc | -| stellar.anchor.data_access.setttings | values config root-level key for jdbc config | yes | data-spring-jdbc-sqlite | -| stellar.toml.documentation.ORG_NAME | organization name to configure stellar.toml | yes | My Organization | -| stellar.toml.documentation.ORG_URL | your organization URL to configure stellar.toml | yes | https:/myorg.org | -| stellar.toml.documentation.ORG_DESCRIPTION | your organization description to configure stellar.toml | yes | https://mylogo.png | -| stellar.toml.documentation.ORG_SUPPORT_EMAIL | your organization support email address to configure stellar.toml | yes | myname@myorg.org | -| stellar.app_config.app.integration_auth.auth_type | Anchor Platform to Anchor Backend Authentication type JWT_TOKEN, API_KEY or NONE | n | NONE | -| stellar.app_config.app.integration_auth.platformToAnchorSecret | secret value | n | ${PLATFORM_TO_ANCHOR_SECRET} | -| stellar.app_config.app.integration_auth.anchorToPlatformSecret | secret value | n | ${ANCHOR_TO_PLATFORM_SECRET} | -| stellar.app_config.app.integration_auth.expirationMilliseconds | integration auth credential expiration ms | n | 30000 | -| stellar.app_config.app.anchor_callback | endpoint to retrieve unique address & memo for sep 31 post /transaction | n | N/A | -| sep1.enabled | sep1 true if service enabled | yes | true | -| sep10.enabled | sep1 true if service enabled | yes | true | -| sep10.homeDomain | a domain hosting a SEP-1 stellar.toml | n | N/A | -| sep10.signingSeed | secret key of Stellar Account used for auth. (public key in stellar.toml ACCOUNTS) | y | N/A | -| sep12.enabled | sep1 true if service enabled | yes | true | -| sep12.customerIntegrationEndpoint | URL of SEP 12 KYC Endpoint | no | N/A | -| sep31.enabled | sep1 true if service enabled | yes | true | -| sep31.feeIntegrationEndPoint | URL of Fees Endpoint | no | N/A | -| sep38.enabled | sep1 true if service enabled | yes | true | -| sep38.quoteIntegrationEndPoint | URL of Quotes Endpoint | no | N/A | -| event.enabled | sep1 true if event service enabled | yes | true | -| event.publisherType | kafka | sqs | no | kafka | -| kafka_publisher.bootstrapServer | kafka broker host:port | no | N/A | -| kafka_publisher.useIAM | use IAM Authentication for MSK | no | true | -| assets | see [Assets Configuration](#assets-configuration) | yes | [] | diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index 4800c063c8..a842a9e272 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -11,6 +11,9 @@ image: tag: latest pullPolicy: Always deployment: + args: + - '"--sep-server"' + - '"--stellar-observer"' startupProbePeriodSeconds: 10 startupProbeFailureThreshold: 30 serviceAccountName: default @@ -33,7 +36,9 @@ deployment: value: file:/config/anchor-config.yaml envFrom: - secretRef: - name: apsigningseed + name: anchor-platform-secrets + - configMapRef: + name: {{ .Values.fullName }}-config-env-vars ingress: labels: app: appy @@ -58,102 +63,78 @@ ingress: backend: servicePort: "{{ .Values.service.containerPort }}" serviceName: "{{ .Values.fullName }}-svc-{{ .Values.service.name }}" -stellar: - toml: - #accounts: ['"GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR"'] - signing_key: "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" - documentation: - ORG_NAME: "Stellar Development Foundation" - ORG_URL: "https://www.stellar.org" - ORG_DESCRIPTION: "Stellar Network" - ORG_URL: "https://your_org_url.png" - ORG_SUPPORT_EMAIL: "reece@stellar.org" - anchor: - data_access: - type: data-spring-jdbc - settings: data-spring-jdbc-sqlite -# settings: data-spring-jdbc-aws-aurora-postgres -# data_spring_jdbc_aws_aurora_postgres: -# spring.jpa.database: POSTGRESQL -# spring.jpa.show-sql: false -# spring.datasource.driver-class-name: org.postgresql.Driver -# spring.datasource.type: org.stellar.anchor.platform.databaseintegration.IAMAuthDataSource -# spring.datasource.url: jdbc:postgresql://database-aurora.region.amazonaws.com:port/instance -# spring.datasource.username: ${DB_USERNAME} -# spring.datasource.hikari.max-lifetime: 840000 # 14 minutes because IAM tokens are valid for 15min -## spring.mvc.converters.preferred-json-mapper: gson -# spring.flyway.user: ${FLYWAY_USER} -# spring.flyway.password: ${FLYWAY_PASSWORD} # can use a token value if authenticating via IAM -# spring.flyway.url: jdbc:postgresql://database-aurora.region.rds.amazonaws.com:port/instance -# spring.flyway.locations: classpath:/db/migration - data_spring_jdbc_sqlite: - spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect - spring.jpa.hibernate.ddl-auto: update - spring.jpa.generate-ddl: true - spring.jpa.hibernate.show_sql: false - spring.datasource.url: jdbc:sqlite:anchor-proxy.db - spring.datasource.driver-class-name: org.sqlite.JDBC - spring.datasource.username: ${SQLITE_USERNAME} - spring.datasource.password: ${SQLITE_PASSWORD} - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.user: ${SQLITE_USERNAME} - spring.flyway.password: ${SQLITE_USERNAME} # can use a token value if authenticating via IAM - spring.flyway.url: jdbc:sqlite:anchor-proxy.db - spring.flyway.locations: classpath:/db/migration - #spring.flyway.enabled: false - app_config: - app: - stellarNetwork: TESTNET - hostUrl: https://your_anchor_domain.com - backendUrl: https://your_anchor_domain.com - jwtSecretKey: ${JWT_SECRET} - assets: file:/assets/assets.json - platformToAnchorSecret: ${PLATFORM_TO_ANCHOR_SECRET} - anchorToPlatformSecret: ${ANCHOR_TO_PLATFORM_SECRET} - sep1: - enabled: true - stellarFile: file:/config/stellar-wks.toml - sep10: - enabled: true - homeDomain: your_anchor_domain.com - signingSeed: ${SEP10_SIGNING_SEED} - requireKnownOmnibusAccount: false - omnibusAccountList: -# omnibusAccountList: | -# GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAV, -# GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAA - sep12: - enabled: true - customerIntegrationEndpoint: https://anchor-reference-server-dev.stellar.org - sep31: - enabled: true - feeIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org - uniqueAddressIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org - sep38: - enabled: true - quoteIntegrationEndPoint: https://anchor-reference-server-dev.stellar.org - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: ${CIRCLE_API_KEY} - event: - enabled: true - publisherType: kafka - kafka_publisher: - bootstrapServer: reecekafka-0.reecekafka-headless.sandbox.svc.cluster.local:9092 - circle_payment_observer: - enabled: true - payment_gateway: - circle: - enabled: true - stellar: - enabled: false - name: "stellar" - horizonUrl: https://horizon-testnet.stellar.org - secretKey: secret # stellar account secret key - spring: - logging: - level: - root: DEBUG - org.springframework: DEBUG - org.springframework.web.filter: DEBUG - org.stellar: DEBUG + +app_config: + host_url: https://your_anchor_domain.com + stellar_network.network: TESTNET + callback_api.base_url: https://your_anchor_domain.com + callback_api.auth.type: JWT_TOKEN + sep1.enabled: true + sep10.enabled: true + sep10.home_domain: your_anchor_domain.com + sep12.enabled: true + sep31.enabled: true + sep31.deposit_info_generator_type: api + sep38.enabled: true + data.type: h2 + assets.type: json + +toml_config: | + ACCOUNTS = [] + VERSION = "0.1.0" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + SIGNING_KEY = "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" + TRANSFER_SERVER = "https://your_anchor_domain.com/sep6" + TRANSFER_SERVER_SEP0024 = "https://your_anchor_domain.com/sep24" + WEB_AUTH_ENDPOINT = "https://your_anchor_domain.com/auth" + KYC_SERVER = "https://your_anchor_domain.com/sep12" + DIRECT_PAYMENT_SERVER = "https://your_anchor_domain.com/sep31" + ANCHOR_QUOTE_SERVER = "https://your_anchor_domain.com//sep38" + + [[CURRENCIES]] + code = "USDC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + status = "test" + is_asset_anchored = true + anchor_asset_type = "fiat" + desc = "A test USDC issued by Circle." + + [DOCUMENTATION] + ORG_NAME = "Your Anchor Domain" + ORG_URL = "https://your_anchor_domain.org" + ORG_DESCRIPTION = "Your anchor domain" + ORG_SUPPORT_EMAIL="support@your_anchor_domain.org" + +assets_config: | + { + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", + "distribution_account": "GBJDTHT4562X2H37JMOE6IUTZZSDU6RYGYUNFYCHVFG3J4MYJIMU33HK", + "significant_decimals": 2, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + } + } + ] + } + diff --git a/helm-charts/sep-service/templates/_common.tpl b/helm-charts/sep-service/templates/_common.tpl index 8a1e8e8aaf..8363b0e835 100644 --- a/helm-charts/sep-service/templates/_common.tpl +++ b/helm-charts/sep-service/templates/_common.tpl @@ -17,4 +17,26 @@ {{- define "common.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} \ No newline at end of file +{{- end -}} + +{{- define "common.addField" -}} +{{- $key := index . 0 }} +{{- $val := index . 1 }} +{{- $tp := typeOf $val }} +{{- if eq $tp "map[string]interface {}" }}{{ printf "%s:\n" $key }} +{{- range $mapKey, $mapValue := $val }} +{{- printf " %s: \"%v\"\n" $mapKey $mapValue }} +{{- end }} +{{- else }} +{{- if toString $val }} +{{- printf "%s: \"%v\"\n" $key $val -}} +{{- end }} +{{- end }} +{{- end }} + +{{- define "common.addMultiline" -}} +{{- $key := index . 0 -}} +{{- $value := index . 1 -}} +{{ printf "%s: |" $key }} +{{ printf "%s" $value | indent 4 }} +{{- end }} \ No newline at end of file diff --git a/helm-charts/sep-service/templates/configmap.yaml b/helm-charts/sep-service/templates/configmap.yaml index f4f49b48c6..9376b5f391 100644 --- a/helm-charts/sep-service/templates/configmap.yaml +++ b/helm-charts/sep-service/templates/configmap.yaml @@ -1,277 +1,18 @@ -{{- if .Values.assets }} -kind: ConfigMap apiVersion: v1 +kind: ConfigMap metadata: - name: {{ .Values.fullName }}-assets + name: {{ .Values.fullName }}-config-env-vars data: - assets.json: | - { - "assets": {{ toPrettyJson .Values.assets | indent 4 }} - } + {{- range $key, $value := .Values.app_config }} + {{ include "common.addField" (list $key $value ) }} + {{- end }} + {{ include "common.addMultiline" (list "assets.value" .Values.assets_config ) }} + {{ include "common.addMultiline" (list "sep1.toml.value" .Values.toml_config ) }} --- -{{- end }} apiVersion: v1 kind: ConfigMap metadata: name: {{ .Values.fullName }} data: - # TODO: remove all secrets (ex: move to env vars, k8s secrets, vault, etc...) anchor-config.yaml: | - stellar: - anchor: - {{- if .Values.stellar.anchor }} - config: {{ .Values.stellar.anchor.config | default "in-memory" }} - {{- else }} - config: in-memory - {{- end }} - {{- if (.Values.stellar.anchor).app_config }} - app-config: - type: {{ .Values.stellar.anchor.app_config.type | default "config-spring-property" }} # Activate [config-spring-property] module - settings: {{ .Values.stellar.anchor.app_config.settings | default "app-config" }} # The location of the configuration data - {{- else }} - app-config: - type: config-spring-property # Activate [config-spring-property] module - settings: app-config # The location of the configuration data - {{- end }} - {{- if (.Values.stellar.anchor).data_access }} - data-access: - type: {{ .Values.stellar.anchor.data_access.type | default "data-spring-jdbc" }} # Activate [config-spring-jdbc] module. - settings: {{ .Values.stellar.anchor.data_access.settings | default "data-spring-jdbc-sqlite" }} # The location of the configuration data in this file. - {{- else }} - data-access: - type: data-spring-jdbc # Activate [config-spring-jdbc] module. - settings: data-spring-jdbc-sqlite # The location of the configuration data in this file. - {{- end }} - {{- if (.Values.stellar.anchor).logging }} - logging: - type: {{ .Values.stellar.anchor.logging.type | default "logging-logback" }} - settings: {{ .Values.stellar.anchor.logging.settings | default "logging-logback-settings" }} - {{- else }} - logging: - type: logging-logback - settings: logging-logback-settings - {{- end }} - app-config: - app: - stellarNetwork: {{ .Values.stellar.app_config.app.stellarNetwork | default "TESTNET" }} - {{- if eq .Values.stellar.app_config.app.stellarNetwork "PUBNET" }} - stellarNetworkPassphrase: Public Global Stellar Network ; September 2015 - horizonUrl: https://horizon.stellar.org - {{- else }} - stellarNetworkPassphrase: {{ .Values.stellar.app_config.app.stellarNetworkPassphrase | default "Test SDF Network ; September 2015" }} - horizonUrl: {{ .Values.stellar.app_config.app.horizonUrl | default "https://horizon-testnet.stellar.org" }} - {{- end }} - hostUrl: {{ .Values.stellar.app_config.app.hostUrl }} - languages: en - assets: {{ .Values.stellar.app_config.app.assets | default "assets-test.json" }} - jwtSecretKey: {{ .Values.stellar.app_config.app.jwtSecretKey | default "${JWT_SECRET}" }} - integration-auth: - authType: JWT_TOKEN - platformToAnchorSecret: {{ .Values.stellar.app_config.app.platformToAnchorSecret | default "${PLATFORM_TO_ANCHOR_SECRET}" }} - anchorToPlatformSecret: {{ .Values.stellar.app_config.app.anchorToPlatformSecret | default "${ANCHOR_TO_PLATFORM_SECRET}" }} - expirationMilliseconds: {{ .Values.stellar.app_config.app.expirationMilliseconds | default 30000 }} - # The anchor callback configuration - anchor-callback: - # The CallbackAPI endpoint - {{- if ((.Values.stellar.app_config).anchor_callback) }} - endpoint: {{ .Values.stellar.app_config.anchor_callback.customerIntegrationEndpoint | default .Values.stellar.app_config.app.backendUrl }} - {{- else }} - endpoint: {{ .Values.stellar.app_config.app.backendUrl }} - {{- end }} - # sep-1 - sep1: - enabled: true - stellarFile: file:/config/stellar-wks.toml - # sep-10 - sep10: - {{- if ((.Values.stellar.app_config).sep10) }} - enabled: {{ .Values.stellar.app_config.sep10.enabled | default "true" }} - homeDomain: {{ .Values.stellar.app_config.sep10.homeDomain }} - clientAttributionRequired: {{ .Values.stellar.app_config.sep10.clientAttributionRequired | default "false" }} - clientAttributionAllowList: {{ .Values.stellar.app_config.sep10.clientAttributionAllowList | default "lobstr.co,preview.lobstr.co" }} - # clientAttributionDenyList: # use this if we want to black list. - authTimeout: {{ .Values.stellar.app_config.sep10.authTimeout | default 900 }} - jwtTimeout: {{ .Values.stellar.app_config.sep10.jwtTimeout | default 86400 }} - signingSeed: {{ .Values.stellar.app_config.sep10.signingSeed | default "${SEP10_SIGNING_SEED}" }} - requireKnownOmnibusAccount: {{ .Values.stellar.app_config.sep10.requireKnownOmnibusAccount | default "false" }} - {{- if .Values.stellar.app_config.sep10.omnibusAccountList }} - omnibusAccountList: -{{- $.Values.stellar.app_config.sep10.omnibusAccountList | toYaml | nindent 10 }} - {{- end }} - {{- else }} - enabled: true - homeDomain: "your_home_domain.com" - clientAttributionRequired: "false" - clientAttributionAllowList: "lobstr.co,preview.lobstr.co" - # clientAttributionDenyList: # use this if we want to black list. - authTimeout: 900 - jwtTimeout: 86400 - signingSeed: ${SEP10_SIGNING_SEED} - {{- end }} - # sep-12 - sep12: - {{- if ((.Values.stellar.app_config).sep12) }} - enabled: {{ .Values.stellar.app_config.sep12.enabled | default "true" | toYaml }} - customerIntegrationEndpoint: {{ .Values.stellar.app_config.sep12.customerIntegrationEndpoint | default .Values.stellar.app_config.app.backendUrl }} - {{- else }} - enabled: "true" - customerIntegrationEndpoint: {{ .Values.stellar.app_config.app.backendUrl }} - {{- end }} - # sep-24 - sep24: - {{- if ((.Values.stellar.app_config).sep24) }} - enabled: {{ .Values.stellar.app_config.sep24.enabled | default "true" }} - interactiveJwtExpiration: {{ .Values.stellar.app_config.sep24.interactiveJwtExpiration | default 3600 }} - interactiveUrl: {{ .Values.stellar.app_config.sep24.interactiveUrl | default .Values.stellar.app_config.app.backendUrl }} - {{- else }} - enabled: false - interactiveJwtExpiration: 3600 - interactiveUrl: {{ .Values.stellar.app_config.app.backendUrl }} - {{- end }} - # sep-31 - sep31: - {{- if ((.Values.stellar.app_config).sep31) }} - enabled: {{ .Values.stellar.app_config.sep31.enabled | default "true" }} - feeIntegrationEndPoint: {{ .Values.stellar.app_config.sep31.feeIntegrationEndPoint | default .Values.stellar.app_config.app.backendUrl }} - uniqueAddressIntegrationEndPoint: {{ .Values.stellar.app_config.sep31.uniqueAddressIntegrationEndPoint | default .Values.stellar.app_config.app.backendUrl}} - depositInfoGeneratorType: {{ .Values.stellar.app_config.sep31.depositInfoGeneratorType | default "self" }} - paymentType: {{ .Values.stellar.app_config.sep31.paymentType | default "STRICT_SEND" }} - {{- else }} - enabled: "true" - feeIntegrationEndPoint: {{ .Values.stellar.app_config.app.backendUrl }} - uniqueAddressIntegrationEndPoint: {{ .Values.stellar.app_config.app.backendUrl }} - paymentType: STRICT_SEND - {{- end }} - # sep-38 - sep38: - {{- if ((.Values.stellar.app_config).sep38) }} - enabled: {{ .Values.stellar.app_config.sep38.enabled | default "true" }} - quoteIntegrationEndPoint: {{ .Values.stellar.app_config.sep38.quoteIntegrationEndPoint | default .Values.stellar.app_config.app.backendUrl }} - {{- else }} - enabled: "true" - quoteIntegrationEndPoint: {{ .Values.stellar.app_config.app.backendUrl }} - {{- end }} - {{- if ((.Values.stellar.app_config).circle) }} - circle: - circleUrl: {{ .Values.stellar.app_config.circle.circleUrl | default "https://api-sandbox.circle.com" }} - apiKey: {{ .Values.stellar.app_config.circle.apiKey | default "${CIRCLE_API_KEY}" }} - {{- end }} - {{- if (.Values.stellar.app_config).payment_gateway }} - payment-gateway: - {{- if ((.Values.stellar.app_config).payment_gateway).circle }} - circle: - name: {{ .Values.stellar.app_config.payment_gateway.circle.name | default "circle" }} - enabled: {{ .Values.stellar.app_config.payment_gateway.circle.enabled | default "true" }} - {{- end }} - {{- if ((.Values.stellar.app_config).payment_gateway).stellar }} - stellar: - enabled: {{ .Values.stellar.app_config.payment_gateway.stellar.enabled | default "false" }} - name: {{ .Values.stellar.app_config.payment_gateway.stellar.name | default "stellar" }} - horizonUrl: {{ .Values.stellar.app_config.app.horizonUrl | default "https://horizon-testnet.stellar.org" }} - secretKey: {{ .Values.stellar.app_config.payment_gateway.stellar.secretKey | default "${PAYMENT_GATEWAY_STELLAR_SECRET_KEY}" }} # stellar account secret key - {{- end }} - {{- end }} - {{- if (.Values.stellar.app_config).circle_payment_observer }} - circle-payment-observer: - enabled: {{ .Values.stellar.app_config.circle_payment_observer.enabled | default "true" }} - horizonUrl: {{ .Values.stellar.app_config.circle_payment_observer.horizonUrl | default "https://horizon-testnet.stellar.org" }} - stellarNetwork: {{ .Values.stellar.app_config.circle_payment_observer.stellarNetwork | default "TESTNET" }} - trackedWallet: {{ .Values.stellar.app_config.circle_payment_observer.trackedWallet | default "all" }} - {{- end }} - {{- if (.Values.stellar.app_config).event }} - event: - # If enabled, publish Events to a queue (publisherType) - # publisherType - the type of queue to use for event publishing - enabled: {{ .Values.stellar.app_config.event.enabled | default "true" }} - publisherType: {{ .Values.stellar.app_config.event.publisherType | default "kafka" }} - {{- else }} - event: - # If enabled, publish Events to a queue (publisherType) - # publisherType - the type of queue to use for event publishing - enabled: false - publisherType: kafka - {{- end }} - metrics-service: - optionalMetricsEnabled: false # optional metrics that periodically query the database - runInterval: 30 # interval to query the database to generate the optional metrics - {{- if (.Values.stellar.app_config).kafka_publisher }} - kafka.publisher: - bootstrapServer: {{ .Values.stellar.app_config.kafka_publisher.bootstrapServer | default "missing_bootstrapServer:port" }} - useIAM: {{ .Values.stellar.app_config.kafka_publisher.useIAM | default "false" }} - useSingleQueue: {{ .Values.stellar.app_config.kafka_publisher.useSingleQueue | default "false" }} - {{- if ((.Values.stellar.app_config).kafka_publisher).eventTypeToQueue }} - eventTypeToQueue: - all: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.all | default "dev_ap_event_single_queue" }} - quoteCreated: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.quoteCreated | default "dev_ap_event_quote_created" }} - transactionCreated: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.transactionCreated | default "dev_ap_event_transaction_created"}} - transactionStatusChanged: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.transactionStatusChanged | default "dev_ap_event_transaction_status_changed" }} - transactionError: {{ .Values.stellar.app_config.kafka_publisher.eventTypeToQueue.transactionError | default "dev_ap_event_transaction_error" }} - {{- else }} - eventTypeToQueue: - all: "dev_ap_event_single_queue" - quoteCreated: "dev_ap_event_quote_created" - transactionCreated: "dev_ap_event_transaction_created" - transactionStatusChanged: "dev_ap_event_transaction_status_changed" - transactionError: "dev_ap_event_transaction_error" - {{- end }} - {{- end }} - {{- $settings_key := .Values.stellar.anchor.data_access.settings }} - {{- $settings := regexReplaceAll "\\W+" .Values.stellar.anchor.data_access.settings "_" }} - {{ $settings_key }}: - {{- range $key, $value := index .Values.stellar $settings }} - {{ $key }}: {{ $value }} - {{- end }} - spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 - stellar-wks.toml: | - ACCOUNTS = {{ .Values.stellar.toml.accounts | default "[\"GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY\"]" }} - VERSION = "{{ .Values.stellar.toml.version | default "0.1.0" }}" - NETWORK_PASSPHRASE = "{{ .Values.stellar.app_config.app.stellarNetworkPassphrase | default "Test SDF Network ; September 2015" }}" - SIGNING_KEY = "{{ .Values.stellar.toml.signing_key | default "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" }}" - TRANSFER_SERVER = "{{ .Values.stellar.app_config.app.hostUrl }}/sep6" - TRANSFER_SERVER_SEP0024 = "{{ .Values.stellar.app_config.app.hostUrl }}/sep24" - WEB_AUTH_ENDPOINT = "{{ .Values.stellar.app_config.app.hostUrl }}/auth" - KYC_SERVER = "{{ .Values.stellar.app_config.app.hostUrl }}/sep12" - DIRECT_PAYMENT_SERVER = "{{ .Values.stellar.app_config.app.hostUrl }}/sep31" - ANCHOR_QUOTE_SERVER = "{{ .Values.stellar.app_config.app.hostUrl }}/sep38" - - [[CURRENCIES]] - {{- if ((.Values.stellar).toml).currencies }} - {{- range .Values.stellar.toml.currencies }} - code = {{ .code | quote }} - issuer = {{ .issuer | quote }} - status = {{ .status | quote }} - is_asset_anchored = {{ .is_asset_anchored }} - anchor_asset_type = {{ .anchor_asset_type }} - desc = {{ .desc | quote }} - {{- end }} - {{- else }} - code = "USDC" - issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - status = "test" - is_asset_anchored = true - anchor_asset_type = "fiat" - desc = "A test USDC issued by Circle." - {{- end }} - - [DOCUMENTATION] - {{- if ((.Values.stellar).toml).documentation }} - ORG_NAME = {{ .Values.stellar.toml.documentation.ORG_NAME | default "My Organization" | quote }} - ORG_URL = {{ .Values.stellar.toml.documentation.ORG_URL | default "https://www.myorg.org" | quote }} - ORG_DESCRIPTION = {{ .Values.stellar.toml.documentation.ORG_DESCRIPTION | default "My Organization description" | quote }} - ORG_LOGO = {{ .Values.stellar.toml.documentation.ORG_LOGO | default "https://myorglogo.png" | quote }} - ORG_SUPPORT_EMAIL = {{ .Values.stellar.toml.documentation.ORG_SUPPORT_EMAIL | default "myname@myemail.org" | quote }} - {{- else }} - ORG_NAME = "My Organization" - ORG_URL = "https://www.myorg.org" - ORG_DESCRIPTION = "Please Customize all DOCUMENTATION values for your organization" - ORG_LOGO = "https://myorglogo.png" - ORG_SUPPORT_EMAIL="myname@myemail.org" - {{- end }} \ No newline at end of file + version: 1 diff --git a/helm-charts/sep-service/templates/deployment.yaml b/helm-charts/sep-service/templates/deployment.yaml index 88093ace6c..c89873e458 100644 --- a/helm-charts/sep-service/templates/deployment.yaml +++ b/helm-charts/sep-service/templates/deployment.yaml @@ -20,8 +20,8 @@ spec: {{- if (.Values.deployment).annotations }} annotations: {{- range $key, $value := .Values.deployment.annotations }} - {{ $key }}: {{ $value | quote }} - {{- end }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} spec: {{- if (.Values.deployment).serviceAccountName }} @@ -30,10 +30,8 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" - args: - - --sep-server - - --stellar-observer - imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [{{ join ", " .Values.deployment.args }}] + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always" }} startupProbe: httpGet: path: {{ .Values.deployment.startupProbePath | default "/health" }} @@ -57,46 +55,37 @@ spec: - name: sep-config-volume mountPath: /config readOnly: true - {{- if ((.Values.deployment).volumeMounts).configMaps }} - {{- range $conf := .Values.deployment.volumeMounts.configMaps }} + {{- if .Values.deployment.volumeMounts.configMaps }} + {{- range $conf := .Values.deployment.volumeMounts.configMaps }} - mountPath: {{ $conf.mountPath }} name: {{ $conf.name }}-volume + {{- end }} {{- end }} - {{- end }} - {{- if ((.Values.deployment).volumeMounts).secrets }} - {{- range $secret := .Values.deployment.volumeMounts.secrets }} + {{- if .Values.deployment.volumeMounts.secrets }} + {{- range $secret := .Values.deployment.volumeMounts.secrets }} - mountPath: {{ $secret.mountPath | default $secret.name }} name: {{ $secret.name }}-volume - {{- end }} + {{- end }} {{- end }} ports: - name: http containerPort: {{ .Values.service.containerPort }} protocol: TCP - {{- if (.Values.deployment).env }} - env: -{{ toYaml .Values.deployment.env | indent 12 }} - {{- end }} - {{- if (.Values.deployment).envFrom }} - envFrom: -{{ toYaml .Values.deployment.envFrom | indent 12 }} - {{- end }} - {{- if (.Values.deployment).resources }} - resources: -{{- toYaml .Values.deployment.resources | indent 12 }} + {{- if .Values.deployment.envVars }} + {{- toYaml .Values.deployment.envVars | nindent 10 }} {{- end }} volumes: - name: sep-config-volume configMap: name: {{ .Values.fullName }} - {{- if ((.Values.deployment).volumeMounts).configMaps }} + {{- if .Values.deployment.volumeMounts.configMaps }} {{- range $conf := .Values.deployment.volumeMounts.configMaps }} - name: {{ $conf.name }}-volume configMap: name: {{ $conf.name }} {{- end }} {{- end }} - {{- if ((.Values.deployment).volumeMounts).secrets }} + {{- if .Values.deployment.volumeMounts.secrets }} {{- range $secret := .Values.deployment.volumeMounts.secrets }} - name: {{ $secret.name }}-volume secret: diff --git a/helm-charts/sep-service/templates/ingress.yaml b/helm-charts/sep-service/templates/ingress.yaml index 0edc198882..4681efa0ee 100644 --- a/helm-charts/sep-service/templates/ingress.yaml +++ b/helm-charts/sep-service/templates/ingress.yaml @@ -3,8 +3,9 @@ kind: Ingress metadata: name: {{ .Values.fullName }}-ing-{{ .Values.service.name }} {{- if .Values.ingress.metadata }} - {{- range $key, $value := .Values.ingress.metadata }} - {{- end }} + {{- range $key, $value := .Values.ingress.metadata }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} {{- if .Values.ingress.annotations }} annotations: diff --git a/helm-charts/sep-service/templates/service.yaml b/helm-charts/sep-service/templates/service.yaml index ebe8cfa47f..91df67a4ca 100644 --- a/helm-charts/sep-service/templates/service.yaml +++ b/helm-charts/sep-service/templates/service.yaml @@ -2,15 +2,16 @@ apiVersion: v1 kind: Service metadata: name: {{ .Values.fullName }}-svc-{{ .Values.service.name }} -{{- if .Values.service.annotations }} + {{- if .Values.service.annotations }} annotations: -{{- range $key, $value := .Values.service.annotations }} + {{- range $key, $value := .Values.service.annotations }} {{ $key }}: {{ $value }} -{{- end }} -{{- end }} + {{- end }} + {{- end }} labels: {{- if .Values.service.labels }} {{- range $key, $value := .Values.service.labels }} + {{ $key }}: {{ $value | quote }} {{- end }} {{- end }} app.kubernetes.io/name: {{ .Values.fullName }} @@ -21,7 +22,7 @@ spec: type: {{ .Values.service.type }} ports: - protocol: TCP - port: {{ .Values.service.servicePort | default 8080}} # port number the service will listen on + port: {{ .Values.service.servicePort | default 8080 }} # port number the service will listen on targetPort: {{ .Values.service.targetPort | default 8080 }} # port number pods listen on selector: app.kubernetes.io/name: {{ .Values.fullName }} \ No newline at end of file diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt index 2839cf8410..b5e5665c47 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt @@ -12,7 +12,6 @@ import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* import org.skyscreamer.jsonassert.JSONAssert import org.springframework.context.ConfigurableApplicationContext -import org.springframework.core.env.get import org.stellar.anchor.api.callback.GetFeeRequest import org.stellar.anchor.api.callback.GetRateRequest import org.stellar.anchor.api.callback.GetRateRequest.Type.* @@ -29,6 +28,7 @@ import org.stellar.anchor.config.Sep38Config import org.stellar.anchor.platform.callback.RestCustomerIntegration import org.stellar.anchor.platform.callback.RestFeeIntegration import org.stellar.anchor.platform.callback.RestRateIntegration +import org.stellar.anchor.reference.AnchorReferenceServer import org.stellar.anchor.util.GsonUtils import org.stellar.anchor.util.Sep1Helper @@ -37,9 +37,12 @@ class AnchorPlatformIntegrationTest { companion object { private const val SEP_SERVER_PORT = 8080 private const val REFERENCE_SERVER_PORT = 8081 - private const val PLATFORM_TO_ANCHOR_SECRET = "myPlatformToAnchorSecret" private const val JWT_EXPIRATION_MILLISECONDS: Long = 10000 + private const val FIAT_USD = "iso4217:USD" + private const val STELLAR_USD = + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + private val platformToAnchorJwtService = JwtService(PLATFORM_TO_ANCHOR_SECRET) private val authHelper = AuthHelper.forJwtToken( @@ -64,24 +67,34 @@ class AnchorPlatformIntegrationTest { authHelper, gson ) - private val rri = + private val rriClient = RestRateIntegration("http://localhost:$REFERENCE_SERVER_PORT", httpClient, authHelper, gson) - private val rfi = + private val rfiClient = RestFeeIntegration("http://localhost:$REFERENCE_SERVER_PORT", httpClient, authHelper, gson) - const val fiatUSD = "iso4217:USD" - const val stellarUSDC = "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + private lateinit var platformServerContext: ConfigurableApplicationContext + init { + val props = System.getProperties() + props.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") + } @BeforeAll @JvmStatic fun setup() { - System.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") - ServiceRunner.startAnchorReferenceServer() - - SystemUtil.setEnv("STELLAR_ANCHOR_CONFIG", "classpath:/integration-test.anchor-config.yaml") - platformServerContext = ServiceRunner.startSepServer() + platformServerContext = + ServiceRunner.startSepServer( + SEP_SERVER_PORT, + "/", + mapOf( + "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", + "secret.sep10.jwt_secret" to "secret", + "secret.sep10.signing_seed" to + "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF" + ) + ) ServiceRunner.startStellarObserver() - SystemUtil.setEnv("STELLAR_ANCHOR_CONFIG", null) + + AnchorReferenceServer.start(REFERENCE_SERVER_PORT, "/") } } @@ -148,12 +161,12 @@ class AnchorPlatformIntegrationTest { @Test fun testRate_indicativePrices() { val result = - rri.getRate( + rriClient.getRate( GetRateRequest.builder() .type(INDICATIVE_PRICES) - .sellAsset(fiatUSD) + .sellAsset(FIAT_USD) .sellAmount("100") - .buyAsset(stellarUSDC) + .buyAsset(STELLAR_USD) .build() ) assertNotNull(result) @@ -171,13 +184,13 @@ class AnchorPlatformIntegrationTest { @Test fun testRate_indicativePrice() { val result = - rri.getRate( + rriClient.getRate( GetRateRequest.builder() .type(INDICATIVE_PRICE) .context(SEP31) - .sellAsset(fiatUSD) + .sellAsset(FIAT_USD) .sellAmount("100") - .buyAsset(stellarUSDC) + .buyAsset(STELLAR_USD) .build() ) assertNotNull(result) @@ -190,7 +203,7 @@ class AnchorPlatformIntegrationTest { "buy_amount": "97.0588", "fee": { "total": "1.00", - "asset": "$fiatUSD", + "asset": "$FIAT_USD", "details": [ { "name": "Sell fee", @@ -207,12 +220,12 @@ class AnchorPlatformIntegrationTest { @Test fun testRate_firm() { val rate = - rri.getRate( + rriClient.getRate( GetRateRequest.builder() .type(FIRM) .context(SEP31) - .sellAsset(fiatUSD) - .buyAsset(stellarUSDC) + .sellAsset(FIAT_USD) + .buyAsset(STELLAR_USD) .buyAmount("100") .build() ) @@ -238,7 +251,7 @@ class AnchorPlatformIntegrationTest { assertEquals(wantExpiresAt.toInstant(), gotExpiresAt) // check if rate was persisted by getting the rate with ID - val gotQuote = rri.getRate(GetRateRequest.builder().id(rate.id).build()) + val gotQuote = rriClient.getRate(GetRateRequest.builder().id(rate.id).build()) assertEquals(rate.id, gotQuote.rate.id) assertEquals("1.02", gotQuote.rate.price) @@ -253,7 +266,7 @@ class AnchorPlatformIntegrationTest { "expires_at": "$expiresAtStr", "fee": { "total": "1.00", - "asset": "$fiatUSD", + "asset": "$FIAT_USD", "details": [ { "name": "Sell fee", @@ -280,7 +293,7 @@ class AnchorPlatformIntegrationTest { val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) val result = - rfi.getFee( + rfiClient.getFee( GetFeeRequest.builder() .sendAmount("10") .sendAsset("USDC") @@ -304,27 +317,6 @@ class AnchorPlatformIntegrationTest { ) } - @Test - fun testYamlProperties() { - val tests = - mapOf( - "sep1.enabled" to "true", - "sep10.enabled" to "true", - "sep10.homeDomain" to "localhost:8080", - "sep10.signingSeed" to "SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X", - "sep24.enabled" to "true", - "sep31.enabled" to "true", - "sep38.enabled" to "true", - "sep38.quoteIntegrationEndPoint" to "http://localhost:8081", - "payment-gateway.circle.name" to "circle", - "payment-gateway.circle.enabled" to "true", - "spring.jpa.database-platform" to "org.stellar.anchor.platform.sqlite.SQLiteDialect", - "logging.level.root" to "INFO" - ) - - tests.forEach { assertEquals(it.value, platformServerContext.environment[it.key]) } - } - @Test fun testAppConfig() { val appConfig = platformServerContext.getBean(AppConfig::class.java) @@ -332,15 +324,12 @@ class AnchorPlatformIntegrationTest { assertEquals("http://localhost:8080", appConfig.hostUrl) assertEquals(listOf("en"), appConfig.languages) assertEquals("https://horizon-testnet.stellar.org", appConfig.horizonUrl) - assertEquals("assets-test.json", appConfig.assets) - assertEquals("secret", appConfig.jwtSecretKey) } @Test fun testSep1Config() { val sep1Config = platformServerContext.getBean(Sep1Config::class.java) assertEquals(true, sep1Config.isEnabled) - assertEquals("classpath:/sep1/test-stellar.toml", sep1Config.stellarFile) } @Test @@ -352,17 +341,11 @@ class AnchorPlatformIntegrationTest { assertEquals(listOf("lobstr.co", "preview.lobstr.co"), sep10Config.clientAttributionAllowList) assertEquals(900, sep10Config.authTimeout) assertEquals(86400, sep10Config.jwtTimeout) - assertEquals( - "SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X", - sep10Config.signingSeed - ) } @Test fun testSep38Config() { val sep38Config = platformServerContext.getBean(Sep38Config::class.java) - assertEquals(true, sep38Config.isEnabled) - assertEquals("http://localhost:8081", sep38Config.quoteIntegrationEndPoint) } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt index ca3cd097ce..ccc0c15544 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ApiKeyAuthIntegrationTest.kt @@ -1,11 +1,7 @@ package org.stellar.anchor.platform -import io.mockk.InternalPlatformDsl.toStr import io.mockk.clearAllMocks import io.mockk.unmockkAll -import java.io.FileReader -import java.nio.file.Files -import java.nio.file.Paths import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import okhttp3.OkHttpClient @@ -34,36 +30,19 @@ import org.stellar.anchor.util.OkHttpUtil @TestInstance(TestInstance.Lifecycle.PER_CLASS) class ApiKeyAuthIntegrationTest { companion object { - init { - System.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") - } - - private val gson = GsonUtils.getInstance() - - // platform config file - private const val ORIGINAL_PLATFORM_CONFIG_FILE = "integration-test.anchor-config.yaml" - private const val NEW_PLATFORM_CONFIG_FILE = "integration-test.anchor-config.api-key.yaml" - private const val NEW_PLATFORM_CONFIG_FILE_PATH = "src/test/resources/$NEW_PLATFORM_CONFIG_FILE" - private const val PLATFORM_SERVER_PORT = 8888 - - // Auth private const val ANCHOR_TO_PLATFORM_SECRET = "myAnchorToPlatformSecret" private const val PLATFORM_TO_ANCHOR_SECRET = "myPlatformToAnchorSecret" + private const val PLATFORM_SERVER_PORT = 8888 } - - private lateinit var platformServerContext: ConfigurableApplicationContext - private lateinit var mockAnchor: MockWebServer + private val gson = GsonUtils.getInstance() private val httpClient: OkHttpClient = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.MINUTES) .readTimeout(10, TimeUnit.MINUTES) .writeTimeout(10, TimeUnit.MINUTES) .build() - - private fun getDummyRequestBody(method: String): RequestBody? { - return if (method != "PATCH") null - else OkHttpUtil.buildJsonRequestBody(gson.toJson(mapOf("foo" to "bar"))) - } + private lateinit var platformServerContext: ConfigurableApplicationContext + private lateinit var mockAnchor: MockWebServer @BeforeAll fun setup() { @@ -72,23 +51,21 @@ class ApiKeyAuthIntegrationTest { mockAnchor.start() val mockAnchorUrl = mockAnchor.url("").toString() - // Update Platform authType and Anchor backend address - val resource = javaClass.getResource("/$ORIGINAL_PLATFORM_CONFIG_FILE") - val sourceFile = Paths.get(resource!!.toURI()).toFile() - var fileText: String = FileReader(sourceFile).readText() - fileText = fileText.replace("authType: JWT_TOKEN", "authType: API_KEY") - fileText = fileText.replace("http://localhost:8081", mockAnchorUrl) - - Files.deleteIfExists(Paths.get(NEW_PLATFORM_CONFIG_FILE_PATH)) - val newFilePath = Files.createFile(Paths.get(NEW_PLATFORM_CONFIG_FILE_PATH)) - Files.writeString(newFilePath, fileText) - // Start platform platformServerContext = AnchorPlatformServer.start( PLATFORM_SERVER_PORT, "/", - mapOf("stellar.anchor.config" to "file:${newFilePath.toStr()}"), + mapOf( + "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", + "secret.sep10.jwt_secret" to "secret", + "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF", + "platform_api.auth.type" to "API_KEY", + "secret.platform_api.auth_secret" to ANCHOR_TO_PLATFORM_SECRET, + "callback_api.base_url" to mockAnchorUrl, + "callback_api.auth.type" to "API_KEY", + "secret.callback_api.auth_secret" to PLATFORM_TO_ANCHOR_SECRET + ), true ) } @@ -97,8 +74,6 @@ class ApiKeyAuthIntegrationTest { fun teardown() { clearAllMocks() unmockkAll() - val exists = Files.deleteIfExists(Paths.get(NEW_PLATFORM_CONFIG_FILE_PATH)) - println("File $NEW_PLATFORM_CONFIG_FILE_PATH exists? $exists") } @ParameterizedTest @@ -205,4 +180,9 @@ class ApiKeyAuthIntegrationTest { MatcherAssert.assertThat(request.path, CoreMatchers.endsWith(wantEndpoint)) assertEquals("", request.body.readUtf8()) } + + private fun getDummyRequestBody(method: String): RequestBody? { + return if (method != "PATCH") null + else OkHttpUtil.buildJsonRequestBody(gson.toJson(mapOf("proposedAssetsJson" to "bar"))) + } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt index e87160798e..2c23cf481c 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt @@ -84,11 +84,12 @@ fun testHappyPath() { assertEquals(31, getTxResponse.sep) } +@Suppress("UNCHECKED_CAST") fun testHealth() { val response = platformApiClient.health(listOf("all")) assertEquals(5, response.size) + assertEquals(1.0, response["number_of_checks"]) assertNotNull(response["checks"]) - assertEquals(0.0, response["number_of_checks"]) assertNotNull(response["started_at"]) assertNotNull(response["elapsed_time_ms"]) assertNotNull(response["number_of_checks"]) diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestFeeIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestFeeIntegrationTest.kt index 811902d24b..f1e475c15b 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestFeeIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestFeeIntegrationTest.kt @@ -181,6 +181,10 @@ class RestFeeIntegrationTest { validateRequest(422, null, BadRequestException("Bad Request")) // 500 with body - validateRequest(500, """{"error": "foo 500"}""", ServerErrorException("internal server error")) + validateRequest( + 500, + """{"error": "proposedAssetsJson 500"}""", + ServerErrorException("internal server error") + ) } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt index 23f56412db..c15bb8f97e 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/RestRateIntegrationTest.kt @@ -310,8 +310,8 @@ class RestRateIntegrationTest { // 400 with body validateRequest( 400, - """{"error": "foo 400"}""", - BadRequestException("foo 400"), + """{"error": "proposedAssetsJson 400"}""", + BadRequestException("proposedAssetsJson 400"), INDICATIVE_PRICES ) @@ -321,8 +321,8 @@ class RestRateIntegrationTest { // 404 with body validateRequest( 404, - """{"error": "foo 404"}""", - NotFoundException("foo 404"), + """{"error": "proposedAssetsJson 404"}""", + NotFoundException("proposedAssetsJson 404"), INDICATIVE_PRICES ) @@ -332,15 +332,15 @@ class RestRateIntegrationTest { // 422 with body validateRequest( 422, - """{"error": "foo 422"}""", - BadRequestException("foo 422"), + """{"error": "proposedAssetsJson 422"}""", + BadRequestException("proposedAssetsJson 422"), INDICATIVE_PRICES ) // 500 validateRequest( 500, - """{"error": "foo 500"}""", + """{"error": "proposedAssetsJson 500"}""", ServerErrorException("internal server error"), INDICATIVE_PRICES ) @@ -440,7 +440,7 @@ class RestRateIntegrationTest { "id": "my-id", "price": "1", "total_price": "1.01", - "expires_at": "foo bar", + "expires_at": "proposedAssetsJson bar", "fee": { "total": "1.00", "asset": "iso4217:USD" diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep38Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep38Tests.kt index 108299c349..22dcff13a5 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep38Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep38Tests.kt @@ -5,7 +5,6 @@ import java.time.format.DateTimeFormatter import kotlin.test.assertEquals import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.SepException -import org.stellar.anchor.api.sep.sep38.GetPriceResponse import org.stellar.anchor.api.sep.sep38.Sep38Context.* import org.stellar.anchor.util.Sep1Helper @@ -74,15 +73,13 @@ fun sep38TestHappyPath() { fun testSellOverAssetLimit() { printRequest("Calling GET /price") - var price: GetPriceResponse? assertThrows { - price = - sep38.getPrice( - "iso4217:USD", - "10000000000", - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - SEP31 - ) + sep38.getPrice( + "iso4217:USD", + "10000000000", + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + SEP31 + ) } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt index 286656ce0b..b6b18f83ee 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt @@ -42,7 +42,11 @@ fun main(args: Array) { // Start sep server if enabled. if (cmd.hasOption("sep-server") || cmd.hasOption("all")) { - ServiceRunner.startSepServer() + ServiceRunner.startSepServer( + ServiceRunner.DEFAULT_SEP_SERVER_PORT, + ServiceRunner.DEFAULT_CONTEXTPATH, + null + ) } // Start anchor reference server if enabled. diff --git a/integration-tests/src/test/resources/anchor-config-integration.yaml b/integration-tests/src/test/resources/anchor-config-integration.yaml new file mode 100644 index 0000000000..c86b310334 --- /dev/null +++ b/integration-tests/src/test/resources/anchor-config-integration.yaml @@ -0,0 +1,309 @@ +####################################################################### +## Anchor Platform - Integration Test Config +####################################################################### + +########################### +## System Configuration +########################## +version: 1 + +data: + type: h2 + options: + url: jdbc:h2:mem:test + +# The anchor server callback API endpoint for customer, fee, quote, and unique url integration. +callback_api: + base_url: https://localhost:8080 + auth_type: JWT_TOKEN + +stellar_network: + # Use `TESTNET` or `PUBNET` network + network: TESTNET + horizon_url: https://horizon-testnet.stellar.org + network_passphrase: 'Test SDF Network ; September 2015' + +events: + enabled: true + publisher_type: kafka + options: + bootstrap_server: localhost:29092 + +payment_observer: + enabled: true + +logging: + level: INFO + +metrics: + enabled: true + +language: en + +####################### +## SEP Configuration +####################### +sep1: + enabled: true + stellar_file: | + ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ] + VERSION = "0.1.0" + SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + + WEB_AUTH_ENDPOINT = "http://localhost:8080/auth" + KYC_SERVER = "http://localhost:8080/sep12" + TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" + DIRECT_PAYMENT_SERVER = "http://localhost:8080/sep31" + ANCHOR_QUOTE_SERVER = "http://localhost:8080/sep38" + + [[CURRENCIES]] + code = "SRT" + issuer = "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B" + status = "test" + is_asset_anchored = false + anchor_asset_type = "other" + desc = "A fake anchored asset to use with this example anchor server." + + [DOCUMENTATION] + ORG_NAME = "Stellar Development Foundation" + ORG_URL = "https://stellar.org" + ORG_DESCRIPTION = "SEP 24 reference server." + ORG_KEYBASE = "stellar.public" + ORG_TWITTER = "StellarOrg" + ORG_GITHUB = "stellar" + +sep10: + enabled: true + +sep12: + enabled: true + +sep24: + enabled: true + +sep31: + enabled: true + +sep38: + enabled: true + +######################### +## Assets Configuration +######################### +assets: + type: json + value: | + { + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 2, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving USD" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving USD" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "stellar", + "code": "JPYC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 4, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving JPY" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving JPY" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": false, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "iso4217", + "code": "USD", + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "sep38": { + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "country_codes": ["USA"], + "decimals": 4, + "sell_delivery_methods": [ + { + "name": "WIRE", + "description": "Send USD directly to the Anchor's bank account." + } + ], + "buy_delivery_methods": [ + { + "name": "WIRE", + "description": "Have USD sent directly to your bank account." + } + ] + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38_enabled": true + } + ] + } diff --git a/integration-tests/src/test/resources/integration-test.anchor-config.yaml b/integration-tests/src/test/resources/integration-test.anchor-config.yaml index 5686c27fcb..51913afa7c 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -1,172 +1,271 @@ -stellar: - anchor: - # Configure the application from this file after it is loaded in memory - # If `config` is in-memory, the yaml file contains all settings for the server. - config: in-memory - app-config: - type: config-spring-property # Activate [config-spring-property] module - settings: app-config # The location of the configuration data - data-access: - type: data-spring-jdbc # Activate [config-spring-jdbc] module. - settings: data-spring-jdbc-sqlite # The location of the configuration data in this file. - logging: - type: logging-logback - settings: logging-logback-settings +version: 1 -# -# Application settings -# -app-config: - # general - app: - stellarNetworkPassphrase: Test SDF Network ; September 2015 - hostUrl: http://localhost:8080 - languages: en - horizonUrl: https://horizon-testnet.stellar.org - assets: assets-test.json - jwtSecretKey: secret +logging: + level: INFO + stellar_level: DEBUG - integration-auth: - authType: JWT_TOKEN - platformToAnchorSecret: myPlatformToAnchorSecret - anchorToPlatformSecret: myAnchorToPlatformSecret - expirationMilliseconds: 30000 +sep1: + enabled: true + toml: + type: string + value: | + ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ] + VERSION = "0.1.0" + SIGNING_KEY = "GBDYDBJKQBJK4GY4V7FAONSFF2IBJSKNTBYJ65F5KCGBY2BIGPGGLJOH" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + + WEB_AUTH_ENDPOINT = "http://localhost:8080/auth" + KYC_SERVER = "http://localhost:8080/sep12" + TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" + DIRECT_PAYMENT_SERVER = "http://localhost:8080/sep31" + ANCHOR_QUOTE_SERVER = "http://localhost:8080/sep38" + + [[CURRENCIES]] + code = "SRT" + issuer = "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B" + status = "test" + is_asset_anchored = false + anchor_asset_type = "other" + desc = "A fake anchored asset to use with this example anchor server." + + [DOCUMENTATION] + ORG_NAME = "Stellar Development Foundation" + ORG_URL = "https://stellar.org" + ORG_DESCRIPTION = "SEP 24 reference server." + ORG_KEYBASE = "stellar.public" + ORG_TWITTER = "StellarOrg" + ORG_GITHUB = "stellar" - # sep-1 - sep1: - enabled: true - stellarFile: classpath:/sep1/test-stellar.toml +sep10: + enabled: true - # sep-10 - sep10: - enabled: true - homeDomain: localhost:8080 - clientAttributionRequired: false - clientAttributionAllowList: lobstr.co,preview.lobstr.co # use this if we want to white list - # clientAttributionDenyList: # use this if we want to black list. - authTimeout: 900 - jwtTimeout: 86400 - signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X +sep12: + enabled: true - # sep-12 - sep12: - enabled: true - customerIntegrationEndpoint: http://localhost:8081 +sep24: + enabled: true - # sep-24 - sep24: - enabled: true - interactiveJwtExpiration: 3600 - interactiveUrl: http://localhost:8081/sep24/interactive +sep31: + enabled: true - # sep-31 - sep31: - enabled: true - feeIntegrationEndPoint: http://localhost:8081 - uniqueAddressIntegrationEndPoint: http://localhost:8081 - # - # paymentType: used to determine how amount_in is calculated from amount in the POST /transaction call - # Possible values: STRICT_SEND or STRICT_RECEIVE. default=STRICT_SEND - # STRICT_SEND: amount_in = amount - # STRICT_RECEIVE: amount_in = amount + fee - paymentType: STRICT_SEND - # - # depositInfoGeneratorType: used to choose how the SEP-31 deposit information will be generated, which includes the - # deposit address, memo and memo type. - # Possible values: - # self: the memo and memo type are generated in the local code, and the distribution account is used for the deposit address. - # circle: the memo and memo type are generated through Circle API, as well as the deposit address. - depositInfoGeneratorType: self # self or circle +sep38: + enabled: true - # sep-38 - sep38: - enabled: true - quoteIntegrationEndPoint: http://localhost:8081 - - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: secret # circle API key - - payment-gateway: - # - # Payment Circle configurations - # - circle: - name: "circle" - enabled: true - - # - # Payment Stellar configurations - # - stellar: - enabled: false - name: "stellar" - horizonUrl: https://horizon-testnet.stellar.org - secretKey: secret # stellar account secret key - - circle-payment-observer: - enabled: true - horizonUrl: https://horizon-testnet.stellar.org - stellarNetwork: TESTNET # TESTNET or PUBLIC - trackedWallet: all - - event: - # If enabled, publish Events to a queue (publisherType) - # publisherType - the type of queue to use for event publishing - # currently supported publisherType values: kafka, sqs - enabled: true - publisherType: kafka - - kafka.publisher: - # kafkaBootstrapServer - the Kafka server used to bootstrap setup - # If useSingleQueue, all events are published to a single queue - # (specified in eventTypeToQueue.all) - # eventTypeToQueue - a map of the event type to the queue name messages are published to - bootstrapServer: localhost:29092 - useSingleQueue: false - eventTypeToQueue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error - - - -# -# Spring Data JDBC settings -# -data-spring-jdbc-sqlite: - spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect - spring.jpa.hibernate.ddl-auto: update - spring.jpa.generate-ddl: true - spring.jpa.hibernate.show_sql: false - spring.datasource.url: jdbc:sqlite:anchor-proxy.db - spring.datasource.driver-class-name: org.sqlite.JDBC - spring.datasource.max-active: 2 - spring.datasource.initial-size: 2 - spring.datasource.username: admin - spring.datasource.password: admin - spring.mvc.converters.preferred-json-mapper: gson - spring.liquibase.enabled: false - -data-spring-jdbc-h2: - spring.datasource.url: jdbc:h2:mem:test - spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.H2Dialect - spring.liquibase.enabled: false - -# -# Spring framework configurations -# -spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: DEBUG - server: - servlet: - context-path: "/" - mvc: - async.request-timeout: 6000 \ No newline at end of file +assets: + type: json + value: | + { + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 2, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving USD" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving USD" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "stellar", + "code": "JPYC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 4, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving JPY" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving JPY" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": false, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "iso4217", + "code": "USD", + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "sep38": { + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "country_codes": ["USA"], + "decimals": 4, + "sell_delivery_methods": [ + { + "name": "WIRE", + "description": "Send USD directly to the Anchor's bank account." + } + ], + "buy_delivery_methods": [ + { + "name": "WIRE", + "description": "Have USD sent directly to your bank account." + } + ] + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38_enabled": true + } + ] + } + + diff --git a/integration-tests/src/test/resources/integration.env b/integration-tests/src/test/resources/integration.env new file mode 100644 index 0000000000..c2b91186df --- /dev/null +++ b/integration-tests/src/test/resources/integration.env @@ -0,0 +1,4 @@ +JWT_SECRET=secret +SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X +PLATFORM_TO_ANCHOR_SECRET=myPlatformToAnchorSecret +ANCHOR_TO_PLATFORM_SECRET=myAnchorToPlatformSecret diff --git a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java index ff040bc33e..831f4e8e2f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -12,10 +12,8 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.stellar.anchor.platform.configurator.DataAccessConfigurator; -import org.stellar.anchor.platform.configurator.PlatformAppConfigurator; -import org.stellar.anchor.platform.configurator.PropertiesReader; -import org.stellar.anchor.platform.configurator.SpringFrameworkConfigurator; +import org.stellar.anchor.platform.configurator.ConfigEnvironment; +import org.stellar.anchor.platform.configurator.ConfigManager; @Profile("default") @SpringBootApplication @@ -23,7 +21,6 @@ @EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) @EnableConfigurationProperties public class AnchorPlatformServer implements WebMvcConfigurer { - public static ConfigurableApplicationContext start( int port, String contextPath, Map environment, boolean disableMetrics) { SpringApplicationBuilder builder = @@ -34,8 +31,6 @@ public static ConfigurableApplicationContext start( // disableMetrics param - // https://github.com/stellar/java-stellar-anchor-sdk/issues/297 "spring.mvc.converters.preferred-json-mapper=gson", - // this allows a developer to use a .env file for local development - "spring.config.import=optional:classpath:example.env[.properties]", String.format("server.port=%d", port), String.format("server.contextPath=%s", contextPath)); @@ -45,19 +40,15 @@ public static ConfigurableApplicationContext start( "management.server.port=8082"); } if (environment != null) { - builder.properties(environment); + for (String name : environment.keySet()) { + System.setProperty(name, String.valueOf(environment.get(name))); + } + ConfigEnvironment.rebuild(); } SpringApplication springApplication = builder.build(); - // Reads the configuration from sources, such as yaml - springApplication.addInitializers(new PropertiesReader()); - // Configure SEPs - springApplication.addInitializers(new PlatformAppConfigurator()); - // Configure databases - springApplication.addInitializers(new DataAccessConfigurator()); - // Configure spring framework - springApplication.addInitializers(new SpringFrameworkConfigurator()); + springApplication.addInitializers(ConfigManager.getInstance()); return springApplication.run(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java b/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java similarity index 55% rename from platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java index eda5b4a122..42509b8d04 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/IntegrationConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java @@ -11,31 +11,15 @@ import org.stellar.anchor.api.callback.UniqueAddressIntegration; import org.stellar.anchor.auth.AuthHelper; import org.stellar.anchor.auth.JwtService; -import org.stellar.anchor.config.*; +import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.platform.callback.RestCustomerIntegration; import org.stellar.anchor.platform.callback.RestFeeIntegration; import org.stellar.anchor.platform.callback.RestRateIntegration; import org.stellar.anchor.platform.callback.RestUniqueAddressIntegration; +import org.stellar.anchor.platform.config.CallbackApiConfig; @Configuration -public class IntegrationConfig { - @Bean - AuthHelper authHelper(AppConfig appConfig, IntegrationAuthConfig integrationAuthConfig) { - String authSecret = integrationAuthConfig.getPlatformToAnchorSecret(); - switch (integrationAuthConfig.getAuthType()) { - case JWT_TOKEN: - return AuthHelper.forJwtToken( - new JwtService(authSecret), - integrationAuthConfig.getExpirationMilliseconds(), - appConfig.getHostUrl()); - - case API_KEY: - return AuthHelper.forApiKey(authSecret); - - default: - return AuthHelper.forNone(); - } - } +public class CallbackApiBeans { @Bean OkHttpClient httpClient() { @@ -49,29 +33,66 @@ OkHttpClient httpClient() { @Bean UniqueAddressIntegration uniqueAddressIntegration( - Sep31Config sep31Config, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { + AppConfig appConfig, + CallbackApiConfig callbackApiConfig, + OkHttpClient httpClient, + Gson gson) { + AuthHelper authHelper = buildAuthHelper(appConfig, callbackApiConfig); return new RestUniqueAddressIntegration( - sep31Config.getUniqueAddressIntegrationEndPoint(), httpClient, authHelper, gson); + callbackApiConfig.getBaseUrl(), httpClient, authHelper, gson); } @Bean CustomerIntegration customerIntegration( - Sep12Config sep12Config, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { + AppConfig appConfig, + CallbackApiConfig callbackApiConfig, + OkHttpClient httpClient, + Gson gson) { return new RestCustomerIntegration( - sep12Config.getCustomerIntegrationEndPoint(), httpClient, authHelper, gson); + callbackApiConfig.getBaseUrl(), + httpClient, + buildAuthHelper(appConfig, callbackApiConfig), + gson); } @Bean RateIntegration rateIntegration( - Sep38Config sep38Config, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { + AppConfig appConfig, + CallbackApiConfig callbackApiConfig, + OkHttpClient httpClient, + Gson gson) { return new RestRateIntegration( - sep38Config.getQuoteIntegrationEndPoint(), httpClient, authHelper, gson); + callbackApiConfig.getBaseUrl(), + httpClient, + buildAuthHelper(appConfig, callbackApiConfig), + gson); } @Bean FeeIntegration feeIntegration( - Sep31Config sep31Config, OkHttpClient httpClient, AuthHelper authHelper, Gson gson) { + AppConfig appConfig, + CallbackApiConfig callbackApiConfig, + OkHttpClient httpClient, + Gson gson) { return new RestFeeIntegration( - sep31Config.getFeeIntegrationEndPoint(), httpClient, authHelper, gson); + callbackApiConfig.getBaseUrl(), + httpClient, + buildAuthHelper(appConfig, callbackApiConfig), + gson); + } + + AuthHelper buildAuthHelper(AppConfig appConfig, CallbackApiConfig callbackApiConfig) { + String authSecret = callbackApiConfig.getAuth().getSecret(); + switch (callbackApiConfig.getAuth().getType()) { + case JWT_TOKEN: + return AuthHelper.forJwtToken( + new JwtService(authSecret), + Long.parseLong(callbackApiConfig.getAuth().getExpirationMilliseconds()), + appConfig.getHostUrl()); + case API_KEY: + return AuthHelper.forApiKey(authSecret); + default: + return AuthHelper.forNone(); + } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java new file mode 100644 index 0000000000..276fff29d4 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java @@ -0,0 +1,125 @@ +package org.stellar.anchor.platform; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.config.*; +import org.stellar.anchor.platform.config.*; +import org.stellar.anchor.platform.configurator.ConfigManager; +import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; + +@Configuration +public class ConfigManagementBeans { + @Bean + ConfigManager configManager() { + return ConfigManager.getInstance(); + } + + @Bean + @ConfigurationProperties(prefix = "") + AppConfig appConfig() { + return new PropertyAppConfig(); + } + + @Bean + @ConfigurationProperties(prefix = "assets") + AssetsConfig assetsConfig() { + return new PropertyAssetsConfig(); + } + + @Bean + @ConfigurationProperties(prefix = "callback-api") + CallbackApiConfig callbackApiConfig(PropertySecretConfig secretConfig) { + return new CallbackApiConfig(secretConfig); + } + + @Bean + @ConfigurationProperties(prefix = "platform-api") + PlatformApiConfig platformApiConfig(PropertySecretConfig secretConfig) { + return new PlatformApiConfig(secretConfig); + } + + @Bean + @ConfigurationProperties(prefix = "sep1") + Sep1Config sep1Config() { + return new PropertySep1Config(); + } + + @Bean + @ConfigurationProperties(prefix = "sep10") + Sep10Config sep10Config(SecretConfig secretConfig, JwtService jwtService) { + return new PropertySep10Config(secretConfig, jwtService); + } + + @Bean + @ConfigurationProperties(prefix = "sep12") + Sep12Config sep12Config(CallbackApiConfig callbackApiConfig) { + return new PropertySep12Config(callbackApiConfig); + } + + @Bean + @ConfigurationProperties(prefix = "sep24") + Sep24Config sep24Config() { + return new PropertySep24Config(); + } + + @Bean + @ConfigurationProperties(prefix = "sep31") + Sep31Config sep31Config(CircleConfig circleConfig, CallbackApiConfig callbackApiConfig) { + return new PropertySep31Config(circleConfig, callbackApiConfig); + } + + @Bean + @ConfigurationProperties(prefix = "sep38") + Sep38Config sep38Config() { + return new PropertySep38Config(); + } + + @Bean + @ConfigurationProperties(prefix = "circle") + CircleConfig circleConfig() { + return new PropertyCircleConfig(); + } + + @Bean + @ConfigurationProperties + PropertySecretConfig secretConfig() { + return new PropertySecretConfig(); + } + + @Bean + @ConfigurationProperties(prefix = "payment-observer") + PaymentObserverConfig paymentObserverConfig() { + return new PropertyPaymentObserverConfig(); + } + + @Bean + CirclePaymentConfig circlePaymentConfig() { + return new CirclePaymentConfig(); + } + + @Bean + @ConfigurationProperties(prefix = "events") + EventConfig eventConfig(PublisherConfig publisherConfig) { + return new PropertyEventConfig(publisherConfig); + } + + @Bean + @ConfigurationProperties(prefix = "events.options") + PublisherConfig publisherConfig(EventTypeToQueueConfig eventTypeToQueueConfig) { + return new PropertyPublisherConfig(eventTypeToQueueConfig); + } + + @Bean + @ConfigurationProperties(prefix = "events.options.event-type-to-queue") + EventTypeToQueueConfig eventTypeToQueueConfig() { + return new PropertyEventTypeToQueueConfig(); + } + + @Bean + @ConfigurationProperties(prefix = "metrics") + MetricConfig metricConfig() { + return new PropertyMetricConfig(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java deleted file mode 100644 index 3fa1be0dd6..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementConfig.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.stellar.anchor.platform; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.stellar.anchor.config.*; -import org.stellar.anchor.platform.config.*; -import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; - -@Configuration -public class ConfigManagementConfig { - @Bean - @ConfigurationProperties(prefix = "app") - AppConfig appConfig() { - return new PropertyAppConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "integration-auth") - IntegrationAuthConfig integrationAuthConfig() { - return new PropertyIntegrationAuthConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "sep1") - Sep1Config sep1Config() { - return new PropertySep1Config(); - } - - @Bean - @ConfigurationProperties(prefix = "sep10") - Sep10Config sep10Config() { - return new PropertySep10Config(); - } - - @Bean - @ConfigurationProperties(prefix = "sep12") - Sep12Config sep12Config() { - return new PropertySep12Config(); - } - - @Bean - @ConfigurationProperties(prefix = "sep24") - Sep24Config sep24Config() { - return new PropertySep24Config(); - } - - @Bean - @ConfigurationProperties(prefix = "sep31") - Sep31Config sep31Config(CircleConfig circleConfig) { - return new PropertySep31Config(circleConfig); - } - - @Bean - @ConfigurationProperties(prefix = "sep38") - Sep38Config sep38Config() { - return new PropertySep38Config(); - } - - @Bean - @ConfigurationProperties(prefix = "circle") - CircleConfig circleConfig() { - return new PropertyCircleConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "circle-payment-observer") - CirclePaymentObserverConfig circlePaymentObserverConfig() { - return new PropertyCirclePaymentObserverConfig(); - } - - @Bean - CirclePaymentConfig circlePaymentConfig() { - return new CirclePaymentConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "event") - EventConfig eventConfig(KafkaConfig kafkaConfig, SqsConfig sqsConfig) { - return new PropertyEventConfig(kafkaConfig, sqsConfig); - } - - @Bean - @ConfigurationProperties(prefix = "kafka.publisher") - KafkaConfig kafkaConfig() { - return new PropertyKafkaConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "sqs.publisher") - SqsConfig sqsConfig() { - return new PropertySqsConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "metrics-service") - MetricConfig metricConfig() { - return new PropertyMetricConfig(); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/EventsConfig.java b/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java similarity index 67% rename from platform/src/main/java/org/stellar/anchor/platform/EventsConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java index fbc6494cc6..0fae3429ef 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/EventsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java @@ -5,19 +5,23 @@ import org.stellar.anchor.config.*; import org.stellar.anchor.event.EventPublishService; import org.stellar.anchor.event.KafkaEventService; +import org.stellar.anchor.event.NoopEventService; import org.stellar.anchor.event.SqsEventService; @Configuration -public class EventsConfig { +public class EventsBeans { @Bean public EventPublishService eventService( - EventConfig eventConfig, KafkaConfig kafkaConfig, SqsConfig sqsConfig) { + EventConfig eventConfig, PublisherConfig publisherConfig) { + if (!eventConfig.isEnabled()) { + return new NoopEventService(); + } // TODO handle when event publishing is disabled switch (eventConfig.getPublisherType()) { case "kafka": - return new KafkaEventService(kafkaConfig); + return new KafkaEventService(publisherConfig); case "sqs": - return new SqsEventService(sqsConfig); + return new SqsEventService(publisherConfig); default: throw new RuntimeException( String.format("Invalid event publisher: %s", eventConfig.getPublisherType())); diff --git a/platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java b/platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java similarity index 91% rename from platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java index bf5b1da4a6..e6babd1105 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/HelperConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java @@ -6,7 +6,7 @@ import org.stellar.anchor.util.GsonUtils; @Configuration -public class HelperConfig { +public class HelperBeans { @Bean public Gson gson() { return GsonUtils.builder().create(); diff --git a/platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java b/platform/src/main/java/org/stellar/anchor/platform/MetricsBeans.java similarity index 95% rename from platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/MetricsBeans.java index 3236b6a837..3f60e90302 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/MetricsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/MetricsBeans.java @@ -7,7 +7,7 @@ import org.stellar.anchor.platform.service.MetricEmitterService; @Configuration -public class MetricsConfig { +public class MetricsBeans { @Bean public MetricEmitterService metricService( MetricConfig metricConfig, JdbcSep31TransactionRepo sep31TransactionRepo) { diff --git a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java b/platform/src/main/java/org/stellar/anchor/platform/PaymentBeans.java similarity index 96% rename from platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/PaymentBeans.java index 0f51f7ffd5..06fd304531 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PaymentConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/PaymentBeans.java @@ -11,7 +11,7 @@ import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.config.AppConfig; -import org.stellar.anchor.config.CirclePaymentObserverConfig; +import org.stellar.anchor.config.PaymentObserverConfig; import org.stellar.anchor.horizon.Horizon; import org.stellar.anchor.platform.data.PaymentObservingAccountRepo; import org.stellar.anchor.platform.payment.observer.PaymentListener; @@ -22,7 +22,7 @@ import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentStreamerCursorStore; @Configuration -public class PaymentConfig { +public class PaymentBeans { @Bean @Profile("stellar-observer") @SneakyThrows @@ -95,7 +95,7 @@ public PaymentObservingAccountStore observingAccountStore(PaymentObservingAccoun @Bean public CirclePaymentObserverService circlePaymentObserverService( OkHttpClient httpClient, - CirclePaymentObserverConfig circlePaymentObserverConfig, + PaymentObserverConfig circlePaymentObserverConfig, Horizon horizon, List paymentListeners) { return new CirclePaymentObserverService( diff --git a/platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java b/platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java new file mode 100644 index 0000000000..78968c3430 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java @@ -0,0 +1,48 @@ +package org.stellar.anchor.platform; + +import javax.servlet.Filter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.filter.ApiKeyFilter; +import org.stellar.anchor.filter.JwtTokenFilter; +import org.stellar.anchor.filter.NoneFilter; +import org.stellar.anchor.platform.config.PlatformApiConfig; + +@Configuration +public class PlatformApiBeans { + /** + * Register anchor-to-platform token filter. + * + * @return Spring Filter Registration Bean + */ + @Bean + public FilterRegistrationBean anchorToPlatformTokenFilter( + PlatformApiConfig platformApiConfig) { + Filter anchorToPlatformFilter; + String authSecret = platformApiConfig.getAuth().getSecret(); + switch (platformApiConfig.getAuth().getType()) { + case JWT_TOKEN: + JwtService jwtService = new JwtService(authSecret); + anchorToPlatformFilter = new JwtTokenFilter(jwtService); + break; + + case API_KEY: + anchorToPlatformFilter = new ApiKeyFilter(authSecret); + break; + + default: + anchorToPlatformFilter = new NoneFilter(); + break; + } + + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(anchorToPlatformFilter); + registrationBean.addUrlPatterns("/transactions/*"); + registrationBean.addUrlPatterns("/transactions"); + registrationBean.addUrlPatterns("/exchange/quotes/*"); + registrationBean.addUrlPatterns("/exchange/quotes"); + return registrationBean; + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java b/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerBeans.java similarity index 94% rename from platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/RequestLoggerBeans.java index 39d7735087..fc67d1ac4a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerBeans.java @@ -7,7 +7,7 @@ import org.stellar.anchor.platform.utils.RequestLoggerFilter; @Configuration -public class RequestLoggerConfig { +public class RequestLoggerBeans { @Bean public FilterRegistrationBean requestLoggerFilter() { FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); diff --git a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java similarity index 73% rename from platform/src/main/java/org/stellar/anchor/platform/SepConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java index 920fcc4613..6deb30af39 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform; import com.google.gson.Gson; +import java.io.IOException; import javax.servlet.Filter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; @@ -9,19 +10,21 @@ import org.stellar.anchor.api.callback.FeeIntegration; import org.stellar.anchor.api.callback.RateIntegration; import org.stellar.anchor.api.callback.UniqueAddressIntegration; +import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.config.*; import org.stellar.anchor.event.EventPublishService; -import org.stellar.anchor.filter.ApiKeyFilter; import org.stellar.anchor.filter.JwtTokenFilter; -import org.stellar.anchor.filter.NoneFilter; import org.stellar.anchor.horizon.Horizon; import org.stellar.anchor.platform.data.*; import org.stellar.anchor.platform.payment.config.CirclePaymentConfig; import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentService; import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager; -import org.stellar.anchor.platform.service.*; +import org.stellar.anchor.platform.service.PropertyAssetsService; +import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorApi; +import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorCircle; +import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf; import org.stellar.anchor.sep1.Sep1Service; import org.stellar.anchor.sep10.Sep10Service; import org.stellar.anchor.sep12.Sep12Service; @@ -32,12 +35,11 @@ import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep38.Sep38QuoteStore; import org.stellar.anchor.sep38.Sep38Service; -import org.stellar.anchor.util.ResourceReader; /** SEP configurations */ @Configuration -public class SepConfig { - public SepConfig() {} +public class SepServiceBeans { + public SepServiceBeans() {} /** * Used by SEP-10 authentication service. @@ -45,8 +47,8 @@ public SepConfig() {} * @return the jwt service used by SEP-10. */ @Bean - public JwtService jwtService(AppConfig appConfig) { - return new JwtService(appConfig); + public JwtService jwtService(SecretConfig secretConfig) { + return new JwtService(secretConfig); } /** @@ -69,43 +71,9 @@ public FilterRegistrationBean sep10TokenFilter(JwtService jwtService) { return registrationBean; } - /** - * Register anchor-to-platform token filter. - * - * @return Spring Filter Registration Bean - */ @Bean - public FilterRegistrationBean anchorToPlatformTokenFilter( - IntegrationAuthConfig integrationAuthConfig) { - Filter anchorToPlatformFilter; - String authSecret = integrationAuthConfig.getAnchorToPlatformSecret(); - switch (integrationAuthConfig.getAuthType()) { - case JWT_TOKEN: - JwtService jwtService = new JwtService(authSecret); - anchorToPlatformFilter = new JwtTokenFilter(jwtService); - break; - - case API_KEY: - anchorToPlatformFilter = new ApiKeyFilter(authSecret); - break; - - default: - anchorToPlatformFilter = new NoneFilter(); - break; - } - - FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - registrationBean.setFilter(anchorToPlatformFilter); - registrationBean.addUrlPatterns("/transactions/*"); - registrationBean.addUrlPatterns("/transactions"); - registrationBean.addUrlPatterns("/exchange/quotes/*"); - registrationBean.addUrlPatterns("/exchange/quotes"); - return registrationBean; - } - - @Bean - AssetService assetService(AppConfig appConfig, ResourceReader resourceReader) { - return new ResourceReaderAssetService(appConfig.getAssets(), resourceReader); + AssetService assetService(AssetsConfig assetsConfig) throws InvalidConfigException { + return new PropertyAssetsService(assetsConfig); } @Bean @@ -114,19 +82,18 @@ public Horizon horizon(AppConfig appConfig) { } @Bean - public ResourceReader resourceReader() { - return new SpringResourceReader(); - } - - @Bean - Sep1Service sep1Service(Sep1Config sep1Config, ResourceReader resourceReader) { - return new Sep1Service(sep1Config, resourceReader); + Sep1Service sep1Service(Sep1Config sep1Config) throws IOException { + return new Sep1Service(sep1Config); } @Bean Sep10Service sep10Service( - AppConfig appConfig, Sep10Config sep10Config, Horizon horizon, JwtService jwtService) { - return new Sep10Service(appConfig, sep10Config, horizon, jwtService); + AppConfig appConfig, + SecretConfig secretConfig, + Sep10Config sep10Config, + Horizon horizon, + JwtService jwtService) { + return new Sep10Service(appConfig, secretConfig, sep10Config, horizon, jwtService); } @Bean diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java index 70748908cf..35506c0ce5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java @@ -13,10 +13,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.stellar.anchor.platform.configurator.DataAccessConfigurator; -import org.stellar.anchor.platform.configurator.PlatformAppConfigurator; -import org.stellar.anchor.platform.configurator.PropertiesReader; -import org.stellar.anchor.platform.configurator.SpringFrameworkConfigurator; +import org.stellar.anchor.platform.configurator.ConfigManager; @Profile("stellar-observer") @SpringBootApplication @@ -44,15 +41,7 @@ public static ConfigurableApplicationContext start(Map environme SpringApplication springApplication = builder.build(); - // Reads the configuration from sources, such as yaml - springApplication.addInitializers(new PropertiesReader()); - // Configure SEPs - springApplication.addInitializers(new PlatformAppConfigurator()); - // Configure databases - springApplication.addInitializers(new DataAccessConfigurator()); - // Configure spring framework - springApplication.addInitializers(new SpringFrameworkConfigurator()); - + springApplication.addInitializers(ConfigManager.getInstance()); return springApplication.run(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java new file mode 100644 index 0000000000..fd0425bcb2 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java @@ -0,0 +1,55 @@ +package org.stellar.anchor.platform.config; + +import java.util.List; +import lombok.Data; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.stellar.anchor.auth.AuthInfo; +import org.stellar.anchor.auth.AuthType; + +@Data +public class CallbackApiConfig implements Validator { + private static long MIN_EXPIRATION = 5000; + + String baseUrl; + + AuthInfo auth; + PropertySecretConfig secretConfig; + + public CallbackApiConfig(PropertySecretConfig secretConfig) { + this.secretConfig = secretConfig; + } + + public void setAuth(AuthInfo auth) { + auth.setSecret(secretConfig.getCallbackApiSecret()); + this.auth = auth; + } + + @Override + public boolean supports(Class clazz) { + return CallbackApiConfig.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + CallbackApiConfig config = (CallbackApiConfig) target; + + if (List.of(AuthType.API_KEY, AuthType.JWT_TOKEN).contains(config.getAuth().getType())) { + if (config.getAuth().getSecret() == null) { + errors.rejectValue( + "platformToAnchorSecret", + "empty-platformToAnchorSecret", + "Please set environment variable [SECRET.CALLBACK_API.AUTH_SECRET] for auth type:" + + config.getAuth().getType()); + } + + if (AuthType.JWT_TOKEN == config.getAuth().getType() + && (Long.parseLong(config.getAuth().getExpirationMilliseconds()) < MIN_EXPIRATION)) { + errors.rejectValue( + "expirationMilliseconds", + "min-expirationMilliseconds", + "expirationMilliseconds cannot be lower than " + MIN_EXPIRATION); + } + } + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PlatformApiConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PlatformApiConfig.java new file mode 100644 index 0000000000..fa918667f7 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PlatformApiConfig.java @@ -0,0 +1,44 @@ +package org.stellar.anchor.platform.config; + +import static org.stellar.anchor.auth.AuthType.API_KEY; +import static org.stellar.anchor.auth.AuthType.JWT_TOKEN; + +import java.util.List; +import lombok.Data; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.stellar.anchor.auth.AuthInfo; + +@Data +public class PlatformApiConfig implements Validator { + AuthInfo auth; + PropertySecretConfig secretConfig; + + public PlatformApiConfig(PropertySecretConfig secretConfig) { + this.secretConfig = secretConfig; + } + + public void setAuth(AuthInfo auth) { + auth.setSecret(secretConfig.getPlatformApiSecret()); + this.auth = auth; + } + + @Override + public boolean supports(Class clazz) { + return PlatformApiConfig.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + PlatformApiConfig config = (PlatformApiConfig) target; + if (List.of(API_KEY, JWT_TOKEN).contains(config.getAuth().getType())) { + if (config.getAuth().getSecret() == null) { + errors.rejectValue( + "secret", + "empty-secret", + "Please set environment variable [platform_api.auth.secret] for auth type:" + + config.getAuth().getType()); + } + } + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAppConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAppConfig.java index fdcfe77682..e4e1ac312d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAppConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAppConfig.java @@ -2,21 +2,24 @@ import java.util.List; import lombok.Data; +import org.springframework.beans.factory.annotation.Value; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.stellar.anchor.config.AppConfig; -import org.stellar.anchor.platform.service.SpringResourceReader; -import org.stellar.anchor.util.ResourceReader; import org.stellar.anchor.util.UrlValidationUtil; @Data public class PropertyAppConfig implements AppConfig, Validator { - private String stellarNetworkPassphrase = "Test SDF Network ; September 2015"; - private String hostUrl = "http://localhost:9800"; - private String horizonUrl = "https://horizon-testnet.stellar.org"; - private String jwtSecretKey; - private String assets = "assets-test.json"; + @Value("${stellar_network.network_passphrase}") + private String stellarNetworkPassphrase; + + @Value("${host_url}") + private String hostUrl; + + @Value("${stellar_network.horizon_url}") + private String horizonUrl; + private List languages; @Override @@ -32,14 +35,6 @@ public void validate(Object target, Errors errors) { errors, "stellarNetworkPassphrase", "empty-stellarNetworkPassphrase"); ValidationUtils.rejectIfEmpty(errors, "hostUrl", "empty-hostUrl"); ValidationUtils.rejectIfEmpty(errors, "horizonUrl", "empty-horizonUrl"); - ValidationUtils.rejectIfEmpty(errors, "jwtSecretKey", "empty-jwtSecretKey"); - - ResourceReader reader = new SpringResourceReader(); - if (!reader.checkResourceExists(config.getAssets())) { - errors.rejectValue( - "assets", "doesNotExist-assets", "assets resource file could not be found"); - } - UrlValidationUtil.rejectIfMalformed(config.getHostUrl(), "hostUrl", errors); UrlValidationUtil.rejectIfMalformed(config.getHorizonUrl(), "horizonUrl", errors); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java new file mode 100644 index 0000000000..73b7689de6 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java @@ -0,0 +1,20 @@ +package org.stellar.anchor.platform.config; + +import lombok.Data; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.stellar.anchor.config.AssetsConfig; + +@Data +public class PropertyAssetsConfig implements AssetsConfig, Validator { + AssetConfigType type; + String value; + + @Override + public boolean supports(Class clazz) { + return AssetsConfig.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) {} +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java index d512ab259c..ce070d43f1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java @@ -6,20 +6,17 @@ import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.stellar.anchor.config.EventConfig; -import org.stellar.anchor.config.KafkaConfig; -import org.stellar.anchor.config.SqsConfig; +import org.stellar.anchor.config.PublisherConfig; @Data public class PropertyEventConfig implements EventConfig, Validator { private boolean enabled = false; private String publisherType; - KafkaConfig kafkaConfig; - SqsConfig sqsConfig; + PublisherConfig publisherConfig; - public PropertyEventConfig(KafkaConfig kafkaConfig, SqsConfig sqsConfig) { - this.kafkaConfig = kafkaConfig; - this.sqsConfig = sqsConfig; + public PropertyEventConfig(PublisherConfig kafkaConfig) { + this.publisherConfig = kafkaConfig; } @Override @@ -36,34 +33,13 @@ public void validate(Object target, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "publisherType", ""); - switch (config.getPublisherType()) { - case "kafka": - BindException validation = kafkaConfig.validate(); - if (validation.hasErrors()) { - String errorString = validation.getAllErrors().get(0).toString(); - errors.rejectValue( - "kafkaConfig", - "badConfig-kafka", - String.format( - "publisherType set to kafka, but kafka config not properly configured: %s", - errorString)); - } - break; - case "sqs": - validation = sqsConfig.validate(); - if (validation.hasErrors()) { - String errorString = validation.getAllErrors().get(0).toString(); - errors.rejectValue( - "sqsConfig", - "badConfig-sqs", - String.format( - "publisherType set to sqs, but sqs config not properly configured: %s", - errorString)); - } - break; - default: - errors.rejectValue( - "publisherType", "invalidType-publisherType", "publisherType set to unknown type"); + BindException validation = publisherConfig.validate(config.getPublisherType()); + if (validation.hasErrors()) { + String errorString = validation.getAllErrors().get(0).toString(); + errors.rejectValue( + "publisherConfig", + "badPublisherConfig", + String.format("event publisher not properly configured: %s", errorString)); } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java new file mode 100644 index 0000000000..506f20ef1e --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java @@ -0,0 +1,32 @@ +package org.stellar.anchor.platform.config; + +import java.util.HashMap; +import java.util.Map; +import lombok.Data; +import org.stellar.anchor.config.EventTypeToQueueConfig; + +@Data +public class PropertyEventTypeToQueueConfig implements EventTypeToQueueConfig { + + private Map eventTypeToQueueMap; + + private String all = "ap_event_single_queue"; + private String quote_created = "ap_event_quote_created"; + private String transaction_created = "ap_event_transaction_created"; + private String transaction_status_changed = "ap_event_transaction_status_changed"; + private String transaction_error = "ap_event_transaction_error"; + + public PropertyEventTypeToQueueConfig() { + this.eventTypeToQueueMap = new HashMap<>(); + this.eventTypeToQueueMap.put("all", this.all); + this.eventTypeToQueueMap.put("quote_created", this.quote_created); + this.eventTypeToQueueMap.put("transaction_created", this.transaction_created); + this.eventTypeToQueueMap.put("transaction_status_changed", this.transaction_status_changed); + this.eventTypeToQueueMap.put("transaction_error", this.transaction_error); + } + + @Override + public Map getEventTypeToQueueMap() { + return this.eventTypeToQueueMap; + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyIntegrationAuthConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyIntegrationAuthConfig.java deleted file mode 100644 index ec71870962..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyIntegrationAuthConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.stellar.anchor.platform.config; - -import static org.stellar.anchor.config.IntegrationAuthConfig.AuthType.*; - -import java.util.List; -import lombok.Data; -import org.springframework.validation.Errors; -import org.springframework.validation.Validator; -import org.stellar.anchor.config.IntegrationAuthConfig; - -@Data -public class PropertyIntegrationAuthConfig implements IntegrationAuthConfig, Validator { - private static long minExpiration = 5000; - - AuthType authType = NONE; - String platformToAnchorSecret; - String anchorToPlatformSecret; - Long expirationMilliseconds; - - @Override - public boolean supports(Class clazz) { - return IntegrationAuthConfig.class.isAssignableFrom(clazz); - } - - @Override - public void validate(Object target, Errors errors) { - IntegrationAuthConfig config = (IntegrationAuthConfig) target; - - if (List.of(API_KEY, JWT_TOKEN).contains(config.getAuthType())) { - if (config.getPlatformToAnchorSecret() == null) { - errors.rejectValue( - "platformToAnchorSecret", - "empty-platformToAnchorSecret", - "platformToAnchorSecret cannot be empty for authType " + config.getAuthType()); - } else if (config.getAnchorToPlatformSecret() == null) { - errors.rejectValue( - "anchorToPlatformSecret", - "empty-anchorToPlatformSecret", - "anchorToPlatformSecret cannot be empty for authType " + config.getAuthType()); - } - - if (JWT_TOKEN == config.getAuthType() - && (config.getExpirationMilliseconds() < minExpiration)) { - errors.rejectValue( - "expirationMilliseconds", - "min-expirationMilliseconds", - "expirationMilliseconds cannot be lower than " + minExpiration); - } - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyKafkaConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyKafkaConfig.java deleted file mode 100644 index baa9a35a5f..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyKafkaConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.stellar.anchor.platform.config; - -import java.util.Map; -import lombok.Data; -import org.springframework.validation.BindException; -import org.springframework.validation.ValidationUtils; -import org.stellar.anchor.config.KafkaConfig; - -@Data -public class PropertyKafkaConfig implements KafkaConfig { - private String bootstrapServer; - private Boolean useSingleQueue; - private Boolean useIAM = false; - private Map eventTypeToQueue; - - @Override - public boolean isUseSingleQueue() { - return useSingleQueue; - } - - @Override - public boolean isUseIAM() { - return useIAM; - } - - public BindException validate() { - BindException errors = new BindException(this, "kafkaConfig"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "bootstrapServer", "empty-bootstrapServer"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "useSingleQueue", "empty-useSingleQueue"); - return errors; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java index c9f875ffdb..3fb647eff9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java @@ -1,10 +1,29 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; +import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.MetricConfig; @Data -public class PropertyMetricConfig implements MetricConfig { - private boolean optionalMetricsEnabled = false; +public class PropertyMetricConfig implements MetricConfig, Validator { + private boolean enbaled = false; + private boolean extrasEnabled = false; private Integer runInterval = 30; + + @Override + public boolean supports(Class clazz) { + return AppConfig.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + System.out.println("here"); + } + + @Override + public boolean isExtrasEnabled() { + return this.extrasEnabled; + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyCirclePaymentObserverConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java similarity index 63% rename from platform/src/main/java/org/stellar/anchor/platform/config/PropertyCirclePaymentObserverConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java index 8068490b9e..1a0e660030 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyCirclePaymentObserverConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java @@ -4,21 +4,21 @@ import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; -import org.stellar.anchor.config.CirclePaymentObserverConfig; +import org.stellar.anchor.config.PaymentObserverConfig; @Data -public class PropertyCirclePaymentObserverConfig implements CirclePaymentObserverConfig, Validator { +public class PropertyPaymentObserverConfig implements PaymentObserverConfig, Validator { private boolean enabled = false; private String trackedWallet = "all"; @Override public boolean supports(Class clazz) { - return CirclePaymentObserverConfig.class.isAssignableFrom(clazz); + return PaymentObserverConfig.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { - PropertyCirclePaymentObserverConfig config = (PropertyCirclePaymentObserverConfig) target; + PropertyPaymentObserverConfig config = (PropertyPaymentObserverConfig) target; if (config.enabled) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "trackedWallet", "empty-trackedWallet"); diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java new file mode 100644 index 0000000000..d83a38d09c --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java @@ -0,0 +1,55 @@ +package org.stellar.anchor.platform.config; + +import java.util.Map; +import lombok.Data; +import org.springframework.validation.BindException; +import org.springframework.validation.ValidationUtils; +import org.stellar.anchor.config.EventTypeToQueueConfig; +import org.stellar.anchor.config.PublisherConfig; + +@Data +public class PropertyPublisherConfig implements PublisherConfig { + private String bootstrapServer; + private Boolean useSingleQueue; + + private Boolean useIAM; + private String region; + private String accessKey; + private String secretKey; + + private Map eventTypeToQueue; + + public PropertyPublisherConfig(EventTypeToQueueConfig eventTypeToQueueConfig) { + this.eventTypeToQueue = eventTypeToQueueConfig.getEventTypeToQueueMap(); + } + + @Override + public boolean isUseSingleQueue() { + return useSingleQueue; + } + + @Override + public boolean isUseIAM() { + return useIAM; + } + + public BindException validate(String publisherType) { + BindException errors = new BindException(this, "publisherConfig"); + switch (publisherType) { + case "kafka": + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, "bootstrapServer", "empty-bootstrapServer"); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "useSingleQueue", "empty-useSingleQueue"); + break; + case "sqs": + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "region", "empty-region"); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "accessKey", "empty-accessKey"); + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "secretKey", "empty-secretKey"); + break; + default: + errors.rejectValue( + "publisherType", "invalidType-publisherType", "publisherType set to unknown type"); + } + return errors; + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java new file mode 100644 index 0000000000..5a3aa6b43b --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java @@ -0,0 +1,20 @@ +package org.stellar.anchor.platform.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.stellar.anchor.config.SecretConfig; + +@Data +public class PropertySecretConfig implements SecretConfig { + @Value("${secret.sep10.jwt_secret:#{null}}") + private String sep10JwtSecretKey; + + @Value("${secret.sep10.signing_seed:#{null}}") + private String sep10SigningSeed; + + @Value("${secret.callback_api.auth_secret:#{null}}") + private String callbackApiSecret = null; + + @Value("${secret.platform_api.auth_secret:#{null}}") + private String platformApiSecret = null; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep10Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep10Config.java index d77d35ebdb..21f93d8ca5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep10Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep10Config.java @@ -1,24 +1,34 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.StringHelper.isEmpty; + import java.util.List; import lombok.Data; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; +import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.config.SecretConfig; import org.stellar.anchor.config.Sep10Config; @Data public class PropertySep10Config implements Sep10Config, Validator { + private Boolean enabled; private String homeDomain; private boolean clientAttributionRequired = false; - private Boolean enabled = true; - private String signingSeed; private Integer authTimeout = 900; private Integer jwtTimeout = 86400; private List clientAttributionDenyList; private List clientAttributionAllowList; private List omnibusAccountList; private boolean requireKnownOmnibusAccount; + private SecretConfig secretConfig; + private JwtService jwtService; + + public PropertySep10Config(SecretConfig secretConfig, JwtService jwtService) { + this.secretConfig = secretConfig; + this.jwtService = jwtService; + } @Override public boolean supports(Class clazz) { @@ -31,7 +41,18 @@ public void validate(Object target, Errors errors) { if (config.getEnabled()) { ValidationUtils.rejectIfEmptyOrWhitespace(errors, "homeDomain", "empty-homeDomain"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "signingSeed", "empty-signingSeed"); + if (isEmpty(secretConfig.getSep10SigningSeed())) { + errors.rejectValue( + null, + "empty-sep10SigningSeed", + "Please set environment variable secret.sep10.signing_seed"); + } + if (isEmpty(secretConfig.getSep10JwtSecretKey()) || isEmpty(jwtService.getJwtKey())) { + errors.rejectValue( + null, + "empty-sep10JwtSecret", + "Please set environment variable secret.sep10.jwt_secret"); + } } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep12Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep12Config.java index 93425c1eff..523ca512d2 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep12Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep12Config.java @@ -2,16 +2,18 @@ import lombok.Data; import org.springframework.validation.Errors; -import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep12Config; -import org.stellar.anchor.util.UrlValidationUtil; @Data public class PropertySep12Config implements Sep12Config, Validator { - Boolean enabled = false; + Boolean enabled; String customerIntegrationEndPoint; + public PropertySep12Config(CallbackApiConfig callbackApiConfig) { + this.customerIntegrationEndPoint = callbackApiConfig.getBaseUrl(); + } + @Override public boolean supports(Class clazz) { return Sep12Config.class.isAssignableFrom(clazz); @@ -20,13 +22,5 @@ public boolean supports(Class clazz) { @Override public void validate(Object target, Errors errors) { Sep12Config config = (Sep12Config) target; - - if (config.getEnabled()) { - ValidationUtils.rejectIfEmpty( - errors, "customerIntegrationEndPoint", "empty-customerIntegrationEndPoint"); - - UrlValidationUtil.rejectIfMalformed( - config.getCustomerIntegrationEndPoint(), "customerIntegrationEndPoint", errors); - } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java index d27e29218d..dbb75bba34 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep1Config.java @@ -1,31 +1,102 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.StringHelper.isNotEmpty; + +import java.io.File; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Value; import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep1Config; -import org.stellar.anchor.platform.service.SpringResourceReader; -import org.stellar.anchor.util.ResourceReader; +import org.stellar.anchor.util.NetUtil; +import org.stellar.anchor.util.Sep1Helper; @Data +@AllArgsConstructor +@NoArgsConstructor public class PropertySep1Config implements Sep1Config, Validator { - String stellarFile; - boolean enabled = false; + boolean enabled; + + @Value("${sep1.toml.type:#{null}}") + String type; + + @Value("${sep1.toml.value:#{null}}") + String value; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return Sep1Config.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { Sep1Config config = (Sep1Config) target; if (config.isEnabled()) { - ResourceReader reader = new SpringResourceReader(); - if (!reader.checkResourceExists(config.getStellarFile())) { - errors.rejectValue( - "stellarFile", "doesNotExist-stellarFile", "stellarFile resource does not resolve"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "type", + "empty-sep1Type", + "The value of config[sep1.toml.type] is must be specified"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "value", + "empty-sep1Value", + "The value of config[sep1.toml.value] is must be specified"); + + if (isNotEmpty(config.getValue()) && isNotEmpty(config.getType())) { + switch (config.getType().toLowerCase()) { + case "string": + try { + Sep1Helper.parse(config.getValue()); + } catch (IllegalStateException isex) { + errors.rejectValue( + "value", + "invalid-sep1Value", + String.format( + "config[sep1.toml.value] does not contain a valid TOML. %s", + isex.getMessage())); + } + break; + case "url": + if (!NetUtil.isUrlValid(config.getValue())) { + errors.rejectValue( + "value", + "invalid-sep1Value", + String.format( + "config[sep1.toml.value]=%s is not a valid URL", config.getValue())); + } + break; + case "file": + File file = new File(config.getValue()); + if (!file.exists()) { + errors.rejectValue( + "value", + "doesNotExist-sep1Value", + String.format( + "config[sep1.toml.value]=%s specifies a file that does not exist", + config.getValue())); + } + break; + default: + errors.rejectValue( + "type", + "invalid-sep1Type", + String.format( + "'%s' is not a valid config[sep1.toml.type]. Only 'string', 'url' and 'file' are supported", + config.getValue())); + break; + } + } else { + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "value", + "empty-sep1TypeOrValue", + "Both the [sep1.toml.type] and [sep1.toml.value] must be specified if enabled"); } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep24Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep24Config.java index a8968b48f2..f3726f3416 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep24Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep24Config.java @@ -1,11 +1,23 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep24Config; @Data -public class PropertySep24Config implements Sep24Config { - boolean enabled = false; - int interactiveJwtExpiration = 300; - String interactiveUrl = "NA"; +public class PropertySep24Config implements Sep24Config, Validator { + boolean enabled; + int interactiveJwtExpiration; + String interactiveUrl; + + @Override + public boolean supports(Class clazz) { + return Sep24Config.class.isAssignableFrom(clazz); + } + + @Override + public void validate(Object target, Errors errors) { + Sep24Config config = (Sep24Config) target; + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java index 9a0d8f32cb..5623d444d7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep31Config.java @@ -12,7 +12,7 @@ @Data public class PropertySep31Config implements Sep31Config, Validator { - boolean enabled = false; + boolean enabled; String feeIntegrationEndPoint; String uniqueAddressIntegrationEndPoint; PaymentType paymentType = STRICT_SEND; @@ -20,8 +20,10 @@ public class PropertySep31Config implements Sep31Config, Validator { CircleConfig circleConfig; - public PropertySep31Config(CircleConfig circleConfig) { + public PropertySep31Config(CircleConfig circleConfig, CallbackApiConfig callbackApiConfig) { this.circleConfig = circleConfig; + this.feeIntegrationEndPoint = callbackApiConfig.getBaseUrl(); + this.uniqueAddressIntegrationEndPoint = callbackApiConfig.getBaseUrl(); } @Override diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep38Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep38Config.java index 8618d8c669..6b159aef18 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep38Config.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep38Config.java @@ -2,15 +2,12 @@ import lombok.Data; import org.springframework.validation.Errors; -import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep38Config; -import org.stellar.anchor.util.UrlValidationUtil; @Data public class PropertySep38Config implements Sep38Config, Validator { - boolean enabled = false; - String quoteIntegrationEndPoint; + boolean enabled; @Override public boolean supports(Class clazz) { @@ -20,13 +17,5 @@ public boolean supports(Class clazz) { @Override public void validate(Object target, Errors errors) { Sep38Config config = (Sep38Config) target; - - if (config.isEnabled()) { - ValidationUtils.rejectIfEmpty( - errors, "quoteIntegrationEndPoint", "empty-quoteIntegrationEndPoint"); - - UrlValidationUtil.rejectIfMalformed( - config.getQuoteIntegrationEndPoint(), "quoteIntegrationEndPoint", errors); - } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySqsConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySqsConfig.java deleted file mode 100644 index da24381dc0..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySqsConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.stellar.anchor.platform.config; - -import java.util.Map; -import lombok.Data; -import org.springframework.validation.BindException; -import org.springframework.validation.ValidationUtils; -import org.stellar.anchor.config.SqsConfig; - -@Data -public class PropertySqsConfig implements SqsConfig { - private String region; - private Boolean useSingleQueue; - private Map eventTypeToQueue; - private String accessKey; - private String secretKey; - - @Override - public Boolean isUseSingleQueue() { - return useSingleQueue; - } - - public BindException validate() { - BindException errors = new BindException(this, "sqsConfig"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "region", "empty-region"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "accessKey", "empty-accessKey"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "secretKey", "empty-secretKey"); - return errors; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/AbstractConfigurator.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/AbstractConfigurator.java deleted file mode 100644 index 974eea2c39..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/AbstractConfigurator.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.stellar.anchor.platform.configurator; - -import java.util.Properties; -import org.springframework.core.env.PropertiesPropertySource; -import org.stellar.anchor.util.PrefixedProperties; - -public abstract class AbstractConfigurator { - protected static Properties loadedProperties = new Properties(); - - protected Properties getFlatProperties() { - return loadedProperties; - } - - protected PropertiesPropertySource createPrefixedPropertySource(String prefix) { - PrefixedProperties prefixedProp = - new PrefixedProperties(prefix.endsWith(".") ? prefix : prefix + ".", loadedProperties); - return new PropertiesPropertySource(prefix, prefixedProp); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java new file mode 100644 index 0000000000..ce5fd39f8f --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java @@ -0,0 +1,43 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.platform.configurator.ConfigHelper.normalize; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class ConfigEnvironment { + static Map env; + + static { + rebuild(); + } + + public static void rebuild() { + env = new HashMap<>(); + for (Map.Entry entry : System.getenv().entrySet()) { + env.put(normalize(entry.getKey()), entry.getValue()); + } + + Properties sysProps = System.getProperties(); + for (Map.Entry entry : sysProps.entrySet()) { + env.put(normalize(String.valueOf(entry.getKey())), String.valueOf(entry.getValue())); + } + } + + /** + * This class seems redundant but it is necessary for JUnit test for creating configuration with + * environment variables. + * + * @param name the name of the environment variable + * @return the value of the environment variable. + */ + public static String getenv(String name) { + return env.get(ConfigHelper.normalize(name)); + } + + public static Collection names() { + return env.keySet(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java new file mode 100644 index 0000000000..0950679b50 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java @@ -0,0 +1,98 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource.*; +import static org.stellar.anchor.util.StringHelper.camelToSnake; +import static org.stellar.anchor.util.StringHelper.isEmpty; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource; +import org.stellar.anchor.util.Log; + +public class ConfigHelper { + public static ConfigMap loadConfig(Resource resource, ConfigSource configSource) + throws IOException { + ConfigMap configMap = new ConfigMap(); + + YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); + List> sources = loader.load("yaml", resource); + + for (PropertySource source : sources) { + MapPropertySource mapPropertySource = (MapPropertySource) source; + for (Map.Entry entry : mapPropertySource.getSource().entrySet()) { + String key = normalize(entry.getKey()); + if (key.equals("version")) { + configMap.setVersion(Integer.parseInt(entry.getValue().toString())); + } else { + configMap.put(key, entry.getValue().toString(), configSource); + } + } + } + return configMap; + } + + public static ConfigMap loadConfigFromEnv(int latestVersion) throws InvalidConfigException { + ConfigMap config = new ConfigMap(); + + // Determine the version. + // if system.env['version'] is not defined, use the latest version. + int version; + String strVersion = ConfigEnvironment.getenv("version"); + if (strVersion == null) { + Log.infoF("System env['version'] is not defined. version:{} is assumed}", latestVersion); + version = latestVersion; + } else { + try { + version = Integer.parseInt(strVersion); + } catch (NumberFormatException nfex) { + Log.infoF( + "System env['version']={} is invalid. All system environment variables are ignored", + strVersion); + return null; + } + } + + ConfigReader configSchema = new ConfigReader(version); + config.setVersion(version); + for (String name : ConfigEnvironment.names()) { + if (!isEmpty(name) && configSchema.has(name) && !name.equals("version")) { + // the envarg is defined in this version + config.put(name, ConfigEnvironment.getenv(name), ENV); + } + } + return config; + } + + public static String normalize(String name) { + return camelToSnake(name); + } + + public static ConfigMap loadDefaultConfig() throws IOException { + return loadConfig(new ClassPathResource("config/anchor-config-default-values.yaml"), DEFAULT); + } + + public static String suggestedSchema() throws IOException { + ConfigMap config = loadDefaultConfig(); + for (String name : config.names()) { + config.put(name, "", VERSION_SCHEMA); + } + + config.put("secret.sep10_signing_seed", "", VERSION_SCHEMA); + config.put("secret.jwt_secret", "", VERSION_SCHEMA); + config.put("secret.platform_api.secret", "", VERSION_SCHEMA); + config.put("secret.callback_api.secret", "", VERSION_SCHEMA); + + return config.printToString(); + } + + public static void main(String[] args) throws IOException { + System.out.println(suggestedSchema()); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java new file mode 100644 index 0000000000..eba0d6c188 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java @@ -0,0 +1,195 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.api.platform.HealthCheckStatus.GREEN; +import static org.stellar.anchor.platform.configurator.ConfigHelper.*; +import static org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource.FILE; +import static org.stellar.anchor.util.Log.info; +import static org.stellar.anchor.util.Log.infoF; + +import java.io.IOException; +import java.util.List; +import java.util.Properties; +import lombok.Builder; +import lombok.Data; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.io.Resource; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.api.platform.HealthCheckResult; +import org.stellar.anchor.healthcheck.HealthCheckable; +import org.stellar.anchor.util.Log; + +public class ConfigManager + implements ApplicationContextInitializer, HealthCheckable { + + static final String STELLAR_ANCHOR_CONFIG = "STELLAR_ANCHOR_CONFIG"; + static ConfigManager configManager = new ConfigManager(); + + ConfigMap configMap; + + private ConfigManager() {} + + public static ConfigManager getInstance() { + return configManager; + } + + @SneakyThrows + @Override + public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { + // Read configuration from system environment variables, configuration file, and default values + info("Read and process configurations"); + configMap = processConfigurations(applicationContext); + + // Make sure no secret is leaked. + sanitize(configMap); + + // Send values to Spring + sendToSpring( + applicationContext, configMap, List.of(new LogConfigAdapter(), new DataConfigAdapter())); + } + + void sanitize(ConfigMap configMap) { + SecretManager.getInstance() + .secretVars + .forEach( + var -> { + if (configMap.get(var) != null) { + Log.warnF( + "Possible secret leak of config[{}]. Please remove the secret from configuration.", + var); + configMap.remove(var); + } + }); + } + + static final String CONFIG_NAME = "config"; + + void sendToSpring( + ConfigurableApplicationContext applicationContext, + ConfigMap config, + List adapters) + throws InvalidConfigException { + + Properties props = new Properties(); + props.putAll(config.toStringMap()); + + // Set Platform configurations + PropertiesPropertySource pps = new PropertiesPropertySource(CONFIG_NAME, props); + applicationContext.getEnvironment().getPropertySources().addFirst(pps); + + for (SpringConfigAdapter adapter : adapters) { + adapter.updateSpringEnv(applicationContext, config); + } + } + + ConfigMap processConfigurations(ConfigurableApplicationContext applicationContext) + throws IOException, InvalidConfigException { + info("reading default configuration values"); + // Load default values + ConfigMap latestConfig = loadDefaultConfig(); + ConfigMap config = latestConfig; + + infoF("default configuration version={}", latestConfig.getVersion()); + // Check if default config is consistent with the definition + ConfigReader configReader = new ConfigReader(latestConfig.getVersion()); + info("validating default configuration values"); + configReader.validate(latestConfig); + + // Read the configuration from the YAML file specified by the STELLAR_ANCHOR_CONFIG system + // environment variable. + Resource configFileResource = getConfigFileAsResource(applicationContext); + if (configFileResource != null) { + infoF("reading configuration file from {}", configFileResource.getURL()); + ConfigMap yamlConfig = loadConfig(configFileResource, FILE); + config.merge(updateToLatestConfig(latestConfig, yamlConfig)); + } + + // Read and process the environment variable + ConfigMap envConfig = loadConfigFromEnv(latestConfig.getVersion()); + if (envConfig != null) { + info("Processing system environment variables"); + config.merge(updateToLatestConfig(latestConfig, envConfig)); + } + + return config; + } + + ConfigMap updateToLatestConfig(ConfigMap latestConfig, ConfigMap config) + throws InvalidConfigException { + int configVersion = config.getVersion(); + int latestVersion = latestConfig.getVersion(); + + ConfigReader configReader = new ConfigReader(configVersion); + configReader.validate(config); + + for (int version = configVersion + 1; version <= latestVersion; version++) { + infoF("Applying configuration version {}", version); + configReader = new ConfigReader(version); + // readFrom(config) returns a new config object to avoid mutating the config map argument + config = configReader.readFrom(config); + } + + return config; + } + + Resource getConfigFileAsResource(ConfigurableApplicationContext applicationContext) + throws IOException { + String yamlConfigFile = ConfigEnvironment.getenv(STELLAR_ANCHOR_CONFIG); + + if (yamlConfigFile != null) { + Resource resource = applicationContext.getResource(yamlConfigFile); + if (!resource.exists()) { + throw new IOException( + String.format("Failed to read configuration from %s", yamlConfigFile)); + } + return resource; + } + return null; + } + + @Override + public String getName() { + return "config"; + } + + @Override + public List getTags() { + return List.of(Tags.ALL, Tags.CONFIG); + } + + @Override + public HealthCheckResult check() { + return ConfigManagerHealthCheckResult.builder().name(getName()).configMap(configMap).build(); + } + + @Override + public int compareTo(@NotNull HealthCheckable other) { + return this.getName().compareTo(other.getName()); + } +} + +@Data +@Builder +class ConfigManagerHealthCheckResult implements HealthCheckResult { + transient String name; + + ConfigMap configMap; + + @Override + public String name() { + return name; + } + + @Override + public List getStatuses() { + return List.of(); + } + + @Override + public String getStatus() { + return GREEN.toString(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java new file mode 100644 index 0000000000..fdd04608c4 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java @@ -0,0 +1,108 @@ +package org.stellar.anchor.platform.configurator; + +import java.util.*; +import java.util.stream.Collectors; +import lombok.Data; +import org.stellar.anchor.api.exception.InvalidConfigException; + +public class ConfigMap { + int version; + Map data; + + public ConfigMap() { + data = new HashMap<>(); + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public ConfigEntry get(String name) { + return data.get(name); + } + + public void put(String key, String value, ConfigSource source) { + data.put(key, new ConfigEntry(value.trim(), source)); + } + + public void remove(String name) { + data.remove(name); + } + + public String getString(String key) { + return getString(key, null); + } + + public String getString(String key, String defaultValue) { + ConfigEntry entry = data.get(key); + if (entry == null) return defaultValue; + return entry.value; + } + + public Integer getInt(String key) throws InvalidConfigException { + try { + return Integer.parseInt(getString(key)); + } catch (NumberFormatException nfex) { + throw new InvalidConfigException(String.format("[%s] is not an integer.", data.get(key))); + } + } + + public Boolean getBoolean(String key) { + + return Boolean.parseBoolean(getString(key)); + } + + public Collection names() { + return data.keySet(); + } + + public Map toStringMap() { + return data.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().getValue())); + } + + public void merge(ConfigMap config) { + for (String name : config.names()) { + data.put(name, config.data.get(name)); + } + } + + public boolean sameAs(ConfigMap anotherMap) { + for (String key : names()) { + if (!anotherMap.getString(key).equals(getString(key))) { + return false; + } + } + + return data.size() == anotherMap.data.size(); + } + + public enum ConfigSource { + FILE, + ENV, + DEFAULT, + VERSION_SCHEMA + } + + @Data + public static class ConfigEntry { + String value; + ConfigSource source; + + public ConfigEntry(String value, ConfigSource source) { + this.value = value; + this.source = source; + } + } + + public String printToString() { + List lines = new LinkedList<>(); + data.forEach((k, v) -> lines.add(String.format("%s:%s", k, v.value))); + Collections.sort(lines); + return String.join(System.lineSeparator(), lines); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java new file mode 100644 index 0000000000..3d2f51a5c0 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java @@ -0,0 +1,85 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.platform.configurator.ConfigHelper.loadConfig; +import static org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource.VERSION_SCHEMA; +import static org.stellar.anchor.util.StringHelper.isEmpty; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import org.springframework.core.io.ClassPathResource; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.platform.configurator.ConfigMap.ConfigEntry; +import org.stellar.anchor.util.Log; + +public class ConfigReader { + private final int version; + ConfigMap configSchema; + + public ConfigReader(int version) throws InvalidConfigException { + try { + configSchema = loadConfig(new ClassPathResource(getVersionSchemaFile(version)), VERSION_SCHEMA); + this.version = version; + } catch (IOException e) { + throw new InvalidConfigException(String.format("version:%s is not a defined", version)); + } + } + + public boolean has(String name) { + if (configSchema.getString(name) == null) { + return false; + } + + return isEmpty(configSchema.getString(name)); + } + + public void validate(ConfigMap configMap) throws InvalidConfigException { + List errors = new LinkedList<>(); + for (String key : configMap.names()) { + if (this.configSchema.getString(key) == null) { + errors.add( + String.format( + "Invalid configuration: %s=%s. (version=%d)", + key, configMap.getString(key), version)); + } + } + + if (errors.size() > 0) { + throw new InvalidConfigException(errors); + } + } + + public ConfigMap readFrom(ConfigMap configMap) { + ConfigMap updatedConfigMap = new ConfigMap(); + updatedConfigMap.setVersion(this.version); + configMap + .names() + .forEach( + key -> { + ConfigEntry entry = configMap.get(key); + String value = entry.getValue(); + String configLocation = configSchema.getString(key); + if (configLocation != null) { + if (isEmpty(configLocation)) { + // Copy value + updatedConfigMap.put(key, value, entry.source); + } else { + Log.debugF( + "config[{}] is moved to config[{}] in version:{}", + key, + configLocation, + version); + updatedConfigMap.put(configLocation, value, entry.source); + } + } else { + Log.debugF("config[{}] is removed in version {}", key, version); + } + }); + + return updatedConfigMap; + } + + static String getVersionSchemaFile(Integer version) { + return String.format("config/anchor-config-schema-v%d.yaml", version); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataAccessConfigurator.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataAccessConfigurator.java deleted file mode 100644 index a83940d7b0..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataAccessConfigurator.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.stellar.anchor.platform.configurator; - -import org.jetbrains.annotations.NotNull; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.PropertiesPropertySource; - -public class DataAccessConfigurator extends AbstractConfigurator - implements ApplicationContextInitializer { - @Override - public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { - // Find the location of the data-access settings. - String path = getFlatProperties().getProperty("stellar.anchor.data-access.settings"); - - // Load and add the data access settings to Spring `Environment` - PropertiesPropertySource pps = createPrefixedPropertySource(path); - applicationContext.getEnvironment().getPropertySources().addFirst(pps); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java new file mode 100644 index 0000000000..d567a38338 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java @@ -0,0 +1,183 @@ +package org.stellar.anchor.platform.configurator; + +import java.util.Arrays; +import java.util.List; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.util.Log; + +/** + * This adapter manages Spring datasource and jpa configurations accoring to the config map. The + * following lists Spring data configurations that we manage based on: https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html + * + *

Spring Datasource: + * + *

+ * spring.datasource.driver-class-name:            The JDBC driver class name
+ * spring.datasource.name:                         Datasource name to use if "generate-unique-name" is false. Defaults to "testdb" when using an embedded database, otherwise null.
+ * spring.datasource.type:                         Fully qualified name of the connection pool implementation to use. By default, it is auto-detected from the classpath.
+ * spring.datasource.url:                          JDBC URL of the database.
+ * spring.datasource.username:                     Login username of the database.
+ * spring.datasource.password:                     Login password of the database.
+ * spring.datasource.generate-unique-name:         Whether to generate a random datasource name.
+ * 
+ * + * Spring Connection Pool Management + * + *
+ * spring.datasource.hikari.connection-timeout = 20000 # maximum number of milliseconds that a client will wait for a connection
+ * spring.datasource.hikari.minimum-idle= 10           # minimum number of idle connections maintained by HikariCP in a connection pool
+ * spring.datasource.hikari.maximum-pool-size= 10      # maximum pool size
+ * spring.datasource.hikari.idle-timeout=10000         # maximum idle time for connection
+ * spring.datasource.hikari.max-lifetime= 1000         # maximum lifetime in milliseconds of a connection in the pool after it is closed.
+ * spring.datasource.hikari.auto-commit =true          # default auto-commit behavior.
+ * 
+ * + * Spring JPA. + * + *
+ * spring.jpa.database: 	                           Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.
+ * spring.jpa.database-platform:                       Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.
+ * spring.jpa.defer-datasource-initialization:         Whether to defer DataSource initialization until after any EntityManagerFactory beans have been created and initialized.: false
+ * spring.jpa.generate-ddl:                            Whether to initialize the schema on startup. : false
+ * spring.jpa.hibernate.ddl-auto:                      DDL mode. This is actually a shortcut for the "hibernate.hbm2ddl.auto" property. Defaults to "create-drop" when using an embedded database and no schema manager was detected. Otherwise, defaults to "none".
+ * spring.jpa.hibernate.naming.implicit-strategy:      Fully qualified name of the implicit naming strategy.
+ * spring.jpa.hibernate.naming.physical-strategy:      Fully qualified name of the physical naming strategy.
+ * spring.jpa.hibernate.use-new-id-generator-mappings: Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE. This is actually a shortcut for the "hibernate.id.new_generator_mappings" property. When not specified will default to "true".
+ * spring.jpa.mapping-resources:                       Mapping resources (equivalent to "mapping-file" entries in persistence.xml).
+ * spring.jpa.open-in-view:                            Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request. : true
+ * spring.jpa.show-sql:                                Show SQL : false
+ * 
+ */ +public class DataConfigAdapter extends SpringConfigAdapter { + List allFields = + Arrays.asList( + "spring.datasource.driver-class-name", + "spring.datasource.name", + "spring.datasource.type", + "spring.datasource.url", + "spring.datasource.username", + "spring.datasource.password", + "spring.datasource.generate-unique-name", + "spring.datasource.hikari.connection-timeout ", + "spring.datasource.hikari.minimum-idle", + "spring.datasource.hikari.maximum-pool-size", + "spring.datasource.hikari.idle-timeout", + "spring.datasource.hikari.max-lifetime", + "spring.datasource.hikari.auto-commit ", + "spring.jpa.database", + "spring.jpa.database-platform", + "spring.jpa.defer-datasource-initialization", + "spring.jpa.generate-ddl", + "spring.jpa.hibernate.use-new-id-generator-mappings", + "spring.jpa.open-in-view", + "spring.jpa.show-sql", + "spring.flyway.enabled"); + + @Override + void updateSpringEnv(ConfigMap config) throws InvalidConfigException { + // Set our default value to start with + setSpringDataDefaults(); + + // + // The following code block purposely kept long for easier understanding of how the Spring + // database configurations are set. + // + String type = config.getString("data.type").toLowerCase(); + switch (type) { + case "h2": + set("spring.datasource.driver-class-name", "org.h2.Driver"); + set("spring.datasource.embedded-database-connection", "H2"); + set("spring.datasource.name", "anchor-platform"); + set("spring.datasource.url", "jdbc:h2:mem:test"); + set("spring.jpa.database-platform", "org.hibernate.dialect.H2Dialect"); + set("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + copy(config, "data.username", "spring.datasource.username"); + copy(config, "data.password", "spring.datasource.password"); + break; + case "sqlite": + set("spring.datasource.driver-class-name", "org.sqlite.JDBC"); + set("spring.datasource.name", "anchor-platform"); + set("spring.jpa.database-platform", "org.stellar.anchor.platform.sqlite.SQLiteDialect"); + copy(config, "data.url", "spring.datasource.url"); + copy(config, "data.username", "spring.datasource.username"); + copy(config, "data.password", "spring.datasource.password"); + if (config.getString("flyway.enabled").equalsIgnoreCase("true")) { + set("spring.flyway.enabled", true); + set("spring.flyway.locations", "classpath:/db/migration"); + copy(config, "data.username", "spring.flyway.user"); + copy(config, "data.password", "spring.flyway.password"); + copy(config, "data.url", "spring.flyway.url"); + } + break; + case "aurora": + set("spring.datasource.driver-class-name", "org.postgresql.Driver"); + set("spring.datasource.name", "anchor-platform"); + set("spring.jpa.database-platform", "org.hibernate.dialect.PostgreSQL9Dialect"); + set( + "spring.datasource.hikari.max-lifetime", + 840000); // 14 minutes because IAM tokens are valid for 15 min + copy(config, "data.url", "spring.datasource.url"); + copy(config, "data.username", "spring.datasource.username"); + copy(config, "data.password", "spring.datasource.password"); + if (config.getString("flyway.enabled").equalsIgnoreCase("true")) { + set("spring.flyway.enabled", true); + set("spring.flyway.locations", "classpath:/db/migration"); + copy(config, "data.username", "spring.flyway.user"); + copy(config, "data.password", "spring.flyway.password"); + copy(config, "data.url", "spring.flyway.url"); + } + break; + case "postgres": + set("spring.datasource.driver-class-name", "org.postgresql.Driver"); + set("spring.datasource.name", "anchor-platform"); + set("spring.jpa.database-platform", "org.hibernate.dialect.PostgreSQL9Dialect"); + copy(config, "data.url", "spring.datasource.url"); + copy(config, "data.username", "spring.datasource.username"); + copy(config, "data.password", "spring.datasource.password"); + if (config.getString("flyway.enabled").equalsIgnoreCase("true")) { + set("spring.flyway.enabled", true); + set("spring.flyway.locations", "classpath:/db/migration"); + copy(config, "data.username", "spring.flyway.user"); + copy(config, "data.password", "spring.flyway.password"); + copy(config, "data.url", "spring.flyway.url"); + } + break; + default: + Log.errorF("Invalid config[data.type]={}", type); + throw new InvalidConfigException(String.format("Invalid config[data.type]=%s", type)); + } + + checkIfAllFieldsAreSet(); + } + + private void checkIfAllFieldsAreSet() { + allFields.forEach( + field -> { + if (get(field) == null) { + Log.infoF("{} is not set.", field); + } + }); + } + + void setSpringDataDefaults() throws InvalidConfigException { + set("spring.datasource.generate-unique-name", "false"); + + set("spring.datasource.hikari.connection-timeout ", 20000); // in ms + set("spring.datasource.hikari.minimum-idle", 10); + set("spring.datasource.hikari.maximum-pool-size", 10); + set("spring.datasource.hikari.idle-timeout", 10000); + set("spring.datasource.hikari.max-lifetime", 1000); + set("spring.datasource.hikari.auto-commit ", true); + + set("spring.jpa.database", ""); + set("spring.jpa.database-platform", ""); + set("spring.jpa.defer-datasource-initialization", false); + set("spring.jpa.generate-ddl", false); + set("spring.jpa.hibernate.use-new-id-generator-mappings", true); + set("spring.jpa.open-in-view", true); + set("spring.jpa.show-sql", false); + + set("spring.flyway.enabled", false); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/LogConfigAdapter.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/LogConfigAdapter.java new file mode 100644 index 0000000000..fa150d6e95 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/LogConfigAdapter.java @@ -0,0 +1,47 @@ +package org.stellar.anchor.platform.configurator; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.slf4j.Log4jLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.stellar.anchor.api.exception.InvalidConfigException; + +public class LogConfigAdapter extends SpringConfigAdapter { + + @Override + void updateSpringEnv(ConfigMap config) throws InvalidConfigException { + copy(config, "logging.level", "logging.level.root"); + copy(config, "logging.stellar_level", "logging.level.org.stellar"); + + // Check the logger type + Logger logger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + if (logger instanceof Log4jLogger) { + // we are using log4j + if (config.get("logging.level") != null) { + Level rootLevel = getLog4j2Level(config.getString("logging.level", "INFO")); + Configurator.setAllLevels(LogManager.getRootLogger().getName(), rootLevel); + } + + if (config.get("logging.stellar_level") != null) { + Level stellarLevel = getLog4j2Level(config.getString("logging.stellar_level")); + Configurator.setAllLevels("org.stellar", stellarLevel); + } + } + } + + private Level getLog4j2Level(String level) throws InvalidConfigException { + switch (level.toUpperCase()) { + case "TRACE": + case "DEBUG": + case "INFO": + case "WARN": + case "ERROR": + case "FATAL": + return Level.getLevel(level.toUpperCase()); + default: + throw new InvalidConfigException("Invalid config[logger.level]=" + level); + } + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/PlatformAppConfigurator.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/PlatformAppConfigurator.java deleted file mode 100644 index a5eeec12cd..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/PlatformAppConfigurator.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.stellar.anchor.platform.configurator; - -import org.jetbrains.annotations.NotNull; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.PropertiesPropertySource; - -public class PlatformAppConfigurator extends AbstractConfigurator - implements ApplicationContextInitializer { - @Override - public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { - // Find the location of the data-access settings. - String path = getFlatProperties().getProperty("stellar.anchor.app-config.settings"); - - // Load and add the data access settings to Spring `Environment` - PropertiesPropertySource pps = createPrefixedPropertySource(path); - applicationContext.getEnvironment().getPropertySources().addFirst(pps); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/PropertiesReader.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/PropertiesReader.java deleted file mode 100644 index 387155bfb2..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/PropertiesReader.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.stellar.anchor.platform.configurator; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import lombok.SneakyThrows; -import org.jetbrains.annotations.NotNull; -import org.springframework.boot.env.YamlPropertySourceLoader; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; - -public class PropertiesReader extends AbstractConfigurator - implements ApplicationContextInitializer { - - @SneakyThrows - @Override - public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { - // Load default values - loadConfigYaml(new ClassPathResource("anchor-config-defaults.yaml")); - - // If stellar.anchor.config is specified in Spring application properties, use it. - // This is mainly for the purpose of integration test where we may inject the configuration yaml - // file from the test resource. - String yamlLocation = applicationContext.getEnvironment().getProperty("stellar.anchor.config"); - if (yamlLocation != null) { - loadConfigYaml(applicationContext, yamlLocation); - return; - } - - // Read from Java VM OPTS - yamlLocation = getFromSystemProperty(); - if (yamlLocation != null) { - loadConfigYaml(applicationContext, yamlLocation); - return; - } - - // Read from $USER_HOME/.anchor/anchor-config.yaml - File yamlFile = getFromUserFolder(); - if (yamlFile.exists()) { - loadConfigYaml(new FileSystemResource(yamlFile)); - return; - } - - // Read from the file specified by STELLAR_ANCHOR_CONFIG environment variable. - yamlLocation = getFromSystemEnv(); - if (yamlLocation != null) { - loadConfigYaml(applicationContext, yamlLocation); - return; - } - - throw new IllegalArgumentException("Unable to load anchor platform configuration file."); - } - - String getFromSystemEnv() { - return System.getenv() - .getOrDefault("STELLAR_ANCHOR_CONFIG", "classpath:/anchor-config-defaults.yaml"); - } - - String getFromSystemProperty() { - return System.getProperty("stellar.anchor.config"); - } - - File getFromUserFolder() { - return new File(System.getProperty("user.home"), ".anchor/anchor-config.yaml"); - } - - void loadConfigYaml(ApplicationContext applicationContext, String location) throws IOException { - Resource resource = applicationContext.getResource(location); - if (!resource.exists()) { - throw new IOException("Resource not found"); - } - loadConfigYaml(resource); - } - - void loadConfigYaml(Resource resource) throws IOException { - Properties flattenedProperty = getFlatProperties(); - - YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); - List> sources = loader.load("yaml", resource); - - for (PropertySource source : sources) { - MapPropertySource mapPropertySource = (MapPropertySource) source; - for (Map.Entry entry : mapPropertySource.getSource().entrySet()) { - flattenedProperty.put(entry.getKey(), entry.getValue().toString()); - } - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java new file mode 100644 index 0000000000..a8ad8b6d01 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java @@ -0,0 +1,46 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.util.Log.info; +import static org.stellar.anchor.util.StringHelper.isNotEmpty; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.PropertiesPropertySource; + +public class SecretManager + implements ApplicationContextInitializer { + List secretVars = + Arrays.asList( + "secret.sep10.jwt_secret", + "secret.sep10.signing_seed", + "secret.callback_api.auth_secret", + "secret.platform_api.auth_secret"); + + static SecretManager secretManager = new SecretManager(); + + private SecretManager() {} + + public static SecretManager getInstance() { + return secretManager; + } + + @Override + public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { + info("Secret manager started."); + Properties props = new Properties(); + secretVars.forEach( + var -> { + String secret = ConfigEnvironment.getenv(var); + if (isNotEmpty(secret)) { + props.put(var, secret); + } + }); + // Set Platform configurations + PropertiesPropertySource pps = new PropertiesPropertySource("secret", props); + applicationContext.getEnvironment().getPropertySources().addFirst(pps); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringConfigAdapter.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringConfigAdapter.java new file mode 100644 index 0000000000..715fa92f6e --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringConfigAdapter.java @@ -0,0 +1,65 @@ +package org.stellar.anchor.platform.configurator; + +import java.util.Properties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.PropertiesPropertySource; +import org.stellar.anchor.api.exception.InvalidConfigException; + +/** + * The SpringConfigAdapter is the abstract base class of the configuration adapters that reads the + * values from the configuration and sets the Spring environment fields. + * + *

The subclass of the SpringConfigAdapter must override the sendToSpring() function to read from + * the configMap and sets up the Spring environment properly. + * + *

The SpringConfigAdapter is NOT thread-safe. + */ +public abstract class SpringConfigAdapter { + Properties props = new Properties(); + + protected void set(String name, boolean value) { + props.put(name, value); + } + + protected void set(String name, int value) { + props.put(name, value); + } + + protected void set(String name, String value) throws InvalidConfigException { + props.setProperty(name, value); + } + + protected void copy(ConfigMap config, String from, String to) throws InvalidConfigException { + copy(config, from, to, null); + } + + protected void copy(ConfigMap config, String from, String to, String defaultValue) + throws InvalidConfigException { + String value = config.getString(from); + if (value == null) { + if (defaultValue == null) { + throw new InvalidConfigException(String.format("config[%s] is not defined", from)); + } + value = defaultValue; + } + set(to, value); + } + + protected String get(String name) { + return props.getProperty(name); + } + + void updateSpringEnv(ConfigurableApplicationContext applicationContext, ConfigMap config) + throws InvalidConfigException { + props.clear(); + + updateSpringEnv(config); + + applicationContext + .getEnvironment() + .getPropertySources() + .addFirst(new PropertiesPropertySource(this.getClass().getSimpleName(), props)); + } + + abstract void updateSpringEnv(ConfigMap config) throws InvalidConfigException; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringFrameworkConfigurator.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringFrameworkConfigurator.java deleted file mode 100644 index 58c7c58916..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringFrameworkConfigurator.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.stellar.anchor.platform.configurator; - -import org.jetbrains.annotations.NotNull; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.PropertiesPropertySource; - -public class SpringFrameworkConfigurator extends AbstractConfigurator - implements ApplicationContextInitializer { - @Override - public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { - // Load and add the data access settings to Spring `Environment` - PropertiesPropertySource pps = createPrefixedPropertySource("spring"); - applicationContext.getEnvironment().getPropertySources().addFirst(pps); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java index 1613773d2a..30ac9ba477 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java @@ -15,7 +15,7 @@ import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.exception.ServerErrorException; import org.stellar.anchor.api.exception.UnprocessableEntityException; -import org.stellar.anchor.config.CirclePaymentObserverConfig; +import org.stellar.anchor.config.PaymentObserverConfig; import org.stellar.anchor.horizon.Horizon; import org.stellar.anchor.platform.payment.observer.PaymentListener; import org.stellar.anchor.platform.payment.observer.circle.model.CircleNotification; @@ -49,7 +49,7 @@ public class CirclePaymentObserverService { public CirclePaymentObserverService( OkHttpClient httpClient, - CirclePaymentObserverConfig circlePaymentObserverConfig, + PaymentObserverConfig circlePaymentObserverConfig, Horizon horizon, List observers) { this.httpClient = httpClient; diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java index b47a6eb454..17f2ab51ed 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java @@ -2,6 +2,8 @@ import static org.stellar.anchor.api.platform.HealthCheckStatus.GREEN; import static org.stellar.anchor.api.platform.HealthCheckStatus.RED; +import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.ALL; +import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.EVENT; import static org.stellar.anchor.util.ReflectionUtil.getField; import com.google.gson.annotations.SerializedName; @@ -169,7 +171,7 @@ public static Builder builder() { @Override public int compareTo(@NotNull HealthCheckable other) { - return other.getName().compareTo(other.getName()); + return this.getName().compareTo(other.getName()); } public static class Builder { @@ -215,8 +217,8 @@ public String getName() { } @Override - public List getTags() { - return List.of("all", "event"); + public List getTags() { + return List.of(ALL, EVENT); } @Override diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/HealthCheckService.java b/platform/src/main/java/org/stellar/anchor/platform/service/HealthCheckService.java index 4df8dd2cba..ec329be10d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/HealthCheckService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/HealthCheckService.java @@ -1,16 +1,26 @@ package org.stellar.anchor.platform.service; +import static org.stellar.anchor.util.Log.debugF; +import static org.stellar.anchor.util.Log.warnF; + import java.util.List; +import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Service; import org.stellar.anchor.api.platform.HealthCheckResponse; import org.stellar.anchor.healthcheck.HealthCheckProcessor; import org.stellar.anchor.healthcheck.HealthCheckable; @Service +@DependsOn("configManager") public class HealthCheckService { HealthCheckProcessor processor; public HealthCheckService(List checkables) { + checkables.forEach( + checkable -> debugF("{} is added to the health check list.", checkable.getName())); + if (checkables.size() == 0) { + warnF("No health-checkable services are found"); + } processor = new HealthCheckProcessor(checkables); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java b/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java index dc778be0c1..44b56f84e1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/MetricEmitterService.java @@ -119,7 +119,7 @@ public void stop() { @PostConstruct public void start() { if (metricConfig != null) { - if (metricConfig.isOptionalMetricsEnabled()) { + if (metricConfig.isExtrasEnabled()) { this.executor.scheduleAtFixedRate( new MetricEmitter(), 10, metricConfig.getRunInterval(), TimeUnit.SECONDS); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java b/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java new file mode 100644 index 0000000000..f137144a34 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java @@ -0,0 +1,56 @@ +package org.stellar.anchor.platform.service; + +import com.google.gson.Gson; +import java.util.ArrayList; +import java.util.List; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.api.sep.AssetInfo; +import org.stellar.anchor.asset.AssetService; +import org.stellar.anchor.asset.Assets; +import org.stellar.anchor.config.AssetsConfig; +import org.stellar.anchor.util.GsonUtils; +import org.stellar.anchor.util.Log; + +public class PropertyAssetsService implements AssetService { + static final Gson gson = GsonUtils.getInstance(); + Assets assets; + + public PropertyAssetsService(AssetsConfig assetsConfig) throws InvalidConfigException { + switch (assetsConfig.getType()) { + case JSON: + String assetsJson = assetsConfig.getValue(); + assets = gson.fromJson(assetsJson, Assets.class); + break; + case YAML: + default: + Log.infoF("assets type {} is not supported", assetsConfig.getType()); + throw new InvalidConfigException( + String.format("assets type %s is not supported", assetsConfig.getType())); + } + } + + public List listAllAssets() { + return new ArrayList<>(assets.getAssets()); + } + + public AssetInfo getAsset(String code) { + for (AssetInfo asset : assets.getAssets()) { + if (asset.getCode().equals(code)) { + return asset; + } + } + return null; + } + + public AssetInfo getAsset(String code, String issuer) { + if (issuer == null) { + return getAsset(code); + } + for (AssetInfo asset : assets.getAssets()) { + if (asset.getCode().equals(code) && issuer.equals(asset.getIssuer())) { + return asset; + } + } + return null; + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/ResourceReaderAssetService.java b/platform/src/main/java/org/stellar/anchor/platform/service/ResourceReaderAssetService.java deleted file mode 100644 index 6b92951b6c..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/service/ResourceReaderAssetService.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.stellar.anchor.platform.service; - -import org.stellar.anchor.asset.JsonAssetService; -import org.stellar.anchor.util.ResourceReader; - -public class ResourceReaderAssetService extends JsonAssetService { - public ResourceReaderAssetService(String assets) { - this(assets, new SpringResourceReader()); - } - - public ResourceReaderAssetService(String assets, ResourceReader resourceReader) { - super(resourceReader.readResourceAsString(assets)); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/SpringResourceReader.java b/platform/src/main/java/org/stellar/anchor/platform/service/SpringResourceReader.java deleted file mode 100644 index 81f88dc81a..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/service/SpringResourceReader.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.stellar.anchor.platform.service; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UncheckedIOException; -import org.apache.commons.io.IOUtils; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.stellar.anchor.util.ResourceReader; - -public class SpringResourceReader implements ResourceReader { - final ResourceLoader resourceLoader = new DefaultResourceLoader(); - - @Override - public String readResourceAsString(String path) { - return asString(resourceLoader.getResource(path)); - } - - @Override - public boolean checkResourceExists(String path) { - return resourceLoader.getResource(path).exists(); - } - - public String asString(Resource resource) { - try (Reader reader = new InputStreamReader(resource.getInputStream(), UTF_8)) { - return IOUtils.toString(reader); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/platform/src/main/resources/anchor-config-defaults.yaml b/platform/src/main/resources/anchor-config-defaults.yaml deleted file mode 100644 index 07f0346224..0000000000 --- a/platform/src/main/resources/anchor-config-defaults.yaml +++ /dev/null @@ -1,350 +0,0 @@ -# This file configures the anchor platform server. - -stellar: - anchor: - # Set the source of the configuration. The configuration source determines how the anchor platofrm - # server fetch the configuration values. - # - # If `config` is `in-memory`, this yaml file contains all settings for the server. - # - # `in-memory` is the only `config` value supported in MVP. - # - config: in-memory - # - # Sets how the application configuration values are read. - # - # If `type` is `config-spring-property`, the platform server will use the Spring's `@ConfigurationProperties` to - # read populate the configuration values. - # - # `config-spring-property` is the only `type` value supported in MVP. - app-config: - type: config-spring-property - settings: app-config # The absolute location of the configuration data in this yaml file - - # - # Sets how the application access the datastore. - # - # If the `type` is `data-spring-jdbc`, Spring Data JDBC will be used to read the application data. - # - # `data-spring-jdbc` is the only `type` value supported for now. - # - data-access: - type: data-spring-jdbc - settings: data-spring-jdbc-sqlite # The absolute location of the configuration data in this yaml file - #settings: data-spring-jdbc-h2 # use in-memory database. It cannot be shared between server and observer though. - #settings: data-spring-jdbc-local-postgres # use local postgres instance - #settings: data-spring-jdbc-aws-aurora-postgres # use AWS Aurora Postgres with IAM authentication - # - # Sets how the generates the logging information - # - # If the `type` is `logging-logback`, Logback will be used to generate the logs. - # - # `logging-logback` is the only `type` value supported for now. - # - logging: - type: logging-logback - settings: logging-logback-settings # The absolute location of the configuration data in this yaml file - -# -# Platform application configuration -# -app-config: - # shared / general application configurations - app: - # The stellar network passphrase used to access Horizon server. - # For Pubnet: use 'Public Global Stellar Network ; September 2015' - # For Testnet: use 'Test SDF Network ; September 2015' - stellarNetworkPassphrase: Test SDF Network ; September 2015 - - # The endpoint of the horizon server. - horizonUrl: https://horizon-testnet.stellar.org - - # The URL where the platform server can be accessed by the client. - hostUrl: http://localhost:8080 - - # The supported languages defined by RFC4646 (https://datatracker.ietf.org/doc/html/rfc4646) - # Currently, only "en" is supported. - languages: en - - # Location of the assets file. - # - # The format of the resource is specified as Table 6.1 of the Spring document located at: - # https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/resources.html - # - # Valid resource schemes are: ["classpath:", "file:", "http:", "https"] - assets: classpath:/assets-test.json - - # The secret key of JWT encryption - jwtSecretKey: ${JWT_SECRET} - - anchor-callback: - endpoint: http://localhost:8081 - - # These are secrets shared between Anchor and Platform that are used to safely communicate from `Platform->Anchor` - # and `Anchor->Platform`, specially when they are in different clusters. - # - # When the receiving part decodes the incoming request token, it needs to verify if the token is still valid (and not expired). - integration-auth: - # - # authType: used to determine how the authentication will happen between Anchor and Platform. Can be one of the following: - # NONE: no authentication is used - # API_KEY: the authentication is done using an API key added to the `X-Api-Key` header. - # JWT_TOKEN: the authentication is done using a JWT token added to the `Authorization` header. this token is generated from the secret key. - authType: NONE - # CallbackAPI requests (`Platform->Anchor`) will contain an authentication header whose token was built using this - # secret. The Anchor Backend will also store this same secret and use it to decode the incoming token to verify it - # came from the Platform. - platformToAnchorSecret: - # PlatformAPI requests (`Anchor->Platform`) will contain an authentication header whose token was built using this - # secret. The Platform Server will use this secret to decode the incoming token to verify it came from the Anchor. - anchorToPlatformSecret: - # Expiration time, in milliseconds, that will be used to build and validate the JWT tokens - expirationMilliseconds: - - # sep-1 - sep1: - enabled: true - # - # The stellarFile is the location where to retrieve the content as the return value - # of the `/.well-known/stellar.toml`. - # - # The format of the resource is specified as Table 6.1 of the Spring document located at: - # https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/resources.html - # - stellarFile: classpath:sep1/stellar-wks.toml - - # sep-10 - sep10: - enabled: true - - # The `home_domain` property of SEP-10. https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#request - homeDomain: localhost:8080 - - # Set if the client attribution is required - clientAttributionRequired: false - - # Set the white list of the client domain. The domains are comma-separated - # Although it is allowed to set both the allow-list and the deny-list, it is not helpful if both are set. - clientAttributionAllowList: lobstr.co,preview.lobstr.co - - # Set the black list of the client domain. - # clientAttributionDenyList: - - # Set the authentication challenge transaction timeout. An expired signed transaction will be rejected. - # This is the timeout period the client must finish the authentication process. (ie: sign and respond the challenge - # transaction). - # - # The value is in seconds. - authTimeout: 900 - - # Set the timeout of the authenticated JSON Web Token. An expired JWT will be rejected. - # This is the timeout period after the client has authenticated. - # - # The value is in seconds. - jwtTimeout: 86400 - - # The private key of the SEP-10 challenge. - # We highly recommend that this private key should not be used to sign any transactions to submit to the Stellar - # network. - signingSeed: ${SEP10_SIGNING_SEED} - - # The list of omnibus accounts (comma separated). - # The detail of the SEP-10 omnibus account is described at: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#memos - # omnibusAccountList: | - # GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAV, - # GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAA - omnibusAccountList: - - # Require authenticating clients to be in the omnibusAccountList - requireKnownOmnibusAccount: false - - # sep-12 - sep12: - enabled: true - # The callback API endpoint. - customerIntegrationEndpoint: http://localhost:8081 - - # sep-24 - sep24: - enabled: true - # Set the timeout of the JSON Web Token returned with the embedded interactive url of the SEP-24 process. - # An expired JWT will be rejected. - # - # If the interactive flow needs to access the platform server, the interactive process must finish within - # the specified timeout period. - interactiveJwtExpiration: 3600 - - # The interactive URL where the platform server will redirect to start the SEP-24 interactive flow. - interactiveUrl: http://localhost:8081/sep24/interactive - - # sep-31 - sep31: - enabled: true - - # The /fee callback API endpoint. - feeIntegrationEndPoint: http://localhost:8081 - - # The /unique_address callback API endpoint. - uniqueAddressIntegrationEndPoint: http://localhost:8081 - - # - # paymentType: used to determine how amount_in is calculated from amount in the POST /transaction call - # Possible values: STRICT_SEND or STRICT_RECEIVE. default=STRICT_SEND - # STRICT_SEND: amount_in = amount - # STRICT_RECEIVE: amount_in = amount + fee - paymentType: STRICT_SEND - - # - # depositInfoGeneratorType: used to choose how the SEP-31 deposit information will be generated, which includes the - # deposit address, memo and memo type. - # Possible values: - # self: the memo and memo type are generated in the local code, and the distribution account is used for the deposit address. - # circle: the memo and memo type are generated through Circle API, as well as the deposit address. - # api: the memo and memo type are generated through calling the anchor's GET /unique_address endpoint. - depositInfoGeneratorType: self # self or circle or api - - # sep-38 - sep38: - enabled: true - - # The callback API endpoint. - quoteIntegrationEndPoint: http://localhost:8081 - - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: ${CIRCLE_API_KEY} # circle API key - - payment-gateway: - # - # Payment Circle configurations - # - circle: - name: "circle" - enabled: true - - # - # Payment Stellar configurations - # - stellar: - enabled: false - name: "stellar" - horizonUrl: https://horizon-testnet.stellar.org - secretKey: ${PAYMENT_GATEWAY_STELLAR_SECRET_KEY} # stellar account secret key - - circle-payment-observer: - enabled: true - horizonUrl: https://horizon-testnet.stellar.org - stellarNetwork: TESTNET # TESTNET or PUBLIC - trackedWallet: all - - event: - # If enabled, publish Events to a queue (publisherType) - # publisherType - the type of queue to use for event publishing - # currently supported publisherType values: kafka, sqs - enabled: true - publisherType: kafka - - metrics-service: - optionalMetricsEnabled: false # optional metrics that periodically query the database - runInterval: 30 # interval to query the database to generate the optional metrics - - kafka.publisher: - # kafkaBootstrapServer - the Kafka server used to bootstrap setup - # If useSingleQueue, all events are published to a single queue - # (specified in eventTypeToQueue.all) - # eventTypeToQueue - a map of the event type to the queue name messages are published to - # useIAM - IAM authentication for AWS MSK - # NOTE: MSK - use port 9098 for access from within AWS and port 9198 for public access. - # https://docs.aws.amazon.com/msk/latest/developerguide/port-info.html - # NOTE: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables need to be set - bootstrapServer: localhost:29092 - #bootstrapServer: b-1-public.democluster1.w7j4hi.c25.kafka.us-east-1.amazonaws.com:9198 # AWS MSK example - useIAM: false - useSingleQueue: false - eventTypeToQueue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error - - sqs.publisher: - # region - AWS region for the SQS queue - # accessKey - AWS access key used to publish events to SQS - # secretKey - AWS secret key to be used with the accessKey - # eventTypeToQueue - a map of the event type to the queue name messages are published to - # NOTE: The SQS FIFO queues should be pre-created in AWS (Anchor Platform will not create them) - region: us-east-1 - useSingleQueue: false - eventTypeToQueue: - all: sdf_dev_ap_event_single_queue.fifo - quote_created: sdf_dev_ap_event_quote_created.fifo - transaction_created: sdf_dev_ap_event_transaction_created.fifo - transaction_status_changed: sdf_dev_ap_event_transaction_status_changed.fifo - transaction_error: sdf_dev_ap_event_transaction_error.fifo - accessKey: ${SQS_PUBLISHER_ACCESS_KEY} - secretKey: ${SQS_PUBLISHER_SECRET_KEY} - -# -# Spring Data JDBC settings -# -data-spring-jdbc-sqlite: - spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect - spring.jpa.hibernate.ddl-auto: update - spring.jpa.generate-ddl: true - spring.jpa.hibernate.show_sql: false - spring.datasource.url: jdbc:sqlite:anchor-proxy.db - spring.datasource.driver-class-name: org.sqlite.JDBC - spring.datasource.max-active: 2 # For SQLite, set this to 2 to avoid database file lock exception, while letting both sep-server and stellar-observer use it. - spring.datasource.initial-size: 2 # For SQLite, set this to 2 to avoid database file lock exception, while letting both sep-server and stellar-observer use it. - spring.datasource.username: ${SQLITE_USERNAME} - spring.datasource.password: ${SQLITE_PASSWORD} - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.enabled: false # certain features that flyway uses (ex: addForeignKeyConstraint) are not supported by sqlite - -# settings for aurora postgres connection using IAM for authentication -# NOTE: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables need to be set -data-spring-jdbc-aws-aurora-postgres: - spring.jpa.database: POSTGRESQL - spring.jpa.show-sql: false - spring.datasource.driver-class-name: org.postgresql.Driver - spring.datasource.type: org.stellar.anchor.platform.databaseintegration.IAMAuthDataSource - spring.datasource.url: jdbc:postgresql://database-aurora-iam-instance-1.chizvyczscs2.us-east-1.rds.amazonaws.com:5432/anchorplatform - spring.datasource.username: anchorplatform1 - spring.datasource.hikari.max-lifetime: 840000 # 14 minutes because IAM tokens are valid for 15min - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.user: postgres - spring.flyway.password: password # can use a token value if authenticating via IAM - spring.flyway.url: jdbc:postgresql://database-aurora-iam-instance-1.chizvyczscs2.us-east-1.rds.amazonaws.com:5432/anchorplatform - spring.flyway.locations: classpath:/db/migration - -data-spring-jdbc-local-postgres: - spring.jpa.database: POSTGRESQL - spring.jpa.show-sql: false - spring.datasource.driver-class-name: org.postgresql.Driver - spring.datasource.url: jdbc:postgresql://localhost:5431/anchorplatform - spring.datasource.username: ${POSTGRES_USERNAME} - spring.datasource.password: ${POSTGRES_PASSWORD} - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.user: ${POSTGRES_USERNAME} - spring.flyway.password: ${POSTGRES_PASSWORD} - spring.flyway.url: jdbc:postgresql://localhost:5431/anchorplatform - spring.flyway.locations: classpath:/db/migration - -data-spring-jdbc-h2: - spring.datasource.url: jdbc:h2:mem:test - spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.H2Dialect - spring.flyway.enabled: false - -# -# Spring framework configurations -# -spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 \ No newline at end of file diff --git a/platform/src/main/resources/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml new file mode 100644 index 0000000000..c34ed89977 --- /dev/null +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -0,0 +1,489 @@ +#################################################################################################### +## Anchor Platform - Default Configuration +## +## Values not defined in the user config use the default values defined here. +#################################################################################################### + +version: 1 + +############################## +## Application Configuration +############################## + +## @param: host_url +## @type: string +## @supported_prefixes: http:, https: +## The anchor platform host url. +# +host_url: http://localhost:8080 + +stellar_network: + ## @param: network + ## Whether to use testnet (TESTNET) or pubnet (PUBNET) + # + network: TESTNET + + ## @param: network_passphrase + ## @type: string + ## The stellar network passphrase used to access Horizon server. + ## For pubnet: use 'Public Global Stellar Network ; September 2015' + ## For testnet: use 'Test SDF Network ; September 2015' + # + network_passphrase: Test SDF Network ; September 2015 + + ## @param: horizon_url + ## @type: string + ## A horizon server endpoint. + ## @required_secret: + ## PAYMENT_GATEWAY_STELLAR_SECRET_KEY - stellar account secret key + # + horizon_url: https://horizon-testnet.stellar.org + + +callback_api: + ## @param: baseUrl + ## @type: string + ## @supported_prefixes: http:, https: + ## The base URL of the Anchor Backend server that implements the callback API endpoints described in the + ## "Callbacks API.yml" spec, which are: `/customer`, `/fee`, `/rate, and `/unique_address`. + # + base_url: http://localhost:8081 + + ## Authentication config for Anchor server and Anchor Platform server to safely communicate, + ## particularly when housed in different clusters. + ## The receiving party should verify that an incoming request token is still valid. + # + auth: + ## @param: type + ## @supported_values: NONE, API_KEY, JWT_TOKEN + ## NONE: no authentication is used + ## API_KEY: The authentication is done using an API key added to the `X-Api-Key` header. + ## JWT_TOKEN: The authentication is done using a JWT token added to the `Authorization` + ## header. This token is generated from the secret key. + ## + ## If the type is not NONE, the secret must be set by the following environment variable: `secret.callback_api.auth_secret` + ## + ## The value of the secret depends on the type specified. + ## If type is JWT_TOKEN – the secret key used to encode/decode the JWT token. + ## If type is API_KEY – the secret is the API_KEY itself. + ## Secrets are shared on both sides (Platform and Anchor) and are used in CallbackAPI + ## requests (`Platform->Anchor`) so the Anchor can ensure the party making the request + ## is the Platform + # + type: NONE + + ## @param: expiration_milliseconds + ## @type: int + ## Expiration time, in milliseconds, that will be used to build and validate the JWT tokens + # + expiration_milliseconds: 30000 + +platform_api: + auth: + ## @param: type + ## @supported_values: NONE, API_KEY, JWT_TOKEN + ## NONE: no authentication is used + ## API_KEY: The authentication is done using an API key added to the `X-Api-Key` header. + ## JWT_TOKEN: The authentication is done using a JWT token added to the `Authorization` + ## header. This token is generated from the secret key. + ## + ## If the type is not NONE, the secret must be set by the following environment variable: `secret.platform_api.auth_secret` + ## + ## The value of the secret depends on the type specified. + ## If type is JWT_TOKEN – the secret key used to encode/decode the JWT token. + ## If type is API_KEY – the secret is the API_KEY itself. + ## Secrets are shared on both sides (Platform and Anchor) and are used in PlatformAPI + ## requests (`Anchor->Platform`) so the Platform can ensure the party making the request + ## is the Anchor. + # + type: NONE + + ## @param: expiration_milliseconds + ## @type: int + ## Expiration time, in milliseconds, that will be used to build and validate the JWT tokens + # + expiration_milliseconds: 30000 + +payment_observer: + ## @param: enabled + ## @type: bool + ## Whether to enable a circle payment observer + # + enabled: false + + ## @param: circle_url + ## @type: string + ## Circle api endpoint. + # + circle_url: https://api-sandbox.circle.com + + + ## @param: tracked_wallet + ## @type: string + ## The id of the wallet to track. 'all' indicates that events for all wallets are tracked. + # + tracked_wallet: all + + +## @param: languages +## @supported_values: en +## The supported languages defined by RFC4646 (https://datatracker.ietf.org/doc/html/rfc4646) +# +languages: en + +logging: + ## @param: level + ## @type: string + ## The root logger logging level + level: INFO + + ## @param: level + ## @type: string + ## The "org.stellar" logger logging level. + stellar_level: INFO + +###################### +## SEP configuration +###################### +sep1: + + ## @param: enabled + ## @type: bool + ## Whether to enable SEP-1 for this instance of AP server. + # + enabled: false + + toml: + ## @param: type + ## @type: string + ## @supported_values: + ## `string`: value contains the content of the stellar toml file + ## `file`: value contains the path to the stellar toml file + ## `url`: value contains the url to the content of the stellar toml file + type: string + + ## @param: value + ## @type: string + # + value: | + + +sep10: + + ## @param: enabled + ## @type: bool + ## Whether to enable SEP-10 for this instance of AP server. + ## If true, @required_secrets: SEP10_SIGNING_SEED + # + enabled: false + + ## If `enabled` is set to True, the following two secrets must be set via the environment + ## variables. + ## `secret.sep10_signing_seed`: The private key for signing challenge transactions. + ## `secret.sep10_jwt_secret`: The JWT encryption key. + # + + ## @param: home_domain + ## @type: string + ## The `home_domain` property of SEP-10. https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#request + # + home_domain: localhost:8080 + + ## @param: client_attribution_required + ## @type: bool + ## Set if the client attribution is required + # + client_attribution_required: false + + ## @param: client_attribution_allow_list + ## Set the white list of the client domain. The domains are comma-separated. + # + client_attribution_allow_list: lobstr.co,preview.lobstr.co + + ## @param: client_attribution_deny_list + ## Set the black list of the client domain. The domains are comma-separated. + # + client_attribution_deny_list: + + ## @param: auth_timeout + ## @type: integer + ## Set the authentication challenge transaction timeout in seconds. An expired signed transaction will be rejected. + ## This is the timeout period the client must finish the authentication process. (ie: sign and respond the challenge + ## transaction). + # + auth_timeout: 900 + + ## @param: jwt_timeout + ## @type: integer + ## Set the timeout in seconds of the authenticated JSON Web Token. An expired JWT will be rejected. + ## This is the timeout period after the client has authenticated. + # + jwt_timeout: 86400 + + ## @param: omnibus_account_list + ## A comma-separated list of omnibus accounts (comma separated). + ## The SEP-10 omnibus account is described at: + ## https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#memos + # + omnibus_account_list: + + ## @param: require_known_omnibus_account + ## @type: bool + ## Whether to require authenticating clients to be in the omnibusAccountList + # + require_known_omnibus_account: false + +sep12: + + ## @param: enabled + ## @type: bool + ## Whether to enable SEP-12 for this instance of AP server. + # + enabled: false + +sep24: + + ## SEP24 in experimental. + + ## @param: enabled + ## @type: bool + ## Whether to enable SEP-24 for this instance of AP server. + # + enabled: false + + ## @param: interactive_jwt_expiration + ## @type: integer + ## Set the timeout in seconds of the JSON Web Token returned with the embedded interactive url of the SEP-24 process. + ## An expired JWT will be rejected. + ## + ## If the interactive flow needs to access the platform server, the interactive process must finish within + ## the specified timeout period. + # + interactive_jwt_expiration: 3600 + + ## @param: interactive_url + ## @type: string + ## The interactive URL where the platform server will redirect to start the SEP-24 interactive flow. + # + interactive_url: http://localhost:8080/sep24/interactive + +sep31: + + ## @param: enabled + ## @type: bool + ## Whether to enable SEP-31 for this instance of AP server. + # + enabled: false + + ## @param: payment_type + ## @default: STRICT_SEND + ## Used to determine how amount_in is calculated from amount in the POST /transaction call + ## @supported_values: + ## STRICT_SEND: amount_in = amount + ## STRICT_RECEIVE: amount_in = amount + fee + payment_type: STRICT_SEND + + ## @param: deposit_info_generator_type + ## @default: self + ## Used to choose how the SEP-31 deposit information will be generated, which includes the + ## deposit address, memo and memo type. + ## @supported_values: + ## self: the memo and memo type are generated in the local code, and the distribution account is used for the deposit address. + ## circle: the memo and memo type are generated through Circle API, as well as the deposit address. + ## api: the memo and memo type are generated through calling the anchor's GET /unique_address endpoint. + # + deposit_info_generator_type: self + +sep38: + ## @param: enabled + ## @type: bool + ## Whether to enable SEP-38 for this instance of AP server. + # + enabled: true + + +########################## +## Metrics Configuration +########################## +metrics: + ## @param: enabled + ## @type: bool + ## If true, enable metrics. + # + enabled: false + + ## @param: extras_enabled + ## @type: bool + ## If true, enable extra metrics. + # + extras_enabled: false + + ## @param: run_interval + ## @type: integer + ## Interval in seconds to query the database to generate metrics + # + run_interval: 30 + + +######################### +## Events Configuration. +######################### +## The events being sent from the platform are the ones described in the `Events Schema.yml` file +events: + + ## @param: enabled + ## @type: bool + ## Whether to enable events. + # + enabled: false + + ## @param: publisher_type + ## @supported_values: `kafka`, `sqs` + ## The type of queue to use for event publishing + ## + # + publisher_type: kafka + + + options: + + ## @param: bootstrap_server + ## @type: string + ## The Kafka server used to bootstrap setup + ## For MSK, use port 9098 for access from within AWS and port 9198 for public access and specify + ## AWS credentials. + ## https://docs.aws.amazon.com/msk/latest/developerguide/port-info.html + # + bootstrap_server: localhost:29092 + + ## @param: use_IAM + ## @type: boolean + ## Use IAM authentication for AWS MSK or AWS SQS. + ## SQS FIFO queues should be pre-created in AWS (Anchor Platform will not create them) + ## If true, @required_secrets: + ## AWS_ACCESS_KEY_ID + ## AWS_SECRET_ACCESS_KEY + # + use_IAM: false + + ## @param: region + ## @type: string + ## AWS region for the queue. Can also be defined as environment variable: AWS_REGION + # + aws_region: us-east-1 + + ## @param: use_single_queue + ## @type: boolean + ## If true, all events are published to a single queue (specified in eventTypeToQueue.all) + use_single_queue: false + + ## @param: event_type_to_queue + ## @type: map + ## Mapping of the event type to the queue name that messages are published to + event_type_to_queue: + all: ap_event_single_queue + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + + +######################### +## Assets Configuration +######################### +## Assets are empty by default. Please see `integration-test.anchor-config.yaml` for reference. +## Accepts file reference (eg. 'file:assets.yaml') or in-line definition. +assets: + ## @param: type + ## @supported_values: `json` + ## Describe the asset definition. + # + type: json + + ## @param: value + ## @type: string + ## Example of yaml definition: + ## { "assets": + ## [{ + ## "schema": "stellar" + ## "code": "USDC" + ## "issuer": "G..." + ## "distribution_account": "G..." + ## .... + ## }] + ## } + # + value: "" + + +################################ +## Data Configuration +################################ +data: + + ## DB credentials are specified in @environment_variables DATA_USERNAME, DATA_PASSWORD + + ## @param: type + ## @supported_values: + ## `h2` (in-memory), `sqlite` (local), `postgres` (local), `aurora` (postgres on AWS) + ## Type of storage. + ## If this is set to `aurora`, + ## @required_secrets: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + # + type: h2 + + ## @param: url + ## @type: string + ## Location of the database + # + url: jdbc:h2:mem:anchor-platform + + ## @param: username + ## @type: string + ## The username for database connection + # + username: + + + ## @param: username + ## @type: string + ## The password for database connection + # + password: + + ## @param: initial_connection_pool_size + ## @type: integer + ## Initial number of connections + ## For `sqlite`, set this to 1 to avoid database file lock exception + # + initial_connection_pool_size: 1 + + ## @param: max_active_connections + ## @type: integer + ## Maximum number of db active connections + ## For `sqlite`, set this to 1 to avoid database file lock exception + # + max_active_connections: 10 + + ## @param: flyway_enabled + ## @type: bool + ## Whether to enable flyway. + ## Should be disabled for `sqlite` because certain features that flyway uses + ## (ex: addForeignKeyConstraint) are not supported. + # + flyway_enabled: false + + ## @param: flyway_enabled + ## @type: string + ## Location on disk where migrations are stored if flyway is enabled. + # + flyway_location: /db/migration + + ## @param: hikari_max_lifetime + ## @type: integer + ## Maximum connection time before expiration + ## Only valid when database `type` is `aurora`. + ## Recommended setting is 14 minutes because IAM tokens are valid for 15 min. + # + # hikari_max_lifetime: 840000 + diff --git a/platform/src/main/resources/config/anchor-config-schema-v1.yaml b/platform/src/main/resources/config/anchor-config-schema-v1.yaml new file mode 100644 index 0000000000..9b5f11f27c --- /dev/null +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -0,0 +1,59 @@ +assets.type: +assets.value: +callback_api.auth.expiration_milliseconds: +callback_api.auth.type: +callback_api.base_url: +data.flyway_enabled: +data.flyway_location: +data.initial_connection_pool_size: +data.max_active_connections: +data.password: +data.type: +data.url: +data.username: +events.enabled: +events.options.aws_region: +events.options.bootstrap_server: +events.options.event_type_to_queue.all: +events.options.event_type_to_queue.quote_created: +events.options.event_type_to_queue.transaction_created: +events.options.event_type_to_queue.transaction_error: +events.options.event_type_to_queue.transaction_status_changed: +events.options.use_iam: +events.options.use_single_queue: +events.publisher_type: +host_url: +languages: +logging.level: +logging.stellar_level: +metrics.enabled: +metrics.extras_enabled: +metrics.run_interval: +payment_observer.circle_url: +payment_observer.enabled: +payment_observer.tracked_wallet: +platform_api.auth.type: +platform_api.auth.expiration_milliseconds: +sep1.enabled: +sep1.toml.type: +sep1.toml.value: +sep10.auth_timeout: +sep10.client_attribution_allow_list: +sep10.client_attribution_deny_list: +sep10.client_attribution_required: +sep10.enabled: +sep10.home_domain: +sep10.jwt_timeout: +sep10.omnibus_account_list: +sep10.require_known_omnibus_account: +sep12.enabled: +sep24.enabled: +sep24.interactive_jwt_expiration: +sep24.interactive_url: +sep31.deposit_info_generator_type: +sep31.enabled: +sep31.payment_type: +sep38.enabled: +stellar_network.horizon_url: +stellar_network.network: +stellar_network.network_passphrase: \ No newline at end of file diff --git a/platform/src/main/resources/example.env b/platform/src/main/resources/example.env index 5146d4df58..09bdb88ccb 100644 --- a/platform/src/main/resources/example.env +++ b/platform/src/main/resources/example.env @@ -1,3 +1,10 @@ +#################################################################################################### +## Example Secrets +## +## This file serves as an example only. These values should be injected into environment variables +## to be used by Anchor Platform. +#################################################################################################### + # REQUIRED - The secret key of JWT encryption JWT_SECRET=secret @@ -13,21 +20,14 @@ ANCHOR_TO_PLATFORM_SECRET=myAnchorToPlatformSecret # OPTIONAL (only if using Circle) CIRCLE_API_KEY=QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== -# REQUIRED - Stellar account secret key +# OPTIONAL - Stellar account secret key PAYMENT_GATEWAY_STELLAR_SECRET_KEY=secret -# OPTIONAL (only if using SQS for event messaging) - SQS secrets -SQS_PUBLISHER_ACCESS_KEY=foo2 -SQS_PUBLISHER_SECRET_KEY=bar2 - -# OPTIONAL (only if using Sqlite as a database) - Sqlite secrets -SQLITE_USERNAME=admin -SQLITE_PASSWORD=admin - -# OPTIONAL (only if using Postgres as a database) - Postgres secrets -POSTGRES_USERNAME=postgres -POSTGRES_PASSWORD=password +# OPTIONAL - used for storage definition +DATA_USERNAME=admin +DATA_PASSWORD=admin +# OPTIONAL (only if using AWS credentials) AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION=us-east-1 diff --git a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt new file mode 100644 index 0000000000..29d789cc87 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt @@ -0,0 +1,71 @@ +package org.stellar.anchor + +import java.nio.file.Files +import java.nio.file.Path +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.stellar.anchor.platform.config.PropertySep1Config +import org.stellar.anchor.platform.config.Sep1ConfigTest +import org.stellar.anchor.sep1.Sep1Service + +class Sep1ServiceTest { + lateinit var sep1: Sep1Service + val stellarToml = + """ + ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ] + VERSION = "0.1.0" + SIGNING_KEY = "GBDYDBJKQBJK4GY4V7FAONSFF2IBJSKNTBYJ65F5KCGBY2BIGPGGLJOH" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + + WEB_AUTH_ENDPOINT = "http://localhost:8080/auth" + KYC_SERVER = "http://localhost:8080/sep12" + TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" + DIRECT_PAYMENT_SERVER = "http://localhost:8080/sep31" + ANCHOR_QUOTE_SERVER = "http://localhost:8080/sep38" + + [[CURRENCIES]] + code = "SRT" + issuer = "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B" + status = "test" + is_asset_anchored = false + anchor_asset_type = "other" + desc = "A fake anchored asset to use with this example anchor server." + + [DOCUMENTATION] + ORG_NAME = "Stellar Development Foundation" + ORG_URL = "https://stellar.org" + ORG_DESCRIPTION = "SEP 24 reference server." + ORG_KEYBASE = "stellar.public" + ORG_TWITTER = "StellarOrg" + ORG_GITHUB = "stellar" + """.trimIndent() + + @Test + fun `test Sep1Service reading toml from inline string`() { + + val config = PropertySep1Config(true, "string", stellarToml) + sep1 = Sep1Service(config) + assertEquals(sep1.stellarToml, stellarToml) + } + + @Test + fun `test Sep1Service reading toml from file`() { + val config = PropertySep1Config(true, "file", Sep1ConfigTest.getTestTomlAsFile()) + sep1 = Sep1Service(config) + assertEquals(sep1.stellarToml, Files.readString(Path.of(Sep1ConfigTest.getTestTomlAsFile()))) + } + + @Test + fun `test Sep1Service reading toml from url`() { + val mockServer = MockWebServer() + mockServer.start() + val mockAnchorUrl = mockServer.url("").toString() + mockServer.enqueue(MockResponse().setBody(stellarToml)) + + val config = PropertySep1Config(true, "url", mockAnchorUrl) + sep1 = Sep1Service(config) + assertEquals(sep1.stellarToml, stellarToml) + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentBeansTest.kt similarity index 86% rename from platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/platform/PaymentBeansTest.kt index 3004e37517..64f048c422 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentBeansTest.kt @@ -16,7 +16,7 @@ import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAcco import org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentStreamerCursorStore -class PaymentConfigTest { +class PaymentBeansTest { @MockK private lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore @@ -34,14 +34,14 @@ class PaymentConfigTest { @Test fun test_stellarPaymentObserverService_failure() { val assetService: AssetService = ResourceJsonAssetService("test_assets.json") - val paymentConfig = PaymentConfig() + val paymentBeans = PaymentBeans() val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) // assetService is null var ex = assertThrows { - paymentConfig.stellarPaymentObserverService(null, null, null, null, null) + paymentBeans.stellarPaymentObserverService(null, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) @@ -51,7 +51,7 @@ class PaymentConfigTest { every { mockEmptyAssetService.listAllAssets() } returns null ex = assertThrows { - paymentConfig.stellarPaymentObserverService(mockEmptyAssetService, null, null, null, null) + paymentBeans.stellarPaymentObserverService(mockEmptyAssetService, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) @@ -61,7 +61,7 @@ class PaymentConfigTest { every { mockStellarLessAssetService.listAllAssets() } returns listOf() ex = assertThrows { - paymentConfig.stellarPaymentObserverService( + paymentBeans.stellarPaymentObserverService( mockStellarLessAssetService, null, null, @@ -75,7 +75,7 @@ class PaymentConfigTest { // paymentListeners is null ex = assertThrows { - paymentConfig.stellarPaymentObserverService(assetService, null, null, null, null) + paymentBeans.stellarPaymentObserverService(assetService, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) @@ -83,7 +83,7 @@ class PaymentConfigTest { // paymentListeners is empty ex = assertThrows { - paymentConfig.stellarPaymentObserverService(assetService, listOf(), null, null, null) + paymentBeans.stellarPaymentObserverService(assetService, listOf(), null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) @@ -91,7 +91,7 @@ class PaymentConfigTest { // paymentStreamerCursorStore is null ex = assertThrows { - paymentConfig.stellarPaymentObserverService( + paymentBeans.stellarPaymentObserverService( assetService, mockPaymentListeners, null, @@ -106,7 +106,7 @@ class PaymentConfigTest { every { paymentStreamerCursorStore.load() } returns null ex = assertThrows { - paymentConfig.stellarPaymentObserverService( + paymentBeans.stellarPaymentObserverService( assetService, mockPaymentListeners, paymentStreamerCursorStore, @@ -121,7 +121,7 @@ class PaymentConfigTest { @Test fun test_givenGoodManager_whenConstruct_thenOk() { // success! - val paymentConfig = PaymentConfig() + val paymentBeans = PaymentBeans() val assetService: AssetService = ResourceJsonAssetService("test_assets.json") val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) @@ -132,7 +132,7 @@ class PaymentConfigTest { val mockAppConfig = mockk() every { mockAppConfig.horizonUrl } returns "https://horizon-testnet.stellar.org" assertDoesNotThrow { - paymentConfig.stellarPaymentObserverService( + paymentBeans.stellarPaymentObserverService( assetService, mockPaymentListeners, paymentStreamerCursorStore, diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/callback/PlatformIntegrationHelperTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/callback/PlatformIntegrationHelperTest.kt index c249a997ab..becbb39c75 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/callback/PlatformIntegrationHelperTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/callback/PlatformIntegrationHelperTest.kt @@ -8,9 +8,10 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.EnumSource import org.stellar.anchor.auth.AuthHelper +import org.stellar.anchor.auth.AuthType +import org.stellar.anchor.auth.AuthType.* import org.stellar.anchor.auth.JwtService import org.stellar.anchor.auth.JwtToken -import org.stellar.anchor.config.IntegrationAuthConfig.AuthType class PlatformIntegrationHelperTest { companion object { @@ -28,7 +29,7 @@ class PlatformIntegrationHelperTest { @EnumSource(AuthType::class) fun test_getRequestBuilder(authType: AuthType) { when (authType) { - AuthType.JWT_TOKEN -> { + JWT_TOKEN -> { // Mock calendar to guarantee the jwt token format val calendarSingleton = Calendar.getInstance() val currentTimeMilliseconds = calendarSingleton.timeInMillis @@ -59,7 +60,7 @@ class PlatformIntegrationHelperTest { val wantRequest = wantRequestBuilder.url(TEST_HOME_DOMAIN).get().build() assertEquals(wantRequest.headers, gotRequest.headers) } - AuthType.API_KEY -> { + API_KEY -> { val authHelper = AuthHelper.forApiKey("secret") val gotRequestBuilder = PlatformIntegrationHelper.getRequestBuilder(authHelper) val gotRequest = gotRequestBuilder.url(TEST_HOME_DOMAIN).get().build() @@ -68,7 +69,7 @@ class PlatformIntegrationHelperTest { val wantRequest = wantRequestBuilder.url(TEST_HOME_DOMAIN).get().build() assertEquals(wantRequest.headers, gotRequest.headers) } - AuthType.NONE -> { + NONE -> { val authHelper = AuthHelper.forNone() val gotRequestBuilder = PlatformIntegrationHelper.getRequestBuilder(authHelper) val gotRequest = gotRequestBuilder.url(TEST_HOME_DOMAIN).get().build() diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/AppConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/AppConfigTest.kt index 27e61d18c9..063aafa961 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/AppConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/AppConfigTest.kt @@ -3,7 +3,7 @@ package org.stellar.anchor.platform.config import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.fail -import org.junit.jupiter.api.* +import org.junit.jupiter.api.Test import org.springframework.validation.BindException import org.springframework.validation.ValidationUtils @@ -11,8 +11,6 @@ open class AppConfigTest { @Test fun testAppConfigValid() { val appConfig = PropertyAppConfig() - appConfig.jwtSecretKey = "secret" - appConfig.assets = "test_assets.json" appConfig.horizonUrl = "https://horizon-testnet.stellar.org" appConfig.stellarNetworkPassphrase = "Test SDF Network ; September 2015" appConfig.hostUrl = "http://localhost:8080" @@ -24,38 +22,9 @@ open class AppConfigTest { } } - @Test - fun testAppConfigMissingSecret() { - val appConfig = PropertyAppConfig() - appConfig.assets = "test_assets.json" - appConfig.horizonUrl = "https://horizon-testnet.stellar.org" - appConfig.stellarNetworkPassphrase = "Test SDF Network ; September 2015" - appConfig.hostUrl = "http://localhost:8080" - val errors = BindException(appConfig, "appConfig") - ValidationUtils.invokeValidator(appConfig, appConfig, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "empty-jwtSecretKey") } - } - - @Test - fun testAppConfigBadAsset() { - val appConfig = PropertyAppConfig() - appConfig.jwtSecretKey = "secret" - appConfig.assets = "test_assets_does_not_exist.json" - appConfig.horizonUrl = "https://horizon-testnet.stellar.org" - appConfig.stellarNetworkPassphrase = "Test SDF Network ; September 2015" - appConfig.hostUrl = "http://localhost:8080" - val errors = BindException(appConfig, "appConfig") - ValidationUtils.invokeValidator(appConfig, appConfig, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "doesNotExist-assets") } - } - @Test fun testAppConfigBadHorizonUrl() { val appConfig = PropertyAppConfig() - appConfig.jwtSecretKey = "secret" - appConfig.assets = "test_assets.json" appConfig.horizonUrl = "not-a-url" appConfig.stellarNetworkPassphrase = "Test SDF Network ; September 2015" appConfig.hostUrl = "http://localhost:2222" diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt index 617ca0fa43..e1e8e6e50c 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt @@ -10,7 +10,7 @@ open class EventConfigTest { @Test fun testDisabledEventConfig() { // events are disabled - val eventConfig = PropertyEventConfig(null, null) + val eventConfig = PropertyEventConfig(null) eventConfig.isEnabled = false val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) @@ -20,12 +20,12 @@ open class EventConfigTest { @Test fun testEnabledForKafka() { // Events correctly configured for kafka - val kafkaConfig = PropertyKafkaConfig() + val kafkaConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) kafkaConfig.isUseSingleQueue = true kafkaConfig.bootstrapServer = "localhost:29092" kafkaConfig.eventTypeToQueue = HashMap() - val eventConfig = PropertyEventConfig(kafkaConfig, PropertySqsConfig()) + val eventConfig = PropertyEventConfig(kafkaConfig) eventConfig.isEnabled = true eventConfig.publisherType = "kafka" val errors = BindException(eventConfig, "eventConfig") @@ -36,14 +36,14 @@ open class EventConfigTest { @Test fun testEnabledForSqs() { // Events correctly configured for sqs - val sqsConfig = PropertySqsConfig() + val sqsConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) sqsConfig.accessKey = "accessKey" sqsConfig.secretKey = "secretKey" sqsConfig.isUseSingleQueue = true sqsConfig.region = "region" sqsConfig.eventTypeToQueue = HashMap() - val eventConfig = PropertyEventConfig(PropertyKafkaConfig(), sqsConfig) + val eventConfig = PropertyEventConfig(sqsConfig) eventConfig.isEnabled = true eventConfig.publisherType = "sqs" val errors = BindException(eventConfig, "eventConfig") @@ -53,42 +53,42 @@ open class EventConfigTest { @Test fun testKafkaMissingFields() { - val kafkaConfig = PropertyKafkaConfig() + val kafkaConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) kafkaConfig.isUseSingleQueue = true kafkaConfig.eventTypeToQueue = HashMap() - val eventConfig = PropertyEventConfig(kafkaConfig, PropertySqsConfig()) + val eventConfig = PropertyEventConfig(kafkaConfig) eventConfig.isEnabled = true eventConfig.publisherType = "kafka" val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "badConfig-kafka") } + errors.message?.let { assertContains(it, "badPublisherConfig") } - val kafkaErrors = kafkaConfig.validate() + val kafkaErrors = kafkaConfig.validate(eventConfig.publisherType) assertEquals(1, kafkaErrors.errorCount) kafkaErrors.message?.let { assertContains(it, "empty-bootstrapServer") } } @Test fun testSqsMissingFields() { - val sqsConfig = PropertySqsConfig() + val sqsConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) sqsConfig.isUseSingleQueue = true sqsConfig.eventTypeToQueue = HashMap() sqsConfig.accessKey = "accessKey" sqsConfig.secretKey = "secretKey" - val eventConfig = PropertyEventConfig(PropertyKafkaConfig(), sqsConfig) + val eventConfig = PropertyEventConfig(sqsConfig) eventConfig.isEnabled = true eventConfig.publisherType = "sqs" val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "badConfig-sqs") } + errors.message?.let { assertContains(it, "badPublisherConfig") } - val sqsErrors = sqsConfig.validate() + val sqsErrors = sqsConfig.validate(eventConfig.publisherType) assertEquals(1, sqsErrors.errorCount) sqsErrors.message?.let { assertContains(it, "empty-region") } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep12ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep12ConfigTest.kt index e4bff9731e..4e4ed4f30d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep12ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep12ConfigTest.kt @@ -1,6 +1,5 @@ package org.stellar.anchor.platform.config -import kotlin.test.assertContains import kotlin.test.assertEquals import org.junit.jupiter.api.* import org.springframework.validation.BindException @@ -9,23 +8,13 @@ import org.springframework.validation.ValidationUtils open class Sep12ConfigTest { @Test fun testSep12ConfigValid() { - val sep12Config = PropertySep12Config() + val config = CallbackApiConfig(PropertySecretConfig()) + config.baseUrl = "https://localhost:8081" + val sep12Config = PropertySep12Config(config) sep12Config.enabled = true - sep12Config.customerIntegrationEndPoint = "https://localhost:8081" val errors = BindException(sep12Config, "sep12Config") ValidationUtils.invokeValidator(sep12Config, sep12Config, errors) assertEquals(0, errors.errorCount) } - - @Test - fun testSep12ConfigBadFile() { - val sep12Config = PropertySep12Config() - sep12Config.enabled = true - val errors = BindException(sep12Config, "sep12Config") - ValidationUtils.invokeValidator(sep12Config, sep12Config, errors) - assertEquals(2, errors.errorCount) - errors.message?.let { assertContains(it, "empty-customerIntegrationEndPoint") } - errors.message?.let { assertContains(it, "invalidUrl-customerIntegrationEndPoint") } - } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt index ecf7f180eb..8de08c4c4e 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt @@ -1,32 +1,100 @@ package org.stellar.anchor.platform.config +import java.net.URL +import java.nio.file.Paths import kotlin.test.assertContains import kotlin.test.assertEquals -import org.junit.jupiter.api.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource import org.springframework.validation.BindException import org.springframework.validation.ValidationUtils open class Sep1ConfigTest { - @Test - fun testSep1ConfigValid() { - val sep1Config = PropertySep1Config() - sep1Config.enabled = true - sep1Config.stellarFile = "classpath:/sep1/stellar-wks.toml" + companion object { + fun getTestTomlAsFile(): String { + val resource: URL = + Sep1ConfigTest::class.java.getResource( + "/org/stellar/anchor/platform/config/sep1-stellar-test.toml" + ) as + URL + return Paths.get(resource.toURI()).toFile().absolutePath + } + + fun getTestTomlAsUrl(): String { + val resource: URL = + Sep1ConfigTest::class.java.getResource( + "/org/stellar/anchor/platform/config/sep1-stellar-test.toml" + ) as + URL + return resource.toString() + } + + fun validate(config: PropertySep1Config): BindException { + val errors = BindException(config, "sep1Config") + ValidationUtils.invokeValidator(config, config, errors) + return errors + } + } + + @ParameterizedTest + @ValueSource(strings = ["file", "FILE", "File"]) + fun `test reading from sep1-stellar-test toml file`(type: String) { + val errors = validate(PropertySep1Config(true, type, getTestTomlAsFile())) + assertEquals(0, errors.errorCount) + } + + @ParameterizedTest + @ValueSource(strings = ["string", "STRING", "String"]) + fun `test inline toml`(type: String) { + val errors = validate(PropertySep1Config(true, type, "VERSION = \"0.1.0\"")) + assertEquals(0, errors.errorCount) + } - val errors = BindException(sep1Config, "sep1Config") - ValidationUtils.invokeValidator(sep1Config, sep1Config, errors) + @ParameterizedTest + @ValueSource(strings = ["url", "URL", "Url", "urL"]) + fun `test toml with sep1-stellar-test specified as a URL`(type: String) { + val errors = validate(PropertySep1Config(true, type, getTestTomlAsUrl())) assertEquals(0, errors.errorCount) } + @ParameterizedTest + @NullSource + @ValueSource(strings = ["bad", "strin g", ""]) + fun `test bad Sep1Config values`(type: String?) { + val errors = validate(PropertySep1Config(true, type, getTestTomlAsUrl())) + assertEquals(1, errors.errorCount) + } + + @ParameterizedTest + @ValueSource(strings = ["bad file", "c:/hello"]) + fun `test file of Sep1Config does not exist`(file: String) { + val errors = validate(PropertySep1Config(true, "file", file)) + assertEquals(1, errors.errorCount) + errors.message?.let { assertContains(it, "doesNotExist-sep1Value") } + } + + @ParameterizedTest + @ValueSource(strings = ["string", "file", "url"]) + fun `test Sep1Config empty values`(type: String) { + var errors = validate(PropertySep1Config(true, type, null)) + assertEquals(2, errors.errorCount) + errors.message?.let { assertContains(it, "empty-sep1Value") } + + errors = validate(PropertySep1Config(true, type, "")) + assertEquals(2, errors.errorCount) + errors.message?.let { assertContains(it, "empty-sep1Value") } + } + @Test - fun testSep1ConfigBadFile() { - val sep1Config = PropertySep1Config() - sep1Config.enabled = true - sep1Config.stellarFile = "classpath:/sep1/stellar-dne.toml" + fun `test Sep1Config empty types`() { + var errors = validate(PropertySep1Config(true, null, "test value")) + assertEquals(1, errors.errorCount) + errors.message?.let { assertContains(it, "empty-sep1Type") } - val errors = BindException(sep1Config, "sep1Config") - ValidationUtils.invokeValidator(sep1Config, sep1Config, errors) + errors = validate(PropertySep1Config(true, "", "test value")) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "doesNotExist-stellarFile") } + errors.message?.let { assertContains(it, "empty-sep1Type") } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep31ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep31ConfigTest.kt index 1bce8ec5e7..49f4d6b25d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep31ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep31ConfigTest.kt @@ -13,7 +13,10 @@ open class Sep31ConfigTest { val circleConfig = PropertyCircleConfig() circleConfig.circleUrl = "https://api-sandbox.circle.com" circleConfig.apiKey = "apikey" - val sep31Config = PropertySep31Config(circleConfig) + val callbackApiConfig = CallbackApiConfig(PropertySecretConfig()) + callbackApiConfig.baseUrl = "http://localhost:8080" + + val sep31Config = PropertySep31Config(circleConfig, callbackApiConfig) sep31Config.depositInfoGeneratorType = CIRCLE val errors = BindException(sep31Config, "sep31Config") @@ -28,7 +31,10 @@ open class Sep31ConfigTest { fun testSep31BadCircleConfig() { val circleConfig = PropertyCircleConfig() circleConfig.circleUrl = "https://api-sandbox.circle.com" - val sep31Config = PropertySep31Config(circleConfig) + val callbackApiConfig = CallbackApiConfig(PropertySecretConfig()) + callbackApiConfig.baseUrl = "http://localhost:8080" + + val sep31Config = PropertySep31Config(circleConfig, callbackApiConfig) sep31Config.enabled = true sep31Config.depositInfoGeneratorType = CIRCLE diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep38ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep38ConfigTest.kt index 4ab0debabb..142788a86d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep38ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep38ConfigTest.kt @@ -1,6 +1,5 @@ package org.stellar.anchor.platform.config -import kotlin.test.assertContains import kotlin.test.assertEquals import org.junit.jupiter.api.* import org.springframework.validation.BindException @@ -11,21 +10,9 @@ open class Sep38ConfigTest { fun testSep38ConfigValid() { val sep38Config = PropertySep38Config() sep38Config.enabled = true - sep38Config.quoteIntegrationEndPoint = "http://localhost:8081" val errors = BindException(sep38Config, "sep38Config") ValidationUtils.invokeValidator(sep38Config, sep38Config, errors) assertEquals(0, errors.errorCount) } - @Test - fun testSep38ConfigBadQuoteIntegrationEndpoint() { - val sep38Config = PropertySep38Config() - sep38Config.enabled = true - sep38Config.quoteIntegrationEndPoint = "not-a-url" - - val errors = BindException(sep38Config, "sep38Config") - ValidationUtils.invokeValidator(sep38Config, sep38Config, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "invalidUrl-quoteIntegrationEndPoint") } - } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt new file mode 100644 index 0000000000..c807fdaca2 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt @@ -0,0 +1,98 @@ +package org.stellar.anchor.platform.configurator + +import io.mockk.* +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.springframework.core.io.ClassPathResource +import org.stellar.anchor.api.exception.InvalidConfigException +import org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource.DEFAULT +import org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource.FILE + +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +class ConfigManagerTest { + lateinit var configManager: ConfigManager + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + mockkStatic(ConfigReader::class) + mockkStatic(ConfigHelper::class) + + configManager = spyk(ConfigManager.getInstance()) + + every { ConfigHelper.loadDefaultConfig() } returns + ConfigHelper.loadConfig( + ClassPathResource("org/stellar/anchor/platform/configurator/def/config-defaults-v3.yaml"), + DEFAULT + ) + + every { ConfigReader.getVersionSchemaFile(any()) } answers + { + String.format( + "org/stellar/anchor/platform/configurator/def/config-def-v%d.yaml", + firstArg() + ) + } + } + + @AfterEach + fun teardown() { + clearAllMocks() + unmockkAll() + } + + @Test + @Order(1) + fun `(scene-1) configuration with version upgrades`() { + every { configManager.getConfigFileAsResource(any()) } answers + { + ClassPathResource("org/stellar/anchor/platform/configurator/scene-1/test.yaml") + } + + val wantedConfig = + ConfigHelper.loadConfig( + ClassPathResource("org/stellar/anchor/platform/configurator/scene-1/wanted.yaml"), + FILE + ) + val gotConfig = configManager.processConfigurations(null) + + assertTrue(gotConfig.sameAs(wantedConfig)) + } + + @Test + @Order(2) + fun `(scene-2) bad configuration file`() { + every { configManager.getConfigFileAsResource(any()) } answers + { + ClassPathResource("org/stellar/anchor/platform/configurator/scene-2/test.bad.yaml") + } + val ex = assertThrows { configManager.processConfigurations(null) } + assertEquals(2, ex.messages.size) + assertEquals("Invalid configuration: stellar.apollo=star. (version=1)", ex.messages[0]) + assertEquals("Invalid configuration: horizon.aster=star. (version=1)", ex.messages[1]) + } + + @Test + @Order(3) + fun `(scene-3) configuration from file and system environment variables with upgrades`() { + every { configManager.getConfigFileAsResource(any()) } answers + { + ClassPathResource("org/stellar/anchor/platform/configurator/scene-3/test.yaml") + } + + System.setProperty("stellar.bianca", "white") + System.setProperty("stellar.deimos", "satellite") + + ConfigEnvironment.rebuild() + + val wantedConfig = + ConfigHelper.loadConfig( + ClassPathResource("org/stellar/anchor/platform/configurator/scene-3/wanted.yaml"), + FILE + ) + val gotConfig = configManager.processConfigurations(null) + + assertTrue(gotConfig.sameAs(wantedConfig)) + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigMapTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigMapTest.kt new file mode 100644 index 0000000000..5063d0222d --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigMapTest.kt @@ -0,0 +1,53 @@ +package org.stellar.anchor.platform.configurator + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.stellar.anchor.api.exception.InvalidConfigException + +class ConfigMapTest { + @Test + fun `test getInt() ok`() { + val testKey = "testKey" + val cm = ConfigMap() + cm.put(testKey, "1", ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(1, cm.getInt(testKey)) + cm.put(testKey, "-1", ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(-1, cm.getInt(testKey)) + cm.put(testKey, "0", ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(0, cm.getInt(testKey)) + cm.put(testKey, Integer.MAX_VALUE.toString(), ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(Integer.MAX_VALUE, cm.getInt(testKey)) + cm.put(testKey, Integer.MIN_VALUE.toString(), ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(Integer.MIN_VALUE, cm.getInt(testKey)) + } + + @ParameterizedTest + @ValueSource(strings = ["1.0", "-1.0", "abc", ""]) + fun `test getInt() failure`(value: String) { + val testKey = "testKey" + val cm = ConfigMap() + cm.put(testKey, value, ConfigMap.ConfigSource.ENV) + assertThrows { cm.getInt(testKey) } + } + + @ParameterizedTest + @ValueSource(strings = ["true", "True"]) + fun `test getBoolean() true`(value: String) { + val testKey = "testKey" + val cm = ConfigMap() + cm.put(testKey, value, ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(true, cm.getBoolean(testKey)) + } + + @ParameterizedTest + @ValueSource(strings = ["0", "tr ue", "fa lse", "False", "false", ""]) + fun `test getBoolean() false`(value: String) { + val testKey = "testKey" + val cm = ConfigMap() + cm.put(testKey, value, ConfigMap.ConfigSource.ENV) + Assertions.assertEquals(false, cm.getBoolean(testKey)) + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfiguratorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfiguratorTest.kt deleted file mode 100644 index e04db9c96c..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfiguratorTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package org.stellar.anchor.platform.configurator - -import io.mockk.every -import io.mockk.spyk -import io.mockk.unmockkAll -import io.mockk.verify -import java.io.File -import java.io.IOException -import kotlin.test.assertEquals -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertInstanceOf -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource -import org.springframework.context.ConfigurableApplicationContext -import org.springframework.context.annotation.AnnotationConfigApplicationContext -import org.springframework.util.ResourceUtils - -open class ConfiguratorTest { - @AfterEach - fun tearDown() { - unmockkAll() - } - - @ParameterizedTest - @ValueSource(strings = ["getFromSystemEnv", "getFromSystemProperty"]) - fun testSystemEnv(method: String) { - val applicationContext = AnnotationConfigApplicationContext() - val propertiesReader = spyk(recordPrivateCalls = true) - every { propertiesReader[method]() } returns "classpath:test-read.yaml" - - propertiesReader.initialize(applicationContext) - loadConfigurations(applicationContext) - } - - @Test - fun testReadFromUserFolder() { - val applicationContext = AnnotationConfigApplicationContext() - val propertiesReader = spyk(recordPrivateCalls = true) - - every { propertiesReader.getFromUserFolder() } returns - ResourceUtils.getFile("classpath:test-read.yaml") - assertDoesNotThrow { propertiesReader.initialize(applicationContext) } - verify(exactly = 1) { propertiesReader.getFromUserFolder() } - loadConfigurations(applicationContext) - - every { propertiesReader.getFromUserFolder() } returns File("bad file") - assertDoesNotThrow { propertiesReader.initialize(applicationContext) } - verify(exactly = 2) { propertiesReader.getFromUserFolder() } - loadConfigurations(applicationContext) - } - - @Test - fun testGetFromSystemEnv() { - val applicationContext = AnnotationConfigApplicationContext() - val propertiesReader = spyk(recordPrivateCalls = true) - - every { propertiesReader.fromUserFolder } returns File("not exist") - every { propertiesReader.getFromSystemEnv() } returns "classpath:test-read.yaml" - assertDoesNotThrow { propertiesReader.initialize(applicationContext) } - verify(exactly = 1) { propertiesReader.getFromSystemEnv() } - loadConfigurations(applicationContext) - - every { propertiesReader.getFromSystemEnv() } returns "bad file path" - val ex = assertThrows { propertiesReader.initialize(applicationContext) } - assertInstanceOf(IOException::class.java, ex) - assertEquals("Resource not found", ex.message) - verify(exactly = 2) { propertiesReader.getFromSystemEnv() } - } - - @Test - fun testFromUserFolder() { - val file = PropertiesReader().fromUserFolder - assertTrue( - file.absolutePath.endsWith(String.format(".anchor%sanchor-config.yaml", File.separator)) - ) - } - - private fun loadConfigurations(context: ConfigurableApplicationContext) { - PlatformAppConfigurator().initialize(context) - DataAccessConfigurator().initialize(context) - SpringFrameworkConfigurator().initialize(context) - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt index 745baeef9e..eb1503bf76 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt @@ -14,7 +14,7 @@ import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.AnchorException import org.stellar.anchor.api.exception.BadRequestException import org.stellar.anchor.api.exception.UnprocessableEntityException -import org.stellar.anchor.config.CirclePaymentObserverConfig +import org.stellar.anchor.config.PaymentObserverConfig import org.stellar.anchor.horizon.Horizon import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment @@ -34,7 +34,7 @@ import org.stellar.sdk.responses.operations.PaymentOperationResponse class CirclePaymentObserverServiceTest { @MockK private lateinit var httpClient: OkHttpClient @MockK private lateinit var horizon: Horizon - @MockK private lateinit var circlePaymentObserverConfig: CirclePaymentObserverConfig + @MockK private lateinit var circlePaymentObserverConfig: PaymentObserverConfig @MockK private lateinit var circlePaymentObserverService: CirclePaymentObserverService @MockK private lateinit var paymentListener: PaymentListener private lateinit var server: MockWebServer diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/SpringResourceReaderTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SpringResourceReaderTest.kt deleted file mode 100644 index b558af0009..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/SpringResourceReaderTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.stellar.anchor.platform.service - -import java.io.FileReader -import java.io.UncheckedIOException -import java.net.URL -import java.nio.file.Paths -import org.apache.commons.io.IOUtils -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -internal class SpringResourceReaderTest { - - @Test - fun getListAllAssets() { - val reader = SpringResourceReader() - val result1 = reader.readResourceAsString("test_assets.json") - val result2 = reader.readResourceAsString("classpath:/test_assets.json") - assertEquals(result1, result2) - - val resource: URL = SpringResourceReader::class.java.getResource("/test_assets.json") - var file = Paths.get(resource.toURI()).toFile() - val result3 = IOUtils.toString(FileReader(file)) - assertEquals(result3, result2) - } - - @Test - fun testJsonNotFound() { - val reader = SpringResourceReader() - assertThrows { reader.readResourceAsString("file:test_assets.json") } - assertThrows { reader.readResourceAsString("file:test_assets.json.bad") } - } -} diff --git a/platform/src/test/resources/org/stellar/anchor/platform/config/sep1-stellar-test.toml b/platform/src/test/resources/org/stellar/anchor/platform/config/sep1-stellar-test.toml new file mode 100644 index 0000000000..d7296e8456 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/config/sep1-stellar-test.toml @@ -0,0 +1,26 @@ +ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ] +VERSION = "0.1.0" +SIGNING_KEY = "GBDYDBJKQBJK4GY4V7FAONSFF2IBJSKNTBYJ65F5KCGBY2BIGPGGLJOH" +NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + +WEB_AUTH_ENDPOINT = "http://localhost:8080/auth" +KYC_SERVER = "http://localhost:8080/sep12" +TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" +DIRECT_PAYMENT_SERVER = "http://localhost:8080/sep31" +ANCHOR_QUOTE_SERVER = "http://localhost:8080/sep38" + +[[CURRENCIES]] +code = "SRT" +issuer = "GCDNJUBQSX7AJWLJACMJ7I4BC3Z47BQUTMHEICZLE6MU4KQBRYG5JY6B" +status = "test" +is_asset_anchored = false +anchor_asset_type = "other" +desc = "A fake anchored asset to use with this example anchor server." + +[DOCUMENTATION] +ORG_NAME = "Stellar Development Foundation" +ORG_URL = "https://stellar.org" +ORG_DESCRIPTION = "SEP 24 reference server." +ORG_KEYBASE = "stellar.public" +ORG_TWITTER = "StellarOrg" +ORG_GITHUB = "stellar" \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v1.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v1.yaml new file mode 100644 index 0000000000..aab4d02122 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v1.yaml @@ -0,0 +1,6 @@ +version: 1 + +stellar: + aster: + bianca: + ceilo: \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v2.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v2.yaml new file mode 100644 index 0000000000..a27031b6bb --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v2.yaml @@ -0,0 +1,10 @@ +version: 2 + + +stellar: + aster: horizon.aster # moved to horizon.apollo + bianca: + ceilo: + +horizon: + aster: \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v3.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v3.yaml new file mode 100644 index 0000000000..547bfecbbe --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-def-v3.yaml @@ -0,0 +1,9 @@ +version: 3 + +stellar: + bianca: +# ceilo: # deleted + deimos: # added + +horizon: + aster: diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-defaults-v3.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-defaults-v3.yaml new file mode 100644 index 0000000000..47c433c2f5 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/def/config-defaults-v3.yaml @@ -0,0 +1,8 @@ +version: 3 + +stellar: + bianca: default + deimos: default + +horizon: + aster: default diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/test.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/test.yaml new file mode 100644 index 0000000000..ecafd6dbd9 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/test.yaml @@ -0,0 +1,6 @@ +version: 1 + +stellar: + aster: star + bianca: white + ceilo: sky \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/wanted.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/wanted.yaml new file mode 100644 index 0000000000..487c680b79 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-1/wanted.yaml @@ -0,0 +1,8 @@ +version: 3 + +stellar: + bianca: white + deimos: default + +horizon: + aster: star \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-2/test.bad.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-2/test.bad.yaml new file mode 100644 index 0000000000..54f69a1425 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-2/test.bad.yaml @@ -0,0 +1,9 @@ +version: 1 + +stellar: + apollo: star # bad key + bianca: white + ceilo: sky + +horizon: + aster: star # only valid in version 2 \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/test.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/test.yaml new file mode 100644 index 0000000000..aeba1b4915 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/test.yaml @@ -0,0 +1,5 @@ +version: 1 + +stellar: + aster: star + ceilo: sky \ No newline at end of file diff --git a/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/wanted.yaml b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/wanted.yaml new file mode 100644 index 0000000000..15ec128311 --- /dev/null +++ b/platform/src/test/resources/org/stellar/anchor/platform/configurator/scene-3/wanted.yaml @@ -0,0 +1,8 @@ +version: 3 + +stellar: + bianca: white + deimos: satellite + +horizon: + aster: star \ No newline at end of file diff --git a/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java b/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java index 471dfed1de..2be951d1cb 100644 --- a/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java +++ b/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java @@ -1,5 +1,6 @@ package org.stellar.anchor.platform; +import java.util.Map; import org.apache.commons.cli.*; import org.springframework.context.ConfigurableApplicationContext; import org.stellar.anchor.reference.AnchorReferenceServer; @@ -24,7 +25,7 @@ public static void main(String[] args) { CommandLine cmd = parser.parse(options, args); boolean anyServerStarted = false; if (cmd.hasOption("sep-server") || cmd.hasOption("all")) { - startSepServer(); + startSepServer(DEFAULT_SEP_SERVER_PORT, DEFAULT_CONTEXTPATH, null); anyServerStarted = true; } @@ -46,21 +47,13 @@ public static void main(String[] args) { } } - static ConfigurableApplicationContext startSepServer() { - String strPort = System.getProperty("SEP_SERVER_PORT"); - int port = DEFAULT_SEP_SERVER_PORT; - if (strPort != null) { - port = Integer.parseInt(strPort); - } - String contextPath = System.getProperty("SEP_CONTEXTPATH"); - if (contextPath == null) { - contextPath = DEFAULT_CONTEXTPATH; - } - return AnchorPlatformServer.start(port, contextPath); + static ConfigurableApplicationContext startSepServer( + int port, String contextPath, Map env) { + return AnchorPlatformServer.start(port, contextPath, env, true); } - static void startStellarObserver() { - StellarObservingService.start(); + static ConfigurableApplicationContext startStellarObserver() { + return StellarObservingService.start(); } static void startAnchorReferenceServer() { From 7330181ee02c8ac78bf73dfdcc4509e8aea9cb2b Mon Sep 17 00:00:00 2001 From: erika-sdf <92893480+erika-sdf@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:48:23 -0700 Subject: [PATCH 51/65] Add args to AP helm chart (#584) * Add sep_server and payment_observer flags to AP helm chart --- helm-charts/sep-service/example_values.yaml | 6 +++--- helm-charts/sep-service/templates/deployment.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index a842a9e272..5fc7db21fd 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -11,9 +11,9 @@ image: tag: latest pullPolicy: Always deployment: - args: - - '"--sep-server"' - - '"--stellar-observer"' + args: + - '"--sep-server"' + - '"--stellar-observer"' startupProbePeriodSeconds: 10 startupProbeFailureThreshold: 30 serviceAccountName: default diff --git a/helm-charts/sep-service/templates/deployment.yaml b/helm-charts/sep-service/templates/deployment.yaml index c89873e458..6c07347f8f 100644 --- a/helm-charts/sep-service/templates/deployment.yaml +++ b/helm-charts/sep-service/templates/deployment.yaml @@ -30,7 +30,7 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" - args: [{{ join ", " .Values.deployment.args }}] + args: {{ printf "[ %s ]" ((join ", " .Values.deployment.args) | default "\"--sep-server\", \"--stellar-observer\"") }} imagePullPolicy: {{ .Values.image.pullPolicy | default "Always" }} startupProbe: httpGet: From c2bb57046772cee517a1e66402a2970c6109754a Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 26 Sep 2022 18:33:22 -0300 Subject: [PATCH 52/65] Fix local deployment (#593) ### What Fix localhost deployment. ### Why It stopped working on PR #516. --- .../resources/anchor-reference-server.yaml | 2 +- ...- Running & Configuring the Application.md | 18 +- platform/example.anchor-config.yaml | 1 - .../platform/configurator/ConfigReader.java | 3 +- .../configurator/DataConfigAdapter.java | 9 +- .../config/anchor-config-default-values.yaml | 15 +- .../config/anchor-config-schema-v1.yaml | 1 + .../main/resources/example.anchor-config.yaml | 327 ++++++++++++++++++ platform/src/main/resources/example.env | 11 +- .../anchor/platform/config/Sep1ConfigTest.kt | 4 +- 10 files changed, 366 insertions(+), 25 deletions(-) delete mode 100644 platform/example.anchor-config.yaml create mode 100644 platform/src/main/resources/example.anchor-config.yaml diff --git a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml index d6a6dd5d54..679738f27a 100644 --- a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml +++ b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml @@ -43,7 +43,7 @@ event: # The listener type. values: [kafka, sqs, amqp] # If the listener is kafka, the kafka listener is configured in kafka.listener section. # If the listener is sqs, the sqs listener is configured in the sqs.listener section. - # If the listener is amqp, the AMQP listner is configured in the ampq.listner section. + # If the listener is amqp, the AMQP listener is configured in the ampq.listener section. listenerType: kafka # NOTE: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables need to be set if using AWS MSK diff --git a/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md b/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md index c5fe361990..5c6d364fe6 100644 --- a/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md +++ b/docs/01 - Running & Configuring the Application/A - Running & Configuring the Application.md @@ -25,9 +25,11 @@ This section covers how to run the application from source code using the provid 3. Start the Anchor Reference server: `./gradlew service-runner:bootRun --args=--anchor-reference-server` - This uses the default configuration file at [`anchor-reference-server.yaml`], but you can use a custom configuration file by setting the `REFERENCE_SERVER_CONFIG_ENV` environment variable to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. 4. Start the Anchor Platform: `./gradlew service-runner:bootRun --args=--sep-server` - - This uses the default configuration file at [`anchor-config-defaults.yaml`], but you can use a custom configuration file by setting the `STELLAR_ANCHOR_CONFIG` environment variable to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. + - This step requires you to set up a `STELLAR_ANCHOR_CONFIG`. You can test the application by using the default one with `export STELLAR_ANCHOR_CONFIG=file:/platform/src/main/resources/example.anchor-config.yaml`, + - Eventually you'll need to set up your own configuration based on the `anchor-config-default-values.yaml`. + - You will need to export additional environment variables, depending on your configuration. An example of the variables you may need can be found in [`example.env`] 5. Start the Stellar Observer: `./gradlew service-runner:bootRun --args=--stellar-observer` - - This also uses the default configuration file at [`anchor-config-defaults.yaml`], but you can use a custom configuration file by setting the `STELLAR_ANCHOR_CONFIG` environment variable to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. + - This also needs the `STELLAR_ANCHOR_CONFIG` previously mentioned. ## Configuring the Project @@ -171,7 +173,7 @@ Boot Actuator metrics are enabled by default. There are certain metrics that per the count of transactions in each state); these metrics are disabled by default. They can be enabled with the following configs: -```text +```yaml metrics-service: optionalMetricsEnabled: true # optional metrics that periodically query the database runInterval: 30 # interval to query the database to generate the optional metrics @@ -181,11 +183,11 @@ A Grafana dashboard for the Anchor Platform can be found at `docs/resources/graf and imported into your Grafana instance to visualized the Prometheus metrics. -[`anchor-config-defaults.yaml`]: /platform/src/main/resources/anchor-config-defaults.yaml -[`anchor-reference-server.yaml`]: /anchor-reference-server/src/main/resources/anchor-reference-server.yaml -[`example.env`]: /platform/src/main/resources/example.env -[`docs/resources/docker-examples/kafka/docker-compose.yaml`]: /docs/resources/docker-examples/kafka/docker-compose.yaml -[`assets-test.json`]: /platform/src/main/resources/assets-test.json +[`anchor-config-defaults.yaml`]: ../../platform/src/main/resources/anchor-config-defaults.yaml +[`anchor-reference-server.yaml`]: ../../anchor-reference-server/src/main/resources/anchor-reference-server.yaml +[`example.env`]: ../../platform/src/main/resources/example.env +[`docs/resources/docker-examples/kafka/docker-compose.yaml`]: ../../docs/resources/docker-examples/kafka/docker-compose.yaml +[`assets-test.json`]: ../../platform/src/main/resources/assets-test.json ## Logging diff --git a/platform/example.anchor-config.yaml b/platform/example.anchor-config.yaml deleted file mode 100644 index f719241df2..0000000000 --- a/platform/example.anchor-config.yaml +++ /dev/null @@ -1 +0,0 @@ -src/main/resources/anchor-config-defaults.yaml \ No newline at end of file diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java index 3d2f51a5c0..3e8ad2dc25 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigReader.java @@ -18,7 +18,8 @@ public class ConfigReader { public ConfigReader(int version) throws InvalidConfigException { try { - configSchema = loadConfig(new ClassPathResource(getVersionSchemaFile(version)), VERSION_SCHEMA); + configSchema = + loadConfig(new ClassPathResource(getVersionSchemaFile(version)), VERSION_SCHEMA); this.version = version; } catch (IOException e) { throw new InvalidConfigException(String.format("version:%s is not a defined", version)); diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java index d567a38338..e734dd7ef8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java @@ -99,10 +99,11 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.datasource.driver-class-name", "org.sqlite.JDBC"); set("spring.datasource.name", "anchor-platform"); set("spring.jpa.database-platform", "org.stellar.anchor.platform.sqlite.SQLiteDialect"); + copy(config, "data.ddl_auto", "spring.jpa.hibernate.ddl-auto"); copy(config, "data.url", "spring.datasource.url"); copy(config, "data.username", "spring.datasource.username"); copy(config, "data.password", "spring.datasource.password"); - if (config.getString("flyway.enabled").equalsIgnoreCase("true")) { + if (config.getString("flyway.enabled", "").equalsIgnoreCase("true")) { set("spring.flyway.enabled", true); set("spring.flyway.locations", "classpath:/db/migration"); copy(config, "data.username", "spring.flyway.user"); @@ -117,10 +118,11 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set( "spring.datasource.hikari.max-lifetime", 840000); // 14 minutes because IAM tokens are valid for 15 min + copy(config, "data.ddl_auto", "spring.jpa.hibernate.ddl-auto"); copy(config, "data.url", "spring.datasource.url"); copy(config, "data.username", "spring.datasource.username"); copy(config, "data.password", "spring.datasource.password"); - if (config.getString("flyway.enabled").equalsIgnoreCase("true")) { + if (config.getString("flyway.enabled", "").equalsIgnoreCase("true")) { set("spring.flyway.enabled", true); set("spring.flyway.locations", "classpath:/db/migration"); copy(config, "data.username", "spring.flyway.user"); @@ -132,10 +134,11 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.datasource.driver-class-name", "org.postgresql.Driver"); set("spring.datasource.name", "anchor-platform"); set("spring.jpa.database-platform", "org.hibernate.dialect.PostgreSQL9Dialect"); + copy(config, "data.ddl_auto", "spring.jpa.hibernate.ddl-auto"); copy(config, "data.url", "spring.datasource.url"); copy(config, "data.username", "spring.datasource.username"); copy(config, "data.password", "spring.datasource.password"); - if (config.getString("flyway.enabled").equalsIgnoreCase("true")) { + if (config.getString("flyway.enabled", "").equalsIgnoreCase("true")) { set("spring.flyway.enabled", true); set("spring.flyway.locations", "classpath:/db/migration"); copy(config, "data.username", "spring.flyway.user"); diff --git a/platform/src/main/resources/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index c34ed89977..6c98d02213 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -299,7 +299,7 @@ sep38: ## @type: bool ## Whether to enable SEP-38 for this instance of AP server. # - enabled: true + enabled: false ########################## @@ -340,7 +340,6 @@ events: ## @param: publisher_type ## @supported_values: `kafka`, `sqs` ## The type of queue to use for event publishing - ## # publisher_type: kafka @@ -473,6 +472,18 @@ data: # flyway_enabled: false + ## @param: ddl_auto + ## @supported_values: + ## `none`: (default) No database Schema initialization + ## `create`: Drops and creates the schema at the application startup. With this option, all your data will be gone on each startup. + ## `create-drop`: Creates schema at the startup and destroys the schema on context closure. Useful for unit tests. + ## `validate`: Only checks if the Schema matches the Entities. If the schema doesn't match, then the application startup will fail. Makes no changes to the database. + ## `update`: Updates the schema only if necessary. For example, If a new field was added in an entity, then it will simply alter the table for a new column without destroying the data. + ## This value will be used to configure `spring.jpa.hibernate.ddl-auto` in non-memory databases like SQLite, Postgres, etc. + ## ATTENTION: it should not be used in production! + # + ddl_auto: none + ## @param: flyway_enabled ## @type: string ## Location on disk where migrations are stored if flyway is enabled. diff --git a/platform/src/main/resources/config/anchor-config-schema-v1.yaml b/platform/src/main/resources/config/anchor-config-schema-v1.yaml index 9b5f11f27c..163d755b3b 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -4,6 +4,7 @@ callback_api.auth.expiration_milliseconds: callback_api.auth.type: callback_api.base_url: data.flyway_enabled: +data.ddl_auto: data.flyway_location: data.initial_connection_pool_size: data.max_active_connections: diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml new file mode 100644 index 0000000000..47db947159 --- /dev/null +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -0,0 +1,327 @@ +version: 1 + +host_url: http://localhost:8080 + +stellar_network: + network: TESTNET + network_passphrase: Test SDF Network ; September 2015 + horizon_url: https://horizon-testnet.stellar.org + +callback_api: + base_url: http://localhost:8081 + auth: + type: NONE + expiration_milliseconds: 30000 + +platform_api: + auth: + type: NONE + expiration_milliseconds: 30000 + +payment_observer: + enabled: false + circle_url: https://api-sandbox.circle.com + tracked_wallet: all + +languages: en + +logging: + level: WARN + stellar_level: INFO + +data: + type: sqlite + url: jdbc:sqlite:anchor-platform.db + username: SQLITE_USERNAME # TODO: use secrets + password: SQLITE_PASSWORD # TODO: use secrets + initial_connection_pool_size: 2 + max_active_connections: 10 + flyway_enabled: true + ddl_auto: update + +sep1: + enabled: true + toml: + type: string + value: | + ACCOUNTS = [] + VERSION = "0.1.0" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + + WEB_AUTH_ENDPOINT = "http://localhost:8080/auth" + KYC_SERVER = "http://localhost:8080/sep12" + TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" + DIRECT_PAYMENT_SERVER = "http://localhost:8080/sep31" + ANCHOR_QUOTE_SERVER = "http://localhost:8080/sep38" + + [[CURRENCIES]] + code = "USDC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + status = "test" + is_asset_anchored = true + anchor_asset_type = "fiat" + desc = "A test USDC issued by Stellar." + + [[CURRENCIES]] + code = "JPYC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + status = "test" + is_asset_anchored = true + anchor_asset_type = "fiat" + desc = "A test JPYC issued by Stellar." + +sep10: + enabled: true + home_domain: localhost:8080 + +sep12: + enabled: true + +sep24: + enabled: false + +sep31: + enabled: true + payment_type: STRICT_SEND + deposit_info_generator_type: self + +sep38: + enabled: true + +metrics: + enabled: true + extras_enabled: true + run_interval: 30 + +events: + enabled: true + publisher_type: kafka + options: + bootstrap_server: localhost:29092 + use_single_queue: false + event_type_to_queue: + all: ap_event_single_queue + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + +assets: + type: json + value: | + { + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 2, + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31": { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving USD" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving USD" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "stellar", + "code": "JPYC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 4, + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31": { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving JPY" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving JPY" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": false, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "iso4217", + "code": "USD", + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "sep38": { + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "country_codes": [ + "USA" + ], + "decimals": 4, + "sell_delivery_methods": [ + { + "name": "WIRE", + "description": "Send USD directly to the Anchor's bank account." + } + ], + "buy_delivery_methods": [ + { + "name": "WIRE", + "description": "Have USD sent directly to your bank account." + } + ] + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38_enabled": true + } + ] + } diff --git a/platform/src/main/resources/example.env b/platform/src/main/resources/example.env index 09bdb88ccb..633ebf0865 100644 --- a/platform/src/main/resources/example.env +++ b/platform/src/main/resources/example.env @@ -6,23 +6,20 @@ #################################################################################################### # REQUIRED - The secret key of JWT encryption -JWT_SECRET=secret +SECRET.SEP10.JWT_SECRET=secret # REQUIRED - The private key of the SEP-10 challenge. # We highly recommend that this private key should not be used to sign any transactions to submit to the Stellar # network. -SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X +SECRET.SEP10.SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X # REQUIRED - JWT secrets used to communicate between Anchor and Platform. -PLATFORM_TO_ANCHOR_SECRET=myPlatformToAnchorSecret -ANCHOR_TO_PLATFORM_SECRET=myAnchorToPlatformSecret +SECRET.CALLBACK_API.AUTH_SECRET=myPlatformToAnchorSecret +SECRET.PLATFORM_API.AUTH_SECRET=myAnchorToPlatformSecret # OPTIONAL (only if using Circle) CIRCLE_API_KEY=QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== -# OPTIONAL - Stellar account secret key -PAYMENT_GATEWAY_STELLAR_SECRET_KEY=secret - # OPTIONAL - used for storage definition DATA_USERNAME=admin DATA_PASSWORD=admin diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt index 8de08c4c4e..1f5da4177e 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep1ConfigTest.kt @@ -18,7 +18,7 @@ open class Sep1ConfigTest { Sep1ConfigTest::class.java.getResource( "/org/stellar/anchor/platform/config/sep1-stellar-test.toml" ) as - URL + URL return Paths.get(resource.toURI()).toFile().absolutePath } @@ -27,7 +27,7 @@ open class Sep1ConfigTest { Sep1ConfigTest::class.java.getResource( "/org/stellar/anchor/platform/config/sep1-stellar-test.toml" ) as - URL + URL return resource.toString() } From 32f1652220304e89302807c3389d711a305cdc9a Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 26 Sep 2022 18:54:31 -0300 Subject: [PATCH 53/65] Feature: add badges for the current version and CI status (#587) ### What Add cosmetic badges to the repo readme, to convey more information to the users about the latest version available. ### Why Close #545 Close #316 --- .github/ISSUE_TEMPLATE/release_a_new_version.md | 4 +++- docs/00 - Stellar Anchor Platform.md | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/release_a_new_version.md b/.github/ISSUE_TEMPLATE/release_a_new_version.md index 046c9da372..b3392fc0a0 100644 --- a/.github/ISSUE_TEMPLATE/release_a_new_version.md +++ b/.github/ISSUE_TEMPLATE/release_a_new_version.md @@ -16,6 +16,7 @@ labels: release - [ ] Cut a branch for the new release out of the `develop` branch, following the gitflow naming pattern `release/0.1.0`. - [ ] Update the code to use this version number. - [ ] Update the [CHANGELOG.md] file with the new version number and release notes. +- [ ] Update the badges versions in [docs/00 - Stellar Anchor Platform.md]. - [ ] Run tests and linting. **Not only CI/CD tests, but also manual tests to make sure the release is up and running, and that it's stable!** - [ ] Make all changes necessary to make sure the release is ready to be published. If new issues are found during the manual tests, create new tickets aiming at improving the automated tests so these issues can be automatically detected next time. - [ ] DO NOT RELEASE before holidays or weekends! Mondays and Tuesdays are preferred. @@ -27,4 +28,5 @@ labels: release - [ ] You'll need to manually publish a new version of the SDK to jitpack and - [ ] You'll need to manually upload the jar file from jitpack to the GH release. -[CHANGELOG.md]: ../../CHANGELOG.md \ No newline at end of file +[CHANGELOG.md]: ../../CHANGELOG.md +[docs/00 - Stellar Anchor Platform.md]: ../../docs/00%20-%20Stellar%20Anchor%20Platform.md diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index ac6af6dcff..a5d73641f1 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -1,3 +1,10 @@ +[![License](https://badgen.net/badge/license/Apache%202/blue?icon=github&label=License)](https://github.com/stellar/java-stellar-anchor-sdk/blob/develop/LICENSE) +[![GitHub Version](https://badgen.net/github/release/stellar/java-stellar-anchor-sdk?icon=github&label=Latest%20release)](https://github.com/stellar/java-stellar-anchor-sdk/releases) +[![Docker](https://badgen.net/badge/Latest%20Release/v1.1.0/blue?icon=docker)](https://hub.docker.com/layers/stellar/anchor-platform/1.1.0/images/sha256-c9df239014d31a29af4e379aaf035e489cabb4c51623d135500022a10d136fbd?context=explore) +![Basic Tests](https://github.com/stellar/java-stellar-anchor-sdk/actions/workflows/basic_tests.yml/badge.svg?branch=main) +![End to end Tests](https://github.com/stellar/java-stellar-anchor-sdk/actions/workflows/end_to_end_tests.yml/badge.svg?branch=main) + +

Stellar
From 6dd1e66aebdc284161cb469e6abb0e0e1a578752 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 26 Sep 2022 19:30:22 -0300 Subject: [PATCH 54/65] Feature: address snyk vulnerabilities (#577) ### What Fix Snyk vulnerabilities by upgrading versions and doing some tweaks to the code. ### Why To make the project safer. --- anchor-reference-server/build.gradle.kts | 1 + .../controller/CustomerController.java | 2 ++ .../Sep24InteractiveController.java | 19 ++++++++--- build.gradle.kts | 1 + core/build.gradle.kts | 1 + .../terraform/codebuild_config.tf | 1 + end-to-end-tests/Dockerfile | 2 +- gradle/libs.versions.toml | 15 ++++++--- integration-tests/build.gradle.kts | 1 + platform/build.gradle.kts | 2 ++ .../controller/PlatformController.java | 2 ++ .../platform/controller/Sep31Controller.java | 1 + .../circle/CirclePaymentObserverService.java | 8 ++++- .../CirclePaymentObserverServiceTest.kt | 32 ++++++++++++++++++- service-runner/build.gradle.kts | 1 + 15 files changed, 77 insertions(+), 12 deletions(-) diff --git a/anchor-reference-server/build.gradle.kts b/anchor-reference-server/build.gradle.kts index cbbf7bcf34..4643973ba5 100644 --- a/anchor-reference-server/build.gradle.kts +++ b/anchor-reference-server/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation("org.springframework.boot:spring-boot") implementation("org.springframework.boot:spring-boot-autoconfigure") implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation(libs.snakeyaml) // used to force the version of snakeyaml (used by springboot) to a safer one. implementation("org.springframework.boot:spring-boot-starter-web") implementation(libs.spring.aws.messaging) diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java index efc57be564..686d19ff4d 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/CustomerController.java @@ -1,5 +1,6 @@ package org.stellar.anchor.reference.controller; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.stellar.anchor.api.callback.GetCustomerRequest; import org.stellar.anchor.api.callback.GetCustomerResponse; @@ -28,6 +29,7 @@ public GetCustomerResponse getCustomer(GetCustomerRequest getCustomerRequest) /** Puts a customer */ @RequestMapping( value = "/customer", + consumes = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PUT}) public PutCustomerResponse putCustomer(@RequestBody PutCustomerRequest request) throws NotFoundException { diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java index eb3bc3dfa5..093816e15e 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/Sep24InteractiveController.java @@ -7,6 +7,8 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.stellar.anchor.api.exception.SepValidationException; +import org.stellar.anchor.util.Log; +import org.stellar.anchor.util.StringHelper; /** The controller that implement the endpoints of the Sep24 interactive flow. */ @Controller @@ -20,9 +22,18 @@ public class Sep24InteractiveController { @ResponseBody public String interactive(HttpServletRequest request) throws SepValidationException { String operation = request.getParameter("operation"); - if (operation == null) throw new SepValidationException("Missing [operation] parameter."); - if (operation.equals("withdraw")) return "The sep24 interactive WITHDRAW starts here."; - else if (operation.equals("deposit")) return "The sep24 interactive DEPOSIT starts here."; - else return String.format("Undefined operation %s", operation); + if (StringHelper.isEmpty(operation)) { + throw new SepValidationException("Missing [operation] parameter."); + } + + switch (operation.toLowerCase()) { + case "deposit": + return "The sep24 interactive DEPOSIT starts here."; + case "withdraw": + return "The sep24 interactive WITHDRAW starts here."; + default: + Log.warnF("Unsupported operation {}", operation); + return "The only supported operations are \"deposit\" or \"withdraw\""; + } } } diff --git a/build.gradle.kts b/build.gradle.kts index a26c0db6e4..f22d1ea4a8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -62,6 +62,7 @@ subprojects { implementation(rootProject.libs.findbugs.jsr305) implementation(rootProject.libs.aws.sqs) implementation(rootProject.libs.postgresql) + implementation(rootProject.libs.scala.library) // used to force the version of scala-library (used by kafka-json-schema-serializer) to a safer one. implementation(rootProject.libs.bundles.kafka) implementation(rootProject.libs.spring.kafka) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index a9da76c5fb..74ae4f9140 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation(libs.spring.kafka) + implementation(rootProject.libs.scala.library) // used to force the version of scala-library (used by kafka-json-schema-serializer) to a safer one. implementation(libs.bundles.kafka) // TODO: Consider to simplify diff --git a/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf b/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf index c9dc792449..5514b0b510 100644 --- a/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf +++ b/docs/resources/deployment-examples/example-fargate/terraform/codebuild_config.tf @@ -17,6 +17,7 @@ resource "aws_ecr_repository" "anchor_config" { resource "aws_s3_bucket" "anchor_config" { bucket = "${var.environment}-anchor-config" + block_public_acls = true tags = { Environment = "${var.environment}" } diff --git a/end-to-end-tests/Dockerfile b/end-to-end-tests/Dockerfile index 3d5c90d59f..17c19e7a50 100644 --- a/end-to-end-tests/Dockerfile +++ b/end-to-end-tests/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-slim-buster +FROM python:3.10-alpine WORKDIR /app diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index de090f57a6..a5ccc500df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,20 +23,22 @@ jjwt = "0.9.1" jsonassert = "1.5.0" junit = "5.8.2" junit-suite-engine = "1.8.2" -kafka = "3.1.0" +kafka = "3.1.2" kafka-json-schema = "7.0.1" kotlin = "1.6.20" log4j = "2.17.1" -log4j-template-json = "2.14.1" +log4j-template-json = "2.17.1" lombok = "1.18.22" mockk = "1.12.2" micrometer-prometheus = "1.9.0" okhttp3 = "4.9.3" -postgresql = "42.3.5" +postgresql = "42.4.1" reactor-core = "3.4.14" reactor-netty = "1.0.15" +scala-library = "2.13.9" servlet-api = "2.5" -spring-kafka = "2.8.4" +snakeyaml = "1.32" +spring-kafka = "2.9.1" spring-aws-messaging = "2.2.6.RELEASE" sqlite-jdbc = "3.34.0" slf4j = "1.7.35" @@ -45,7 +47,7 @@ toml4j = "0.7.2" # Plugin versions spotless = "6.2.1" -spring-boot = "2.6.3" +spring-boot = "2.6.8" spring-dependency-management = "1.0.11.RELEASE" [libraries] @@ -82,6 +84,7 @@ kotlin-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref log4j2-slf4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } log4j2-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } log4j2-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } +snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml"} # TODO: upgrade to log4j2 log4j-template-json = { module = "org.apache.logging.log4j:log4j-layout-template-json", version.ref = "log4j-template-json" } lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" } @@ -89,9 +92,11 @@ micrometer-prometheus = { module = "io.micrometer:micrometer-registry-prometheus mockk = { module = "io.mockk:mockk", version.ref = "mockk" } okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } okhttp3-mockserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp3" } +okhttp3-tls = { module = "com.squareup.okhttp3:okhttp-tls", version.ref = "okhttp3" } postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" } reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "reactor-core" } reactor-netty = { module = "io.projectreactor.netty:reactor-netty", version.ref = "reactor-netty" } +scala-library = { module = "org.scala-lang:scala-library", version.ref = "scala-library" } servlet-api = { module = "javax.servlet:servlet-api", version.ref = "servlet-api" } sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index eef5a0e1b2..62fc10b901 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation("org.springframework.boot:spring-boot") implementation("org.springframework.boot:spring-boot-autoconfigure") implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation(libs.snakeyaml) // used to force the version of snakeyaml (used by springboot) to a safer one. implementation("org.springframework.boot:spring-boot-starter-web") implementation(libs.commons.cli) diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index 6b63fa0661..21164598f8 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation("org.springframework.boot:spring-boot") implementation("org.springframework.boot:spring-boot-autoconfigure") implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation(libs.snakeyaml) // used to force the version of snakeyaml (used by springboot) to a safer one. implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-reactor-netty") implementation("org.springframework.boot:spring-boot-starter-actuator") @@ -45,6 +46,7 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation(libs.okhttp3.mockserver) + testImplementation(libs.okhttp3.tls) } tasks.test { diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java index 8fdb628fca..00ae1f2709 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.controller; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.NotFoundException; @@ -32,6 +33,7 @@ public GetTransactionResponse getTransaction(@PathVariable(name = "id") String t @ResponseStatus(code = HttpStatus.OK) @RequestMapping( value = "/transactions", + consumes = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PATCH}) public PatchTransactionsResponse patchTransactions(@RequestBody PatchTransactionsRequest request) throws AnchorException { diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java index 29c59acba1..8dd4e62789 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java @@ -68,6 +68,7 @@ public Sep31GetTransactionResponse getTransaction( @ResponseStatus(code = HttpStatus.OK) @RequestMapping( value = "/transactions/{id}", + consumes = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PATCH}) public Sep31GetTransactionResponse patchTransaction( HttpServletRequest servletRequest, diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java index 30ac9ba477..0f38b9fade 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.payment.observer.circle; import static org.stellar.anchor.util.MathHelper.*; +import static org.stellar.anchor.util.StringHelper.*; import com.google.gson.Gson; import java.io.IOException; @@ -91,11 +92,16 @@ public void handleCircleNotification(CircleNotification circleNotification) public void handleSubscriptionConfirmationNotification(CircleNotification circleNotification) throws BadRequestException { String subscribeUrl = circleNotification.getSubscribeUrl(); - if (subscribeUrl == null) { + if (isEmpty(subscribeUrl)) { throw new BadRequestException( "Notification body of type SubscriptionConfirmation is missing subscription URL."); } + // sanitize + if (!subscribeUrl.toLowerCase().startsWith("https://")) { + throw new BadRequestException("The subscription URL schema is not of type \"https\"."); + } + Request httpRequest = new Request.Builder() .url(subscribeUrl) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt index eb1503bf76..b9170d5093 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt @@ -3,9 +3,12 @@ package org.stellar.anchor.platform.payment.observer import io.mockk.* import io.mockk.impl.annotations.MockK import java.io.IOException +import java.net.InetAddress import okhttp3.OkHttpClient import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer +import okhttp3.tls.HandshakeCertificates +import okhttp3.tls.HeldCertificate import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach @@ -39,6 +42,32 @@ class CirclePaymentObserverServiceTest { @MockK private lateinit var paymentListener: PaymentListener private lateinit var server: MockWebServer + fun mockSslServerAndClient(): Pair { + // create mocked certificate + val localhost = InetAddress.getByName("localhost").canonicalHostName + val localhostCertificate = + HeldCertificate.Builder().addSubjectAlternativeName(localhost).build() + val serverCertificates = + HandshakeCertificates.Builder().heldCertificate(localhostCertificate).build() + + // create mock server + val server = MockWebServer() + server.useHttps(serverCertificates.sslSocketFactory(), false) // enforce "https" + server.start() + + // create client with trusted certificate + val clientCertificates = + HandshakeCertificates.Builder() + .addTrustedCertificate(localhostCertificate.certificate) + .build() + val httpClient = + OkHttpClient.Builder() + .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager) + .build() + + return Pair(server, httpClient) + } + @BeforeEach fun setUp() { MockKAnnotations.init(this, relaxed = true) @@ -92,6 +121,8 @@ class CirclePaymentObserverServiceTest { @Test fun test_handleCircleNotification_handleSubscriptionConfirmationNotification() { + val (server, newHttpClient) = mockSslServerAndClient() + // missing subscribeUrl var subConfirmationNotification = mapOf("Type" to "SubscriptionConfirmation") @@ -121,7 +152,6 @@ class CirclePaymentObserverServiceTest { assertInstanceOf(BadRequestException::class.java, ex) // Failing http request - val newHttpClient = OkHttpClient.Builder().build() circlePaymentObserverService = CirclePaymentObserverService( newHttpClient, diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index 67f99322c7..eae9f4e78e 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -10,6 +10,7 @@ plugins { } dependencies { + implementation(libs.snakeyaml) // used to force the version of snakeyaml (used by springboot) to a safer one. implementation("org.springframework.boot:spring-boot-starter-web") implementation(libs.commons.cli) From 23a656874d020b67339d4f8a6c7c8b0d7b9c2fa1 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 26 Sep 2022 20:07:09 -0300 Subject: [PATCH 55/65] Feature: add CodeQL (#591) ### What Add CodeQL. ### Why It's very beneficial to the project to have this script executed often and ensure the code is protected against common vulnerabilities. --- .github/workflows/codeql_analysis.yml | 80 +++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/codeql_analysis.yml diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml new file mode 100644 index 0000000000..45bd435502 --- /dev/null +++ b/.github/workflows/codeql_analysis.yml @@ -0,0 +1,80 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: + - develop + - main + - 'release/**' + - 'releases/**' + pull_request: + # The branches below must be a subset of the branches above + branches: + - develop + - main + schedule: + - cron: '30 9 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From ffc24b79f8d7e38c9eb7a54821a5a918d94cd221 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Wed, 28 Sep 2022 14:32:49 -0300 Subject: [PATCH 56/65] Hotfix: update helm chart version to 0.3.88 (#600) ### What Update helm chart version to 0.3.88. ### Why It had not been updated when we made the Stellar Observer into a separate service in https://github.com/stellar/java-stellar-anchor-sdk/pull/546/files#diff-78b9c67cf91b015c1ed14485867e595e1ba056a8e28563c2d037c8fe073a7893. Propagating https://github.com/stellar/java-stellar-anchor-sdk/pull/596 to `develop`. --- CHANGELOG.md | 4 ++++ build.gradle.kts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5941b8ce9a..e8ffaafca5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.1.1 + +Update the version of Helm Chart. + ## 1.1.0 * SDK support for [SEP-1], [SEP-10], [SEP-12], [SEP-31] & [SEP-38]. diff --git a/build.gradle.kts b/build.gradle.kts index f22d1ea4a8..1ebf482c20 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -111,7 +111,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "1.1.0" + version = "1.1.1" tasks.jar { manifest { From 5d581d23442adbdf7673e49b14df29e54e202767 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 29 Sep 2022 22:55:16 -0700 Subject: [PATCH 57/65] added workflow_dispatch (#601) --- .github/workflows/basic_tests.yml | 15 ++++++++++----- .github/workflows/branch_develop.yml | 11 +++++++++-- .github/workflows/branch_release.yml | 3 ++- .github/workflows/codeql_analysis.yml | 3 ++- .github/workflows/end_to_end_tests.yml | 3 ++- .github/workflows/published.yml | 1 + 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index 217cdc2c6a..c385d0ca4f 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -1,12 +1,17 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven +# This workflow will build a Java project with Gradle. +# This workflow is triggered: +# 1. Called from other workflows. +# 2. On every pull request +# 3. When commits are pushed or merged onto `main` branch +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven name: Basic Tests on: - pull_request: - workflow_call: # Allows this workflow to be called from another workflow - push: + workflow_dispatch: + workflow_call: # allows this workflow to be called from another workflow + pull_request: # when a pull request is created. + push: # when commits are pushed or merged onto `main` branch branches: - main diff --git a/.github/workflows/branch_develop.yml b/.github/workflows/branch_develop.yml index dc40e21405..3d57d6419f 100644 --- a/.github/workflows/branch_develop.yml +++ b/.github/workflows/branch_develop.yml @@ -1,9 +1,16 @@ -name: "develop Branch" +# This workflow will build a docker image and push to docker hub +# This workflow is triggered: +# 1. When commits are pushed or merged onto `develop` branch +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + + +name: Push/Merge to `develop` Branch on: + workflow_dispatch: push: branches: - - develop + - develop # when commits are pushed or merged onto `develop` branch jobs: tests: diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml index 0660d6d290..8f16d0ecc0 100644 --- a/.github/workflows/branch_release.yml +++ b/.github/workflows/branch_release.yml @@ -1,6 +1,7 @@ -name: "release/** Branch" +name: Push/Merge to any `release` Branches on: + workflow_dispatch: push: branches: - 'release/**' diff --git a/.github/workflows/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml index 45bd435502..541c11c035 100644 --- a/.github/workflows/codeql_analysis.yml +++ b/.github/workflows/codeql_analysis.yml @@ -9,9 +9,10 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: CodeQL on: + workflow_dispatch: push: branches: - develop diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index 7a7acfa3a1..a190025201 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -1,6 +1,7 @@ -name: Integration Tests +name: End-2-End Integration Tests on: + workflow_dispatch: workflow_call: # Allows this workflow to be called from another workflow push: branches: diff --git a/.github/workflows/published.yml b/.github/workflows/published.yml index 9bd9b7325c..694966f395 100644 --- a/.github/workflows/published.yml +++ b/.github/workflows/published.yml @@ -1,6 +1,7 @@ name: Release Published on: + workflow_dispatch: release: types: - published From 99fd38d13209127fa0757dbf818ab41f360722b1 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 3 Oct 2022 09:28:23 -0700 Subject: [PATCH 58/65] Events configuration refactoring (#594) Refactored events configuration. --- .../stellar/anchor/config/EventConfig.java | 7 - .../anchor/config/EventTypeToQueueConfig.java | 7 - .../anchor/config/PublisherConfig.java | 22 --- .../anchor/config/event/EventConfig.java | 30 +++++ .../anchor/config/event/PublisherConfig.java | 5 + ...ublishService.java => EventPublisher.java} | 4 +- .../stellar/anchor/event/EventService.java | 45 +++++++ .../anchor/event/KafkaEventService.java | 61 --------- .../anchor/event/NoopEventService.java | 13 -- .../stellar/anchor/sep31/Sep31Service.java | 6 +- .../stellar/anchor/sep38/Sep38Service.java | 6 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 6 +- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 7 +- .../platform/ConfigManagementBeans.java | 37 +++-- .../stellar/anchor/platform/EventsBeans.java | 41 ++++-- .../anchor/platform/SepServiceBeans.java | 8 +- .../anchor/platform/config/KafkaConfig.java | 27 ++++ .../anchor/platform/config/MskConfig.java | 10 ++ .../platform/config/PropertyEventConfig.java | 70 +++++++--- .../PropertyEventTypeToQueueConfig.java | 32 ----- .../config/PropertyPublisherConfig.java | 53 +------- .../anchor/platform/config/SqsConfig.java | 9 ++ .../platform/event/KafkaEventPublisher.java | 52 ++++++++ .../platform/event/MskEventPublisher.java | 35 +++++ .../platform/event/NoopEventPublisher.java | 13 ++ .../platform/event/SqsEventPublisher.java | 38 ++---- .../PaymentOperationToEventListener.java | 11 +- .../config/anchor-config-default-values.yaml | 126 +++++++++++------- .../config/anchor-config-schema-v1.yaml | 36 +++-- .../main/resources/example.anchor-config.yaml | 23 ++-- .../anchor/platform/config/EventConfigTest.kt | 72 +++++----- .../PaymentOperationToEventListenerTest.kt | 4 +- .../sep31/Sep31DepositInfoGeneratorTest.kt | 4 +- 33 files changed, 510 insertions(+), 410 deletions(-) delete mode 100644 core/src/main/java/org/stellar/anchor/config/EventConfig.java delete mode 100644 core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java delete mode 100644 core/src/main/java/org/stellar/anchor/config/PublisherConfig.java create mode 100644 core/src/main/java/org/stellar/anchor/config/event/EventConfig.java create mode 100644 core/src/main/java/org/stellar/anchor/config/event/PublisherConfig.java rename core/src/main/java/org/stellar/anchor/event/{EventPublishService.java => EventPublisher.java} (52%) create mode 100644 core/src/main/java/org/stellar/anchor/event/EventService.java delete mode 100644 core/src/main/java/org/stellar/anchor/event/KafkaEventService.java delete mode 100644 core/src/main/java/org/stellar/anchor/event/NoopEventService.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/MskConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/SqsConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/event/NoopEventPublisher.java rename core/src/main/java/org/stellar/anchor/event/SqsEventService.java => platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java (50%) diff --git a/core/src/main/java/org/stellar/anchor/config/EventConfig.java b/core/src/main/java/org/stellar/anchor/config/EventConfig.java deleted file mode 100644 index d312a6a49f..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/EventConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.config; - -public interface EventConfig { - boolean isEnabled(); - - String getPublisherType(); -} diff --git a/core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java b/core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java deleted file mode 100644 index 38363f88cd..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/EventTypeToQueueConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.config; - -import java.util.Map; - -public interface EventTypeToQueueConfig { - Map getEventTypeToQueueMap(); -} diff --git a/core/src/main/java/org/stellar/anchor/config/PublisherConfig.java b/core/src/main/java/org/stellar/anchor/config/PublisherConfig.java deleted file mode 100644 index 98724ddbc0..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/PublisherConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.stellar.anchor.config; - -import java.util.Map; -import org.springframework.validation.BindException; - -public interface PublisherConfig { - String getBootstrapServer(); - - boolean isUseSingleQueue(); - - boolean isUseIAM(); - - Map getEventTypeToQueue(); - - BindException validate(String publisherType); - - String getRegion(); - - String getAccessKey(); - - String getSecretKey(); -} diff --git a/core/src/main/java/org/stellar/anchor/config/event/EventConfig.java b/core/src/main/java/org/stellar/anchor/config/event/EventConfig.java new file mode 100644 index 0000000000..9e24da25f7 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/config/event/EventConfig.java @@ -0,0 +1,30 @@ +package org.stellar.anchor.config.event; + +import java.util.Map; + +public interface EventConfig { + /** + * Indicates if the event service is enabled. + * + * @return + *
true
+ * if the service is enabled; + *
false
+ * otherwise. + */ + boolean isEnabled(); + + /** + * Gets the mapping from event type to the queue name. + * + * @return the mapping from the event type to the queue name. + */ + Map getEventTypeToQueue(); + + /** + * Gets the publisher configuration. + * + * @return the publisher configuration. + */ + PublisherConfig getPublisher(); +} diff --git a/core/src/main/java/org/stellar/anchor/config/event/PublisherConfig.java b/core/src/main/java/org/stellar/anchor/config/event/PublisherConfig.java new file mode 100644 index 0000000000..8c154bd3b8 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/config/event/PublisherConfig.java @@ -0,0 +1,5 @@ +package org.stellar.anchor.config.event; + +public interface PublisherConfig { + String getType(); +} diff --git a/core/src/main/java/org/stellar/anchor/event/EventPublishService.java b/core/src/main/java/org/stellar/anchor/event/EventPublisher.java similarity index 52% rename from core/src/main/java/org/stellar/anchor/event/EventPublishService.java rename to core/src/main/java/org/stellar/anchor/event/EventPublisher.java index 2af1ad071e..f4017dcd6b 100644 --- a/core/src/main/java/org/stellar/anchor/event/EventPublishService.java +++ b/core/src/main/java/org/stellar/anchor/event/EventPublisher.java @@ -2,6 +2,6 @@ import org.stellar.anchor.event.models.AnchorEvent; -public interface EventPublishService { - void publish(AnchorEvent event); +public interface EventPublisher { + void publish(String queue, AnchorEvent event); } diff --git a/core/src/main/java/org/stellar/anchor/event/EventService.java b/core/src/main/java/org/stellar/anchor/event/EventService.java new file mode 100644 index 0000000000..80a7edbd81 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/event/EventService.java @@ -0,0 +1,45 @@ +package org.stellar.anchor.event; + +import static org.stellar.anchor.util.Log.errorF; + +import io.micrometer.core.instrument.Metrics; +import java.util.Map; +import org.stellar.anchor.config.event.EventConfig; +import org.stellar.anchor.event.models.AnchorEvent; + +public class EventService { + private final EventConfig eventConfig; + private EventPublisher eventPublisher; + + private final Map eventTypeMapping; + + public EventService(EventConfig eventConfig) { + this.eventConfig = eventConfig; + this.eventTypeMapping = eventConfig.getEventTypeToQueue(); + } + + public void setEventPublisher(EventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + } + + public void publish(AnchorEvent event) { + if (eventConfig.isEnabled()) { + // publish the event + eventPublisher.publish(getQueue(event.getType()), event); + // update metrics + Metrics.counter( + "event.published", "class", event.getClass().getSimpleName(), "type", event.getType()) + .increment(); + } + } + + String getQueue(String eventType) { + String queue = eventTypeMapping.get(eventType); + if (queue == null) { + errorF("There is no queue defined for event type:{}", eventType); + throw new RuntimeException( + String.format("There is no queue defined for event type:%s", eventType)); + } + return queue; + } +} diff --git a/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java b/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java deleted file mode 100644 index 7e06ec4318..0000000000 --- a/core/src/main/java/org/stellar/anchor/event/KafkaEventService.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.stellar.anchor.event; - -import io.micrometer.core.instrument.Metrics; -import java.util.Map; -import java.util.Properties; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.Producer; -import org.apache.kafka.clients.producer.ProducerConfig; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.header.internals.RecordHeader; -import org.apache.kafka.common.serialization.StringSerializer; -import org.springframework.kafka.support.serializer.JsonSerializer; -import org.stellar.anchor.config.PublisherConfig; -import org.stellar.anchor.event.models.AnchorEvent; -import org.stellar.anchor.util.Log; - -public class KafkaEventService implements EventPublishService { - final Producer producer; - final Map eventTypeToQueue; - final boolean useSingleQueue; - - public KafkaEventService(PublisherConfig kafkaConfig) { - Log.debugF("kafkaConfig: {}", kafkaConfig); - Properties props = new Properties(); - props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServer()); - props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); - if (kafkaConfig.isUseIAM()) { - props.put("security.protocol", "SASL_SSL"); - props.put("sasl.mechanism", "AWS_MSK_IAM"); - props.put("sasl.jaas.config", "software.amazon.msk.auth.iam.IAMLoginModule required;"); - props.put( - "sasl.client.callback.handler.class", - "software.amazon.msk.auth.iam.IAMClientCallbackHandler"); - } - - this.producer = new KafkaProducer<>(props); - this.eventTypeToQueue = kafkaConfig.getEventTypeToQueue(); - - this.useSingleQueue = kafkaConfig.isUseSingleQueue(); - } - - public void publish(AnchorEvent event) { - try { - String topic; - if (useSingleQueue) { - topic = eventTypeToQueue.get("all"); - } else { - topic = eventTypeToQueue.get(event.getType()); - } - ProducerRecord record = new ProducerRecord<>(topic, event); - record.headers().add(new RecordHeader("type", event.getType().getBytes())); - producer.send(record); - Metrics.counter( - "event.published", "class", event.getClass().getSimpleName(), "type", event.getType()) - .increment(); - } catch (Exception ex) { - Log.errorEx(ex); - } - } -} diff --git a/core/src/main/java/org/stellar/anchor/event/NoopEventService.java b/core/src/main/java/org/stellar/anchor/event/NoopEventService.java deleted file mode 100644 index 5f32e8f453..0000000000 --- a/core/src/main/java/org/stellar/anchor/event/NoopEventService.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.stellar.anchor.event; - -import static org.stellar.anchor.util.Log.debugF; - -import org.stellar.anchor.event.models.AnchorEvent; - -public class NoopEventService implements EventPublishService { - @Override - public void publish(AnchorEvent event) { - // noop - debugF("Event ID={} is published to NOOP class.", event.getEventId()); - } -} diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index 4856f9cd61..cd27be8f90 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -36,7 +36,7 @@ import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.Sep31Config; -import org.stellar.anchor.event.EventPublishService; +import org.stellar.anchor.event.EventService; import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.sep38.Sep38Quote; import org.stellar.anchor.sep38.Sep38QuoteStore; @@ -52,7 +52,7 @@ public class Sep31Service { private final FeeIntegration feeIntegration; private final CustomerIntegration customerIntegration; private final Sep31InfoResponse infoResponse; - private final EventPublishService eventService; + private final EventService eventService; public Sep31Service( AppConfig appConfig, @@ -63,7 +63,7 @@ public Sep31Service( AssetService assetService, FeeIntegration feeIntegration, CustomerIntegration customerIntegration, - EventPublishService eventService) { + EventService eventService) { debug("appConfig:", appConfig); debug("sep31Config:", sep31Config); this.appConfig = appConfig; diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java index f4c0f06e1c..93db093338 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java @@ -26,7 +26,7 @@ import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.Sep38Config; -import org.stellar.anchor.event.EventPublishService; +import org.stellar.anchor.event.EventService; import org.stellar.anchor.event.models.QuoteEvent; import org.stellar.anchor.util.Log; @@ -35,7 +35,7 @@ public class Sep38Service { final AssetService assetService; final RateIntegration rateIntegration; final Sep38QuoteStore sep38QuoteStore; - final EventPublishService eventService; + final EventService eventService; final InfoResponse infoResponse; final Map assetMap; @@ -44,7 +44,7 @@ public Sep38Service( AssetService assetService, RateIntegration rateIntegration, Sep38QuoteStore sep38QuoteStore, - EventPublishService eventService) { + EventService eventService) { debug("sep38Config:", sep38Config); this.sep38Config = sep38Config; this.assetService = assetService; 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 cbe89620ce..5829991fb4 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -38,9 +38,9 @@ import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep31Config import org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_RECEIVE import org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND -import org.stellar.anchor.event.EventPublishService +import org.stellar.anchor.event.EventService import org.stellar.anchor.event.models.TransactionEvent -import org.stellar.anchor.sep31.Sep31Service.* +import org.stellar.anchor.sep31.Sep31Service.Context import org.stellar.anchor.sep38.PojoSep38Quote import org.stellar.anchor.sep38.Sep38QuoteStore import org.stellar.anchor.util.GsonUtils @@ -278,7 +278,7 @@ class Sep31ServiceTest { @MockK(relaxed = true) lateinit var quoteStore: Sep38QuoteStore @MockK(relaxed = true) lateinit var feeIntegration: FeeIntegration @MockK(relaxed = true) lateinit var customerIntegration: CustomerIntegration - @MockK(relaxed = true) lateinit var eventPublishService: EventPublishService + @MockK(relaxed = true) lateinit var eventPublishService: EventService private lateinit var jwtService: JwtService private lateinit var sep31Service: Sep31Service diff --git a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt index 16af292f38..bc4164bd1e 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -19,13 +19,14 @@ import org.stellar.anchor.api.exception.NotFoundException import org.stellar.anchor.api.exception.ServerErrorException import org.stellar.anchor.api.sep.AssetInfo import org.stellar.anchor.api.sep.sep38.* -import org.stellar.anchor.api.sep.sep38.Sep38Context.* +import org.stellar.anchor.api.sep.sep38.Sep38Context.SEP31 +import org.stellar.anchor.api.sep.sep38.Sep38Context.SEP6 import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep38Config -import org.stellar.anchor.event.EventPublishService +import org.stellar.anchor.event.EventService import org.stellar.anchor.event.models.QuoteEvent class Sep38ServiceTest { @@ -50,7 +51,7 @@ class Sep38ServiceTest { @MockK(relaxed = true) private lateinit var quoteStore: Sep38QuoteStore // events related - @MockK(relaxed = true) private lateinit var eventService: EventPublishService + @MockK(relaxed = true) private lateinit var eventService: EventService // sep10 related: @MockK(relaxed = true) private lateinit var appConfig: AppConfig diff --git a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java index 276fff29d4..4a20dac01e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java @@ -40,6 +40,9 @@ PlatformApiConfig platformApiConfig(PropertySecretConfig secretConfig) { return new PlatformApiConfig(secretConfig); } + /********************************** + * SEP configurations + */ @Bean @ConfigurationProperties(prefix = "sep1") Sep1Config sep1Config() { @@ -76,18 +79,15 @@ Sep38Config sep38Config() { return new PropertySep38Config(); } + /********************************** + * Payment observer configurations + */ @Bean @ConfigurationProperties(prefix = "circle") CircleConfig circleConfig() { return new PropertyCircleConfig(); } - @Bean - @ConfigurationProperties - PropertySecretConfig secretConfig() { - return new PropertySecretConfig(); - } - @Bean @ConfigurationProperties(prefix = "payment-observer") PaymentObserverConfig paymentObserverConfig() { @@ -99,22 +99,13 @@ CirclePaymentConfig circlePaymentConfig() { return new CirclePaymentConfig(); } + /********************************** + * Event configurations + */ @Bean @ConfigurationProperties(prefix = "events") - EventConfig eventConfig(PublisherConfig publisherConfig) { - return new PropertyEventConfig(publisherConfig); - } - - @Bean - @ConfigurationProperties(prefix = "events.options") - PublisherConfig publisherConfig(EventTypeToQueueConfig eventTypeToQueueConfig) { - return new PropertyPublisherConfig(eventTypeToQueueConfig); - } - - @Bean - @ConfigurationProperties(prefix = "events.options.event-type-to-queue") - EventTypeToQueueConfig eventTypeToQueueConfig() { - return new PropertyEventTypeToQueueConfig(); + PropertyEventConfig eventConfig() { + return new PropertyEventConfig(); } @Bean @@ -122,4 +113,10 @@ EventTypeToQueueConfig eventTypeToQueueConfig() { MetricConfig metricConfig() { return new PropertyMetricConfig(); } + + @Bean + @ConfigurationProperties + PropertySecretConfig secretConfig() { + return new PropertySecretConfig(); + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java b/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java index 0fae3429ef..42c30efd99 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java @@ -1,30 +1,43 @@ package org.stellar.anchor.platform; +import static org.stellar.anchor.util.Log.errorF; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.stellar.anchor.config.*; -import org.stellar.anchor.event.EventPublishService; -import org.stellar.anchor.event.KafkaEventService; -import org.stellar.anchor.event.NoopEventService; -import org.stellar.anchor.event.SqsEventService; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.event.*; +import org.stellar.anchor.platform.config.PropertyEventConfig; +import org.stellar.anchor.platform.event.KafkaEventPublisher; +import org.stellar.anchor.platform.event.MskEventPublisher; +import org.stellar.anchor.platform.event.NoopEventPublisher; +import org.stellar.anchor.platform.event.SqsEventPublisher; @Configuration public class EventsBeans { @Bean - public EventPublishService eventService( - EventConfig eventConfig, PublisherConfig publisherConfig) { + public EventService eventService(PropertyEventConfig eventConfig) throws InvalidConfigException { + EventService eventService = new EventService(eventConfig); if (!eventConfig.isEnabled()) { - return new NoopEventService(); + eventService.setEventPublisher(new NoopEventPublisher()); } - // TODO handle when event publishing is disabled - switch (eventConfig.getPublisherType()) { + String publisherType = eventConfig.getPublisher().getType(); + switch (publisherType) { case "kafka": - return new KafkaEventService(publisherConfig); + eventService.setEventPublisher( + new KafkaEventPublisher(eventConfig.getPublisher().getKafka())); + break; case "sqs": - return new SqsEventService(publisherConfig); + eventService.setEventPublisher(new SqsEventPublisher(eventConfig.getPublisher().getSqs())); + break; + case "msk": + eventService.setEventPublisher(new MskEventPublisher(eventConfig.getPublisher().getMsk())); + break; default: - throw new RuntimeException( - String.format("Invalid event publisher: %s", eventConfig.getPublisherType())); + errorF("Invalid event publisher: {}", publisherType); + throw new InvalidConfigException( + String.format("Invalid event publisher: %s", publisherType)); } + + return eventService; } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java index 6deb30af39..4c97da3c3f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java @@ -14,7 +14,7 @@ import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.config.*; -import org.stellar.anchor.event.EventPublishService; +import org.stellar.anchor.event.EventService; import org.stellar.anchor.filter.JwtTokenFilter; import org.stellar.anchor.horizon.Horizon; import org.stellar.anchor.platform.data.*; @@ -155,7 +155,7 @@ Sep31Service sep31Service( AssetService assetService, FeeIntegration feeIntegration, CustomerIntegration customerIntegration, - EventPublishService eventPublishService) { + EventService eventService) { return new Sep31Service( appConfig, sep31Config, @@ -165,7 +165,7 @@ Sep31Service sep31Service( assetService, feeIntegration, customerIntegration, - eventPublishService); + eventService); } @Bean @@ -184,7 +184,7 @@ Sep38Service sep38Service( AssetService assetService, RateIntegration rateIntegration, Sep38QuoteStore sep38QuoteStore, - EventPublishService eventService) { + EventService eventService) { return new Sep38Service( sep38Config, assetService, rateIntegration, sep38QuoteStore, eventService); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java new file mode 100644 index 0000000000..587cf0819d --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java @@ -0,0 +1,27 @@ +package org.stellar.anchor.platform.config; + +import lombok.Data; + +@Data +public class KafkaConfig { + /** + * A comma-separated list of host:port pairs that are the addresses of one or more brokers in a + * Kafka cluster, e.g. localhost:9092 or localhost:9092,another.host:9092. + */ + String bootstrapServers; + + /** The client ID. If left empty, it is randomly generated. */ + String clientId; + + /** + * Determines how many times the producer will attempt to send a message before marking it as + * failed. + */ + int retries; + + /** Determines the time to wait before sending messages out to Kafka. */ + int lingerMs; + + /** Determines the maximum amount of data to be collected before sending the batch. */ + int batchSize; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/MskConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/MskConfig.java new file mode 100644 index 0000000000..0be307bf80 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/MskConfig.java @@ -0,0 +1,10 @@ +package org.stellar.anchor.platform.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class MskConfig extends KafkaConfig { + boolean useIAM; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java index ce070d43f1..c851836f2d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java @@ -1,23 +1,18 @@ package org.stellar.anchor.platform.config; +import java.util.Map; import lombok.Data; -import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; -import org.stellar.anchor.config.EventConfig; -import org.stellar.anchor.config.PublisherConfig; +import org.stellar.anchor.config.event.EventConfig; +import org.stellar.anchor.config.event.PublisherConfig; @Data public class PropertyEventConfig implements EventConfig, Validator { private boolean enabled = false; - private String publisherType; - - PublisherConfig publisherConfig; - - public PropertyEventConfig(PublisherConfig kafkaConfig) { - this.publisherConfig = kafkaConfig; - } + private PropertyPublisherConfig publisher; + private Map eventTypeToQueue; @Override public boolean supports(Class clazz) { @@ -31,15 +26,54 @@ public void validate(Object target, Errors errors) { return; } - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "publisherType", ""); + // Validate publisher type + ValidationUtils.rejectIfEmptyOrWhitespace(errors, "publisher.type", ""); + + PublisherConfig publisherConfig = config.getPublisher(); + String publisherType = publisherConfig.getType(); + switch (publisherType) { + case "msk": + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "publisher.msk.useIAM", + "empty-msk-use-iam", + "use_IAM must be defined for MSK publisher"); + // continue to the kafka case. DO NOT break + case "kafka": + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, "publisher.kafka.bootstrapServers", "empty-bootstrapServer"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "publisher.kafka.retries", + "empty-retries", + "retries must be set for KAFKA/MSK publisher"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "publisher.kafka.batchSize", + "empty-batch-size", + "batch_size must be set for KAFKA/MSK publisher"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "publisher.kafka.lingerMs", + "empty-linger-ms", + "linger_ms must be set for KAFKA/MSK publisher"); - BindException validation = publisherConfig.validate(config.getPublisherType()); - if (validation.hasErrors()) { - String errorString = validation.getAllErrors().get(0).toString(); - errors.rejectValue( - "publisherConfig", - "badPublisherConfig", - String.format("event publisher not properly configured: %s", errorString)); + break; + case "sqs": + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "publisher.sqs.useIAM", + "empty-sqs-use-iam", + "use_IAM must be defined for SQS publisher"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "publisher.sqs.awsRegion", + "empty-aws-region", + "aws_region must be defined for SQS publisher"); + break; + default: + errors.rejectValue( + "publisherType", "invalidType-publisherType", "publisherType set to unknown type"); } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java deleted file mode 100644 index 506f20ef1e..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventTypeToQueueConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.stellar.anchor.platform.config; - -import java.util.HashMap; -import java.util.Map; -import lombok.Data; -import org.stellar.anchor.config.EventTypeToQueueConfig; - -@Data -public class PropertyEventTypeToQueueConfig implements EventTypeToQueueConfig { - - private Map eventTypeToQueueMap; - - private String all = "ap_event_single_queue"; - private String quote_created = "ap_event_quote_created"; - private String transaction_created = "ap_event_transaction_created"; - private String transaction_status_changed = "ap_event_transaction_status_changed"; - private String transaction_error = "ap_event_transaction_error"; - - public PropertyEventTypeToQueueConfig() { - this.eventTypeToQueueMap = new HashMap<>(); - this.eventTypeToQueueMap.put("all", this.all); - this.eventTypeToQueueMap.put("quote_created", this.quote_created); - this.eventTypeToQueueMap.put("transaction_created", this.transaction_created); - this.eventTypeToQueueMap.put("transaction_status_changed", this.transaction_status_changed); - this.eventTypeToQueueMap.put("transaction_error", this.transaction_error); - } - - @Override - public Map getEventTypeToQueueMap() { - return this.eventTypeToQueueMap; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java index d83a38d09c..15acb85ad7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPublisherConfig.java @@ -1,55 +1,12 @@ package org.stellar.anchor.platform.config; -import java.util.Map; import lombok.Data; -import org.springframework.validation.BindException; -import org.springframework.validation.ValidationUtils; -import org.stellar.anchor.config.EventTypeToQueueConfig; -import org.stellar.anchor.config.PublisherConfig; +import org.stellar.anchor.config.event.*; @Data public class PropertyPublisherConfig implements PublisherConfig { - private String bootstrapServer; - private Boolean useSingleQueue; - - private Boolean useIAM; - private String region; - private String accessKey; - private String secretKey; - - private Map eventTypeToQueue; - - public PropertyPublisherConfig(EventTypeToQueueConfig eventTypeToQueueConfig) { - this.eventTypeToQueue = eventTypeToQueueConfig.getEventTypeToQueueMap(); - } - - @Override - public boolean isUseSingleQueue() { - return useSingleQueue; - } - - @Override - public boolean isUseIAM() { - return useIAM; - } - - public BindException validate(String publisherType) { - BindException errors = new BindException(this, "publisherConfig"); - switch (publisherType) { - case "kafka": - ValidationUtils.rejectIfEmptyOrWhitespace( - errors, "bootstrapServer", "empty-bootstrapServer"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "useSingleQueue", "empty-useSingleQueue"); - break; - case "sqs": - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "region", "empty-region"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "accessKey", "empty-accessKey"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "secretKey", "empty-secretKey"); - break; - default: - errors.rejectValue( - "publisherType", "invalidType-publisherType", "publisherType set to unknown type"); - } - return errors; - } + String type; + KafkaConfig kafka; + SqsConfig sqs; + MskConfig msk; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/SqsConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/SqsConfig.java new file mode 100644 index 0000000000..ff683209e4 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/SqsConfig.java @@ -0,0 +1,9 @@ +package org.stellar.anchor.platform.config; + +import lombok.Data; + +@Data +public class SqsConfig { + boolean useIAM; + String awsRegion; +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java new file mode 100644 index 0000000000..e0598746b0 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java @@ -0,0 +1,52 @@ +package org.stellar.anchor.platform.event; + +import java.util.Properties; + +import lombok.NoArgsConstructor; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.Producer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.kafka.support.serializer.JsonSerializer; +import org.stellar.anchor.event.EventPublisher; +import org.stellar.anchor.event.models.AnchorEvent; +import org.stellar.anchor.platform.config.KafkaConfig; +import org.stellar.anchor.util.Log; + +import static org.apache.kafka.clients.producer.ProducerConfig.*; + +@NoArgsConstructor +public class KafkaEventPublisher implements EventPublisher { + Producer producer; + + public KafkaEventPublisher(KafkaConfig kafkaConfig) { + Log.debugF("kafkaConfig: {}", kafkaConfig); + Properties props = new Properties(); + props.put(BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServers()); + props.put(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(CLIENT_ID_CONFIG, kafkaConfig.getClientId()); + props.put(RETRIES_CONFIG, kafkaConfig.getRetries()); + props.put(LINGER_MS_CONFIG, kafkaConfig.getLingerMs()); + props.put(BATCH_SIZE_CONFIG, kafkaConfig.getBatchSize()); + + createPublisher(props); + } + + protected void createPublisher(Properties props) { + this.producer = new KafkaProducer<>(props); + } + + @Override + public void publish(String queue, AnchorEvent event) { + try { + ProducerRecord record = new ProducerRecord<>(queue, event); + record.headers().add(new RecordHeader("type", event.getType().getBytes())); + producer.send(record); + } catch (Exception ex) { + Log.errorEx(ex); + } + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java new file mode 100644 index 0000000000..aecce3187c --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java @@ -0,0 +1,35 @@ +package org.stellar.anchor.platform.event; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.kafka.support.serializer.JsonSerializer; +import org.stellar.anchor.platform.config.MskConfig; +import org.stellar.anchor.util.Log; + +import java.util.Properties; + +import static org.apache.kafka.clients.producer.ProducerConfig.*; + +public class MskEventPublisher extends KafkaEventPublisher { + public MskEventPublisher(MskConfig mskConfig) { + Log.debugF("MskConfig: {}", mskConfig); + Properties props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, mskConfig.getBootstrapServers()); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); + props.put(CLIENT_ID_CONFIG, mskConfig.getClientId()); + props.put(RETRIES_CONFIG, mskConfig.getRetries()); + props.put(LINGER_MS_CONFIG, mskConfig.getLingerMs()); + props.put(BATCH_SIZE_CONFIG, mskConfig.getBatchSize()); + + if (mskConfig.isUseIAM()) { + props.put("security.protocol", "SASL_SSL"); + props.put("sasl.mechanism", "AWS_MSK_IAM"); + props.put("sasl.jaas.config", "software.amazon.msk.auth.iam.IAMLoginModule"); + props.put( + "sasl.client.callback.handler.class", + "software.amazon.msk.auth.iam.IAMClientCallbackHandler"); + } + createPublisher(props); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/NoopEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/NoopEventPublisher.java new file mode 100644 index 0000000000..8a0570ed76 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/event/NoopEventPublisher.java @@ -0,0 +1,13 @@ +package org.stellar.anchor.platform.event; + +import static org.stellar.anchor.util.Log.debugF; + +import org.stellar.anchor.event.EventPublisher; +import org.stellar.anchor.event.models.AnchorEvent; + +public class NoopEventPublisher implements EventPublisher { + @Override + public void publish(String queue, AnchorEvent event) { + debugF("Event ID={} is published to NOOP class.", event.getEventId()); + } +} diff --git a/core/src/main/java/org/stellar/anchor/event/SqsEventService.java b/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java similarity index 50% rename from core/src/main/java/org/stellar/anchor/event/SqsEventService.java rename to platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java index 228ea55c75..bd0acfc3fa 100644 --- a/core/src/main/java/org/stellar/anchor/event/SqsEventService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java @@ -1,46 +1,28 @@ -package org.stellar.anchor.event; +package org.stellar.anchor.platform.event; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.sqs.AmazonSQSAsync; import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder; import com.amazonaws.services.sqs.model.MessageAttributeValue; import com.amazonaws.services.sqs.model.SendMessageRequest; import com.google.gson.Gson; -import io.micrometer.core.instrument.Metrics; -import java.util.Map; -import org.stellar.anchor.config.PublisherConfig; +import org.stellar.anchor.event.EventPublisher; import org.stellar.anchor.event.models.AnchorEvent; +import org.stellar.anchor.platform.config.SqsConfig; import org.stellar.anchor.util.Log; -public class SqsEventService implements EventPublishService { +public class SqsEventPublisher implements EventPublisher { final AmazonSQSAsync sqsClient; - final Map eventTypeToQueue; - final boolean useSingleQueue; - public SqsEventService(PublisherConfig sqsConfig) { + public SqsEventPublisher(SqsConfig sqsConfig) { this.sqsClient = - AmazonSQSAsyncClientBuilder.standard() - .withRegion(sqsConfig.getRegion()) - .withCredentials( - new AWSStaticCredentialsProvider( - new BasicAWSCredentials(sqsConfig.getAccessKey(), sqsConfig.getSecretKey()))) - .build(); - - this.eventTypeToQueue = sqsConfig.getEventTypeToQueue(); - this.useSingleQueue = sqsConfig.isUseSingleQueue(); + AmazonSQSAsyncClientBuilder.standard().withRegion(sqsConfig.getAwsRegion()).build(); } - public void publish(AnchorEvent event) { + @Override + public void publish(String queue, AnchorEvent event) { try { // TODO implement batching // TODO retry logic? - String queue; - if (useSingleQueue) { - queue = eventTypeToQueue.get("all"); - } else { - queue = eventTypeToQueue.get(event.getType()); - } Gson gson = new Gson(); String eventStr = gson.toJson(event); @@ -58,9 +40,7 @@ public void publish(AnchorEvent event) { .withDataType("String") .withStringValue(event.getClass().getSimpleName())); sqsClient.sendMessage(sendMessageRequest); - Metrics.counter( - "event.published", "class", event.getClass().getSimpleName(), "type", event.getType()) - .increment(); + } catch (Exception ex) { Log.errorEx(ex); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index 2e8cb8e5b3..2bfc23e268 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.service; -import static org.stellar.anchor.util.MathHelper.*; +import static org.stellar.anchor.util.MathHelper.decimal; +import static org.stellar.anchor.util.MathHelper.formatAmount; import io.micrometer.core.instrument.Metrics; import java.math.BigDecimal; @@ -18,8 +19,8 @@ import org.stellar.anchor.api.shared.Amount; import org.stellar.anchor.api.shared.StellarPayment; import org.stellar.anchor.api.shared.StellarTransaction; -import org.stellar.anchor.event.EventPublishService; -import org.stellar.anchor.event.models.*; +import org.stellar.anchor.event.EventService; +import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.platform.payment.observer.PaymentListener; import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; import org.stellar.anchor.sep31.Sep31Transaction; @@ -33,10 +34,10 @@ @Profile("stellar-observer") public class PaymentOperationToEventListener implements PaymentListener { final Sep31TransactionStore transactionStore; - final EventPublishService eventService; + final EventService eventService; PaymentOperationToEventListener( - Sep31TransactionStore transactionStore, EventPublishService eventService) { + Sep31TransactionStore transactionStore, EventService eventService) { this.transactionStore = transactionStore; this.eventService = eventService; } diff --git a/platform/src/main/resources/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index 6c98d02213..5c627a92f8 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -330,62 +330,89 @@ metrics: ######################### ## The events being sent from the platform are the ones described in the `Events Schema.yml` file events: - ## @param: enabled ## @type: bool ## Whether to enable events. # enabled: false + ## @param: event_type_to_queue + ## @type: map + ## Mapping of the event type to the queue name that messages are published to + event_type_to_queue: + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + ## @param: publisher_type - ## @supported_values: `kafka`, `sqs` + ## @supported_values: `kafka`, `sqs`, or `msk` ## The type of queue to use for event publishing # - publisher_type: kafka - - - options: - - ## @param: bootstrap_server - ## @type: string - ## The Kafka server used to bootstrap setup - ## For MSK, use port 9098 for access from within AWS and port 9198 for public access and specify - ## AWS credentials. - ## https://docs.aws.amazon.com/msk/latest/developerguide/port-info.html - # - bootstrap_server: localhost:29092 - - ## @param: use_IAM - ## @type: boolean - ## Use IAM authentication for AWS MSK or AWS SQS. - ## SQS FIFO queues should be pre-created in AWS (Anchor Platform will not create them) - ## If true, @required_secrets: - ## AWS_ACCESS_KEY_ID - ## AWS_SECRET_ACCESS_KEY - # - use_IAM: false - - ## @param: region - ## @type: string - ## AWS region for the queue. Can also be defined as environment variable: AWS_REGION - # - aws_region: us-east-1 - - ## @param: use_single_queue - ## @type: boolean - ## If true, all events are published to a single queue (specified in eventTypeToQueue.all) - use_single_queue: false - - ## @param: event_type_to_queue - ## @type: map - ## Mapping of the event type to the queue name that messages are published to - event_type_to_queue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error - + publisher: + type: kafka + + ## If the value of `events.publisher.type` is "kafka", the `events.publisher.kafka` field must be defined. + kafka: + ## @param: bootstrap_server + ## @type: string + ## A comma-separated list of host:port pairs that are the addresses of one or more brokers in a Kafka cluster, + ## e.g. localhost:9092 or localhost:9092,another.host:9092. + # + bootstrap_servers: localhost:29092 + # The client ID. If left empty, it is randomly generated. + client_id: + # Determines how many times the producer will attempt to send a message before marking it as failed. + retries: 1 + # Determines the time to wait before sending messages out to Kafka. + linger_ms: 1000 + # Determines the maximum amount of data to be collected before sending the batch. + batch_size: 10 + + ## If the value of `publisher.type` is "sqs", the `events.publisher.sqs` field must be defined. + sqs: + ## @param: use_IAM + ## @type: boolean + ## Use IAM authentication for AWS MSK or AWS SQS. + ## SQS FIFO queues should be pre-created in AWS (Anchor Platform will not create them) + ## If true, please follow the `Set up AWS Credentials and Region for Development` + ## at https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html + # + use_IAM: false + + ## @param: region + ## @type: string + ## AWS region for the queue. Can also be defined as environment variable: AWS_REGION + # + aws_region: us-east-1 + + ## If the value of `publisher.type` is "msk", the `events.publisher.msk` field must be defined. + msk: + ## @param: use_IAM + ## @type: boolean + ## Use IAM authentication for AWS MSK or AWS SQS. + ## SQS FIFO queues should be pre-created in AWS (Anchor Platform will not create them) + ## + ## If true, please follow the `Set up AWS Credentials and Region for Development` + ## at https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html + # + use_IAM: false + + ## @param: bootstrap_server + ## @type: string + ## The Kafka server used to bootstrap setup in AWS + ## AWS credentials. + ## https://docs.aws.amazon.com/msk/latest/developerguide/port-info.html + # + bootstrap_server: # b-1-public.democluster1.w7j4hi.c25.kafka.us-east-1.amazonaws.com:9198 + # The client ID. If left empty, it is randomly generated. + client_id: + # Determines how many times the producer will attempt to send a message before marking it as failed. + retries: 1 + # Determines the time to wait before sending messages out to Kafka. + linger_ms: 1000 + # Determines the maximum amount of data to be collected before sending the batch. + batch_size: 10 ######################### ## Assets Configuration @@ -426,8 +453,9 @@ data: ## @supported_values: ## `h2` (in-memory), `sqlite` (local), `postgres` (local), `aurora` (postgres on AWS) ## Type of storage. - ## If this is set to `aurora`, - ## @required_secrets: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + ## If this is set to `aurora`, please follow the `Set up AWS Credentials and Region for Development` + ## at https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html + # # type: h2 diff --git a/platform/src/main/resources/config/anchor-config-schema-v1.yaml b/platform/src/main/resources/config/anchor-config-schema-v1.yaml index 163d755b3b..f121333405 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -3,8 +3,8 @@ assets.value: callback_api.auth.expiration_milliseconds: callback_api.auth.type: callback_api.base_url: -data.flyway_enabled: data.ddl_auto: +data.flyway_enabled: data.flyway_location: data.initial_connection_pool_size: data.max_active_connections: @@ -13,16 +13,24 @@ data.type: data.url: data.username: events.enabled: -events.options.aws_region: -events.options.bootstrap_server: -events.options.event_type_to_queue.all: -events.options.event_type_to_queue.quote_created: -events.options.event_type_to_queue.transaction_created: -events.options.event_type_to_queue.transaction_error: -events.options.event_type_to_queue.transaction_status_changed: -events.options.use_iam: -events.options.use_single_queue: -events.publisher_type: +events.event_type_to_queue.quote_created: +events.event_type_to_queue.transaction_created: +events.event_type_to_queue.transaction_error: +events.event_type_to_queue.transaction_status_changed: +events.publisher.kafka.batch_size: +events.publisher.kafka.bootstrap_servers: +events.publisher.kafka.client_id: +events.publisher.kafka.linger_ms: +events.publisher.kafka.retries: +events.publisher.msk.batch_size: +events.publisher.msk.bootstrap_server: +events.publisher.msk.client_id: +events.publisher.msk.linger_ms: +events.publisher.msk.retries: +events.publisher.msk.use_iam: +events.publisher.sqs.aws_region: +events.publisher.sqs.use_iam: +events.publisher.type: host_url: languages: logging.level: @@ -33,8 +41,12 @@ metrics.run_interval: payment_observer.circle_url: payment_observer.enabled: payment_observer.tracked_wallet: -platform_api.auth.type: platform_api.auth.expiration_milliseconds: +platform_api.auth.type: +secret.callback_api.secret: +secret.jwt_secret: +secret.platform_api.secret: +secret.sep10_signing_seed: sep1.enabled: sep1.toml.type: sep1.toml.value: diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml index 47db947159..1dea51cbcb 100644 --- a/platform/src/main/resources/example.anchor-config.yaml +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -96,16 +96,19 @@ metrics: events: enabled: true - publisher_type: kafka - options: - bootstrap_server: localhost:29092 - use_single_queue: false - event_type_to_queue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error + event_type_to_queue: + quote_created: ap_event_quote_created + transaction_created: ap_event_transaction_created + transaction_status_changed: ap_event_transaction_status_changed + transaction_error: ap_event_transaction_error + publisher: + type: kafka + kafka: + bootstrap_servers: localhost:29092 + client_id: + retries: 1 + linger_ms: 1000 + batch_size: 10 assets: type: json diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt index e1e8e6e50c..9530a16bea 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt @@ -2,7 +2,7 @@ package org.stellar.anchor.platform.config import kotlin.test.assertContains import kotlin.test.assertEquals -import org.junit.jupiter.api.* +import org.junit.jupiter.api.Test import org.springframework.validation.BindException import org.springframework.validation.ValidationUtils @@ -10,7 +10,7 @@ open class EventConfigTest { @Test fun testDisabledEventConfig() { // events are disabled - val eventConfig = PropertyEventConfig(null) + val eventConfig = PropertyEventConfig() eventConfig.isEnabled = false val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) @@ -20,14 +20,15 @@ open class EventConfigTest { @Test fun testEnabledForKafka() { // Events correctly configured for kafka - val kafkaConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) - kafkaConfig.isUseSingleQueue = true - kafkaConfig.bootstrapServer = "localhost:29092" - kafkaConfig.eventTypeToQueue = HashMap() + val eventConfig = PropertyEventConfig() + val kafkaConfig = KafkaConfig() - val eventConfig = PropertyEventConfig(kafkaConfig) + kafkaConfig.bootstrapServers = "localhost:29092" + eventConfig.publisher = PropertyPublisherConfig() + eventConfig.publisher.type = "kafka" + eventConfig.publisher.kafka = kafkaConfig eventConfig.isEnabled = true - eventConfig.publisherType = "kafka" + val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) assertEquals(0, errors.errorCount) @@ -35,17 +36,16 @@ open class EventConfigTest { @Test fun testEnabledForSqs() { - // Events correctly configured for sqs - val sqsConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) - sqsConfig.accessKey = "accessKey" - sqsConfig.secretKey = "secretKey" - sqsConfig.isUseSingleQueue = true - sqsConfig.region = "region" - sqsConfig.eventTypeToQueue = HashMap() + // Events correctly configured for kafka + val eventConfig = PropertyEventConfig() + val sqsConfig = SqsConfig() - val eventConfig = PropertyEventConfig(sqsConfig) + sqsConfig.awsRegion = "us-east" + eventConfig.publisher = PropertyPublisherConfig() + eventConfig.publisher.type = "sqs" + eventConfig.publisher.sqs = sqsConfig eventConfig.isEnabled = true - eventConfig.publisherType = "sqs" + val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) assertEquals(0, errors.errorCount) @@ -53,43 +53,33 @@ open class EventConfigTest { @Test fun testKafkaMissingFields() { - val kafkaConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) - kafkaConfig.isUseSingleQueue = true - kafkaConfig.eventTypeToQueue = HashMap() + val eventConfig = PropertyEventConfig() + val kafkaConfig = KafkaConfig() - val eventConfig = PropertyEventConfig(kafkaConfig) + eventConfig.publisher = PropertyPublisherConfig() + eventConfig.publisher.type = "kafka" + eventConfig.publisher.kafka = kafkaConfig eventConfig.isEnabled = true - eventConfig.publisherType = "kafka" - val errors = BindException(eventConfig, "eventConfig") + val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "badPublisherConfig") } - - val kafkaErrors = kafkaConfig.validate(eventConfig.publisherType) - assertEquals(1, kafkaErrors.errorCount) - kafkaErrors.message?.let { assertContains(it, "empty-bootstrapServer") } + errors.message?.let { assertContains(it, "empty-bootstrapServer") } } @Test fun testSqsMissingFields() { - val sqsConfig = PropertyPublisherConfig(PropertyEventTypeToQueueConfig()) - sqsConfig.isUseSingleQueue = true - sqsConfig.eventTypeToQueue = HashMap() - sqsConfig.accessKey = "accessKey" - sqsConfig.secretKey = "secretKey" + val eventConfig = PropertyEventConfig() + val sqsConfig = SqsConfig() - val eventConfig = PropertyEventConfig(sqsConfig) + eventConfig.publisher = PropertyPublisherConfig() + eventConfig.publisher.type = "sqs" + eventConfig.publisher.sqs = sqsConfig eventConfig.isEnabled = true - eventConfig.publisherType = "sqs" - val errors = BindException(eventConfig, "eventConfig") + val errors = BindException(eventConfig, "eventConfig") ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "badPublisherConfig") } - - val sqsErrors = sqsConfig.validate(eventConfig.publisherType) - assertEquals(1, sqsErrors.errorCount) - sqsErrors.message?.let { assertContains(it, "empty-region") } + errors.message?.let { assertContains(it, "empty-aws-region") } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt index 6155d8a021..e6ea469c69 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt @@ -15,7 +15,7 @@ import org.stellar.anchor.api.shared.Customers import org.stellar.anchor.api.shared.StellarId import org.stellar.anchor.api.shared.StellarPayment import org.stellar.anchor.api.shared.StellarTransaction -import org.stellar.anchor.event.EventPublishService +import org.stellar.anchor.event.EventService import org.stellar.anchor.event.models.* import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.data.JdbcSep31TransactionStore @@ -25,7 +25,7 @@ import org.stellar.anchor.util.GsonUtils class PaymentOperationToEventListenerTest { @MockK(relaxed = true) private lateinit var transactionStore: JdbcSep31TransactionStore - @MockK(relaxed = true) private lateinit var eventPublishService: EventPublishService + @MockK(relaxed = true) private lateinit var eventPublishService: EventService private lateinit var paymentOperationToEventListener: PaymentOperationToEventListener private val gson = GsonUtils.getInstance() diff --git a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt index 2a379f2ac9..b8c7bdedba 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -20,7 +20,7 @@ import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.CircleConfig import org.stellar.anchor.config.Sep31Config -import org.stellar.anchor.event.EventPublishService +import org.stellar.anchor.event.EventService import org.stellar.anchor.horizon.Horizon import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.platform.payment.config.CirclePaymentConfig @@ -61,7 +61,7 @@ class Sep31DepositInfoGeneratorTest { @MockK(relaxed = true) private lateinit var quoteStore: Sep38QuoteStore @MockK(relaxed = true) private lateinit var feeIntegration: FeeIntegration @MockK(relaxed = true) private lateinit var customerIntegration: CustomerIntegration - @MockK(relaxed = true) private lateinit var eventPublishService: EventPublishService + @MockK(relaxed = true) private lateinit var eventPublishService: EventService @MockK(relaxed = true) private lateinit var txn: Sep31Transaction private lateinit var sep31Service: Sep31Service From 74becb334fad9f32752b604d6a993ce31ce758c0 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Mon, 3 Oct 2022 20:58:56 -0300 Subject: [PATCH 59/65] Fix: workflow secrets were not being passed from parent (caller) to child (callee) workflows (#609) ### What When calling another workflow, make sure it will inherit the secrets from its parent. ### Why Jobs like [this](https://github.com/stellar/java-stellar-anchor-sdk/actions/runs/3137863047/jobs/5100787037) were failing because the secrets were not being passed from caller to callee. GH covers this in https://docs.github.com/en/enterprise-cloud@latest/actions/using-workflows/reusing-workflows#passing-secrets-to-nested-workflows. --- .github/workflows/branch_develop.yml | 1 + .github/workflows/branch_release.yml | 1 + .github/workflows/published.yml | 2 ++ 3 files changed, 4 insertions(+) diff --git a/.github/workflows/branch_develop.yml b/.github/workflows/branch_develop.yml index 3d57d6419f..9c429d53bf 100644 --- a/.github/workflows/branch_develop.yml +++ b/.github/workflows/branch_develop.yml @@ -15,6 +15,7 @@ on: jobs: tests: uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests + secrets: inherit # pass all secrets build_and_push_docker_image: name: Push to DockerHub # stellar/anchor-platform:{sha} and stellar/anchor-platform:edge diff --git a/.github/workflows/branch_release.yml b/.github/workflows/branch_release.yml index 8f16d0ecc0..2e5e5caf74 100644 --- a/.github/workflows/branch_release.yml +++ b/.github/workflows/branch_release.yml @@ -10,6 +10,7 @@ on: jobs: tests: uses: ./.github/workflows/basic_tests.yml # use the callable tests job to run tests + secrets: inherit # pass all secrets build_and_push_docker_image: name: Push to DockerHub # stellar/anchor-platform:sha diff --git a/.github/workflows/published.yml b/.github/workflows/published.yml index 694966f395..d1371f4aee 100644 --- a/.github/workflows/published.yml +++ b/.github/workflows/published.yml @@ -9,9 +9,11 @@ on: jobs: tests: uses: ./.github/workflows/basic_tests.yml # execute the callable basic_tests.yml + secrets: inherit # pass all secrets end_to_end_tests: uses: ./.github/workflows/end_to_end_tests.yml # execute the callable end_to_end_tests.yml + secrets: inherit # pass all secrets build_and_push_docker_image: name: Push to DockerHub # stellar/anchor-platform:{VERSION} From 4c942efcb3b6a4951b808196627f94aa94e44126 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 6 Oct 2022 11:11:10 -0700 Subject: [PATCH 60/65] Update the action after merging the release to main (#619) --- .github/ISSUE_TEMPLATE/release_a_new_version.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/release_a_new_version.md b/.github/ISSUE_TEMPLATE/release_a_new_version.md index b3392fc0a0..e726669370 100644 --- a/.github/ISSUE_TEMPLATE/release_a_new_version.md +++ b/.github/ISSUE_TEMPLATE/release_a_new_version.md @@ -23,10 +23,10 @@ labels: release - [ ] When the team is confident the release is stable, you'll need to create two pull requests: - [ ] `release/0.1.0 -> main`: this should require two approvals. - [ ] `release/0.1.0 -> develop`: ideally, this should be merged after the `main` branch is merged. -- [ ] Create a new release on GitHub with the name `0.1.0` and the changes from the [CHANGELOG.md] file. +- [ ] After the release(e.g. `0.1.0`) branch is merged to `main`, create a new release on GitHub with the name `0.1.0` and the changes from the [CHANGELOG.md] file. - [ ] The release will automatically publish a new version of the docker image to Docker Hub. - - [ ] You'll need to manually publish a new version of the SDK to jitpack and - - [ ] You'll need to manually upload the jar file from jitpack to the GH release. + - You'll need to manually publish a new version of the SDK to [Maven Central](https://search.maven.org/search?q=g:org.stellar.anchor-sdk). + - [ ] You'll need to manually upload the jar file from [Maven Central](https://search.maven.org/search?q=g:org.stellar.anchor-sdk) to the GH release. [CHANGELOG.md]: ../../CHANGELOG.md [docs/00 - Stellar Anchor Platform.md]: ../../docs/00%20-%20Stellar%20Anchor%20Platform.md From 159b4b958a08986d8ec674b662cf45d3ea39006d Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Fri, 7 Oct 2022 13:37:23 -0300 Subject: [PATCH 61/65] Release to develop 1.2.0 (#620) ### What - [x] #608 - [x] #602 - [x] #607 - [x] #610 - [x] Add `1.2.0` version to the changelog. - [x] #618 - [x] #612 ### Why To merge Release 1.2.0 into dev. --- CHANGELOG.md | 4 + .../api/exception/EventPublishException.java | 14 + build.gradle.kts | 12 +- .../stellar/anchor/event/EventPublisher.java | 3 +- .../stellar/anchor/event/EventService.java | 3 +- .../stellar/anchor/sep31/Sep31Service.java | 1 + .../stellar/anchor/sep38/Sep38Service.java | 1 + .../anchor/util/ExponentialBackoffTimer.java | 45 ++ .../util/ExponentialBackoffTimerTest.kt | 80 +++ helm-charts/sep-service/example_values.yaml | 4 + helm-charts/sep-service/new-yaml-all | 648 ++++++++++++++++++ .../sep-service/templates/deployment.yaml | 3 + .../platform/AnchorPlatformIntegrationTest.kt | 47 +- .../stellar/anchor/platform/SepTestSuite.kt | 2 +- .../platform/StellarObservingService.java | 13 +- .../callback/PlatformIntegrationHelper.java | 39 +- .../CirclePaymentObserverController.java | 10 +- .../controller/PlatformController.java | 2 + .../platform/controller/Sep10Controller.java | 2 + .../platform/controller/Sep12Controller.java | 2 + .../platform/controller/Sep1Controller.java | 2 + .../platform/controller/Sep24Controller.java | 2 + .../platform/controller/Sep31Controller.java | 2 + .../platform/controller/Sep38Controller.java | 2 + .../platform/event/KafkaEventPublisher.java | 16 +- .../platform/event/MskEventPublisher.java | 7 +- .../platform/event/SqsEventPublisher.java | 17 +- .../payment/observer/PaymentListener.java | 3 +- .../circle/CirclePaymentObserverService.java | 12 +- .../stellar/StellarPaymentObserver.java | 44 +- .../PaymentOperationToEventListener.java | 7 +- .../platform/service/TransactionService.java | 1 + .../service/TransactionServiceTest.kt | 1 + .../anchor/platform/ServiceRunner.java | 16 +- 34 files changed, 990 insertions(+), 77 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/EventPublishException.java create mode 100644 core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java create mode 100644 core/src/test/kotlin/org/stellar/anchor/util/ExponentialBackoffTimerTest.kt create mode 100644 helm-charts/sep-service/new-yaml-all diff --git a/CHANGELOG.md b/CHANGELOG.md index e8ffaafca5..db7f24d9cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.0 +* Add Stellar observer retries with exponential back-off timer [#607](https://github.com/stellar/java-stellar-anchor-sdk/pull/607) +* Add health check endpoint to the Stellar observer [#602](https://github.com/stellar/java-stellar-anchor-sdk/pull/602) + ## 1.1.1 Update the version of Helm Chart. diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/EventPublishException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/EventPublishException.java new file mode 100644 index 0000000000..f17433f87a --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/EventPublishException.java @@ -0,0 +1,14 @@ +package org.stellar.anchor.api.exception; + +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = false) +public class EventPublishException extends AnchorException { + public EventPublishException(String message, Exception cause) { + super(message, cause); + } + + public EventPublishException(String message) { + super(message); + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 1ebf482c20..2854596987 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -97,7 +97,15 @@ subprojects { javadoc { options.encoding = "UTF-8" } - test { useJUnitPlatform() } + test { + useJUnitPlatform() + + testLogging { + events("SKIPPED", "FAILED") + showExceptions = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } + } } configurations { @@ -111,7 +119,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "1.1.1" + version = "1.2.0" tasks.jar { manifest { diff --git a/core/src/main/java/org/stellar/anchor/event/EventPublisher.java b/core/src/main/java/org/stellar/anchor/event/EventPublisher.java index f4017dcd6b..6a1a482fd2 100644 --- a/core/src/main/java/org/stellar/anchor/event/EventPublisher.java +++ b/core/src/main/java/org/stellar/anchor/event/EventPublisher.java @@ -1,7 +1,8 @@ package org.stellar.anchor.event; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.event.models.AnchorEvent; public interface EventPublisher { - void publish(String queue, AnchorEvent event); + void publish(String queue, AnchorEvent event) throws EventPublishException; } diff --git a/core/src/main/java/org/stellar/anchor/event/EventService.java b/core/src/main/java/org/stellar/anchor/event/EventService.java index 80a7edbd81..0ef400d1c3 100644 --- a/core/src/main/java/org/stellar/anchor/event/EventService.java +++ b/core/src/main/java/org/stellar/anchor/event/EventService.java @@ -4,6 +4,7 @@ import io.micrometer.core.instrument.Metrics; import java.util.Map; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.config.event.EventConfig; import org.stellar.anchor.event.models.AnchorEvent; @@ -22,7 +23,7 @@ public void setEventPublisher(EventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } - public void publish(AnchorEvent event) { + public void publish(AnchorEvent event) throws EventPublishException { if (eventConfig.isEnabled()) { // publish the event eventPublisher.publish(getQueue(event.getType()), event); diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java index cd27be8f90..5e863d50d9 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -177,6 +177,7 @@ public Sep31PostTransactionResponse postTransaction( Context.get().setTransaction(txn); updateAmounts(); + // TODO: open the connection with DB and only commit/save after publishing the event: Context.get().setTransaction(sep31TransactionStore.save(txn)); txn = Context.get().getTransaction(); diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java index 93db093338..471a736022 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38Service.java @@ -397,6 +397,7 @@ public Sep38QuoteResponse postQuote(JwtToken token, Sep38PostQuoteRequest reques .fee(rate.getFee()) .build(); + // TODO: open the connection with DB and only commit/save after publishing the event: this.sep38QuoteStore.save(newQuote); QuoteEvent event = diff --git a/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java b/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java new file mode 100644 index 0000000000..ebf0059361 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java @@ -0,0 +1,45 @@ +package org.stellar.anchor.util; + +/** + * ExponentialBackoffUtil is used to do an exponential back-off, where the sleep time is doubled + * every time, until things succeed and the `resetSleepSeconds()` method is called. + */ +public class ExponentialBackoffTimer { + static final long DEFAULT_INITIAL_SLEEP_SECONDS = 1; + static final long DEFAULT_MAX_SLEEP_SECONDS = 300; // 5 minutes + + final long initialSleepSeconds; + final long maxSleepSeconds; + long sleepSeconds; + + public ExponentialBackoffTimer() { + this(DEFAULT_INITIAL_SLEEP_SECONDS, DEFAULT_MAX_SLEEP_SECONDS); + } + + public ExponentialBackoffTimer(long initialSleepSeconds, long maxSleepSeconds) { + if (initialSleepSeconds < 1) { + throw new IllegalArgumentException( + "The formula 'initialSleepSeconds >= 1' is not being respected."); + } + this.initialSleepSeconds = initialSleepSeconds; + this.sleepSeconds = initialSleepSeconds; + + if (maxSleepSeconds < initialSleepSeconds) { + throw new IllegalArgumentException( + "The formula 'maxSleepSeconds >= initialSleepSeconds' is not being respected."); + } + this.maxSleepSeconds = maxSleepSeconds; + } + + public void increase() { + sleepSeconds = Long.min(sleepSeconds * 2, maxSleepSeconds); + } + + public void reset() { + sleepSeconds = initialSleepSeconds; + } + + public void sleep() throws InterruptedException { + Thread.sleep(sleepSeconds * 1000); + } +} diff --git a/core/src/test/kotlin/org/stellar/anchor/util/ExponentialBackoffTimerTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/ExponentialBackoffTimerTest.kt new file mode 100644 index 0000000000..0ef3bf505d --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/util/ExponentialBackoffTimerTest.kt @@ -0,0 +1,80 @@ +package org.stellar.anchor.util + +import java.time.Instant +import java.time.temporal.ChronoUnit +import kotlin.test.assertEquals +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +internal class ExponentialBackoffTimerTest { + @Test + fun test_constructorWithParameters() { + // validate if 'initialSleepSeconds >= 1' + var ex: IllegalArgumentException = assertThrows { ExponentialBackoffTimer(0, 0) } + assertEquals("The formula 'initialSleepSeconds >= 1' is not being respected.", ex.message) + + // validate if 'maxSleepSeconds >= initialSleepSeconds' + ex = assertThrows { ExponentialBackoffTimer(1, 0) } + assertEquals( + "The formula 'maxSleepSeconds >= initialSleepSeconds' is not being respected.", + ex.message + ) + + // constructor with all parameters works + lateinit var exponentialBackoffTimer: ExponentialBackoffTimer + assertDoesNotThrow { exponentialBackoffTimer = ExponentialBackoffTimer(1, 2) } + assertEquals(1, exponentialBackoffTimer.initialSleepSeconds) + assertEquals(2, exponentialBackoffTimer.maxSleepSeconds) + + // constructor with no parameters works + assertDoesNotThrow { exponentialBackoffTimer = ExponentialBackoffTimer() } + assertEquals( + ExponentialBackoffTimer.DEFAULT_INITIAL_SLEEP_SECONDS, + exponentialBackoffTimer.initialSleepSeconds + ) + assertEquals( + ExponentialBackoffTimer.DEFAULT_MAX_SLEEP_SECONDS, + exponentialBackoffTimer.maxSleepSeconds + ) + } + + @Test + fun test_increase() { + val exponentialBackoffTimer = ExponentialBackoffTimer(1, 5) + assertEquals(1, exponentialBackoffTimer.sleepSeconds) + + exponentialBackoffTimer.increase() + assertEquals(2, exponentialBackoffTimer.sleepSeconds) + + exponentialBackoffTimer.increase() + assertEquals(4, exponentialBackoffTimer.sleepSeconds) + + exponentialBackoffTimer.increase() + assertEquals(5, exponentialBackoffTimer.sleepSeconds) + + exponentialBackoffTimer.increase() + assertEquals(5, exponentialBackoffTimer.sleepSeconds) + } + + @Test + fun test_reset() { + val exponentialBackoffTimer = ExponentialBackoffTimer(1, 5) + exponentialBackoffTimer.increase() + assertEquals(2, exponentialBackoffTimer.sleepSeconds) + + exponentialBackoffTimer.reset() + assertEquals(1, exponentialBackoffTimer.sleepSeconds) + } + + @Test + fun test_sleep() { + val exponentialBackoffTimer = ExponentialBackoffTimer(1, 5) + + val beforeSleep = Instant.now() + exponentialBackoffTimer.sleep() + val afterSleep = Instant.now() + + assertEquals(1, ChronoUnit.SECONDS.between(beforeSleep, afterSleep)) + } +} diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index 5fc7db21fd..6e6bb7d695 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -31,6 +31,10 @@ deployment: backend: servicePort: "{{ .Values.service.containerPort }}" serviceName: "{{ .Values.fullName }}-svc-{{ .Values.service.name }}" + annotations: + prometheus.io/path: /actuator/prometheus + prometheus.io/port: "8082" + prometheus.io/scrape: "true" env: - name: STELLAR_ANCHOR_CONFIG value: file:/config/anchor-config.yaml diff --git a/helm-charts/sep-service/new-yaml-all b/helm-charts/sep-service/new-yaml-all new file mode 100644 index 0000000000..9a88f541fc --- /dev/null +++ b/helm-charts/sep-service/new-yaml-all @@ -0,0 +1,648 @@ +--- +# Source: sep/templates/configmap.yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: anchor-platform-sep-server-preview-id-assets +data: + assets.json: | + { + "assets": [ + { + "code": "USDC", + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "max_amount": 1000000, + "min_amount": 0 + }, + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "schema": "stellar", + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "max_amount": 1000000, + "min_amount": 0 + }, + "sep24_enabled": true, + "sep31": { + "fields": { + "transaction": { + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "type": { + "choices": [ + "SEPA", + "SWIFT" + ], + "description": "type of deposit to make" + } + } + }, + "quotes_required": false, + "quotes_supported": true, + "sep12": { + "receiver": { + "types": { + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving USD" + }, + "sep31-receiver": { + "description": "U.S. citizens receiving USD" + } + } + }, + "sender": { + "types": { + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + } + } + } + } + }, + "sep31_enabled": true, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep38_enabled": true, + "significant_decimals": 2, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "max_amount": 1000000, + "min_amount": 0 + } + }, + { + "code": "JPYC", + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "max_amount": 1000000, + "min_amount": 0 + }, + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "schema": "stellar", + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "max_amount": 1000000, + "min_amount": 0 + }, + "sep24_enabled": false, + "sep31": { + "fields": { + "transaction": { + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "type": { + "choices": [ + "SEPA", + "SWIFT" + ], + "description": "type of deposit to make" + } + } + }, + "quotes_required": false, + "quotes_supported": true, + "sep12": { + "receiver": { + "types": { + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving JPY" + }, + "sep31-receiver": { + "description": "U.S. citizens receiving JPY" + } + } + }, + "sender": { + "types": { + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + } + } + } + } + }, + "sep31_enabled": true, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep38_enabled": true, + "significant_decimals": 4, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "max_amount": 1000000, + "min_amount": 0 + } + }, + { + "code": "USD", + "deposit": { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "max_amount": 10000, + "min_amount": 1 + }, + "schema": "iso4217", + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "max_amount": 10000, + "min_amount": 1 + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38": { + "buy_delivery_methods": [ + { + "description": "Have USD sent directly to your bank account.", + "name": "WIRE" + } + ], + "country_codes": [ + "USA" + ], + "decimals": 4, + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "sell_delivery_methods": [ + { + "description": "Send USD directly to the Anchor's bank account.", + "name": "WIRE" + } + ] + }, + "sep38_enabled": true, + "withdraw": { + "enabled": false, + "fee_fixed": 0, + "fee_percent": 0, + "max_amount": 10000, + "min_amount": 1 + } + } + ] + } +--- +# Source: sep/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: anchor-platform-sep-server-preview-id +data: + # TODO: remove all secrets (ex: move to env vars, k8s secrets, vault, etc...) + anchor-config.yaml: | + stellar: + anchor: + config: in-memory + app-config: + type: config-spring-property # Activate [config-spring-property] module + settings: app-config # The location of the configuration data + data-access: + type: data-spring-jdbc # Activate [config-spring-jdbc] module. + settings: data-spring-jdbc-sqlite # The location of the configuration data in this file. + logging: + type: logging-logback + settings: logging-logback-settings + app-config: + app: + stellarNetwork: TESTNET + stellarNetworkPassphrase: Test SDF Network ; September 2015 + horizonUrl: https://horizon-testnet.stellar.org + hostUrl: https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com + languages: en + assets: file:/assets/assets.json + jwtSecretKey: ${JWT_SECRET} + integration-auth: + authType: JWT_TOKEN + platformToAnchorSecret: ${PLATFORM_TO_ANCHOR_SECRET} + anchorToPlatformSecret: ${ANCHOR_TO_PLATFORM_SECRET} + expirationMilliseconds: 30000 + # The anchor callback configuration + anchor-callback: + # The CallbackAPI endpoint + endpoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com + # sep-1 + sep1: + enabled: true + stellarFile: file:/config/stellar-wks.toml + # sep-10 + sep10: + enabled: true + homeDomain: anchor-sep-preview-id.previews.kube001.services.stellar-ops.com + clientAttributionRequired: false + clientAttributionAllowList: lobstr.co,preview.lobstr.co + # clientAttributionDenyList: # use this if we want to black list. + authTimeout: 900 + jwtTimeout: 86400 + signingSeed: ${SEP10_SIGNING_SEED} + requireKnownOmnibusAccount: false + # sep-12 + sep12: + enabled: true + customerIntegrationEndpoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com + # sep-24 + sep24: + enabled: false + interactiveJwtExpiration: 3600 + interactiveUrl: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com + # sep-31 + sep31: + enabled: true + feeIntegrationEndPoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com + uniqueAddressIntegrationEndPoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com + depositInfoGeneratorType: api + paymentType: STRICT_SEND + # sep-38 + sep38: + enabled: true + quoteIntegrationEndPoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com + circle: + circleUrl: https://api-sandbox.circle.com + apiKey: ${CIRCLE_API_KEY} + payment-gateway: + circle: + name: circle + enabled: true + stellar: + enabled: false + name: stellar + horizonUrl: https://horizon-testnet.stellar.org + secretKey: ${PAYMENT_GATEWAY_STELLAR_SECRET_KEY} # stellar account secret key + circle-payment-observer: + enabled: true + horizonUrl: https://horizon-testnet.stellar.org + stellarNetwork: TESTNET + trackedWallet: all + event: + # If enabled, publish Events to a queue (publisherType) + # publisherType - the type of queue to use for event publishing + enabled: true + publisherType: kafka + metrics-service: + optionalMetricsEnabled: false # optional metrics that periodically query the database + runInterval: 30 # interval to query the database to generate the optional metrics + kafka.publisher: + bootstrapServer: anchor-platform-kafka.anchor-platform-dev.svc.cluster.local:9092 + useIAM: false + useSingleQueue: false + eventTypeToQueue: + all: preview-id_ap_event_single_queue + quoteCreated: preview-id_ap_event_quote_created + transactionCreated: preview-id_ap_event_transaction_created + transactionStatusChanged: preview-id_ap_event_transaction_status_changed + transactionError: preview-id_ap_event_transaction_error + data-spring-jdbc-sqlite: + spring.datasource.driver-class-name: org.sqlite.JDBC + spring.datasource.initial-size: 1 + spring.datasource.max-active: 1 + spring.datasource.password: ${SQLITE_PASSWORD} + spring.datasource.url: jdbc:sqlite:anchor-proxy.db + spring.datasource.username: ${SQLITE_USERNAME} + spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect + spring.jpa.generate-ddl: true + spring.jpa.hibernate.ddl-auto: update + spring.jpa.hibernate.show_sql: false + spring.mvc.converters.preferred-json-mapper: gson + spring: + logging: + level: + root: INFO + org.springframework: INFO + org.springframework.web.filter: INFO + org.stellar: INFO + mvc: + async.request-timeout: 6000 + stellar-wks.toml: | + ACCOUNTS = ["GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY"] + VERSION = "0.1.0" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + SIGNING_KEY = "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" + TRANSFER_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep6" + TRANSFER_SERVER_SEP0024 = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep24" + WEB_AUTH_ENDPOINT = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/auth" + KYC_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep12" + DIRECT_PAYMENT_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep31" + ANCHOR_QUOTE_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep38" + + [[CURRENCIES]] + code = "USDC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + status = "test" + is_asset_anchored = true + anchor_asset_type = "fiat" + desc = "A test USDC issued by Circle." + + [DOCUMENTATION] + ORG_NAME = "Stellar Development Foundation" + ORG_URL = "https://www.stellar.org" + ORG_DESCRIPTION = "Stellar is an open network for storing and moving money." + ORG_LOGO = "https://assets-global.website-files.com/5deac75ecad2173c2ccccbc7/5dec8960504967fd31147f62_Stellar_lockup_black_RGB.svg" + ORG_SUPPORT_EMAIL = "jamie@stellar.org" +--- +# Source: sep/templates/service.yaml +# SEP Server +apiVersion: v1 +kind: Service +metadata: + name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id + helm.sh/chart: sep-0.3.90 + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 8080 # port number the service will listen on + targetPort: 8080 # port number pods listen on + selector: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id + + +# Stellar Observer +--- +# Source: sep/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id-observer + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer + helm.sh/chart: sep-0.3.90 + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 8083 # port number the service will listen on + targetPort: 8083 # port number pods listen on + selector: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer +--- +# Source: sep/templates/deployment.yaml +# SEP Server +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anchor-platform-sep-server-preview-id + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id + helm.sh/chart: sep-0.3.90 + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm +spec: + replicas: + selector: + matchLabels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id + template: + metadata: + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id + app.kubernetes.io/instance: release-name + annotations: + prometheus.io/path: "/actuator/prometheus" + prometheus.io/port: "8082" + prometheus.io/scrape: "true" + spec: + containers: + - name: sep + image: "docker-registry.services.stellar-ops.com/dev/anchor-platform-preview:preview-id" + args: ["--sep-server"] + imagePullPolicy: Always + startupProbe: + httpGet: + path: /health + port: 8080 + failureThreshold: 10 + periodSeconds: 15 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + failureThreshold: 2 + periodSeconds: 3 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 3 + volumeMounts: + - name: sep-config-volume + mountPath: /config + readOnly: true + - mountPath: assets + name: anchor-platform-sep-server-preview-id-assets-volume + ports: + - name: http + containerPort: 8080 + protocol: TCP + - name: metrics + containerPort: 8082 + protocol: TCP + env: + - name: STELLAR_ANCHOR_CONFIG + value: file:/config/anchor-config.yaml + envFrom: + - secretRef: + name: anchor-platform-secret-common-previews + volumes: + - name: sep-config-volume + configMap: + name: anchor-platform-sep-server-preview-id + - name: anchor-platform-sep-server-preview-id-assets-volume + configMap: + name: anchor-platform-sep-server-preview-id-assets + +# Stellar Observer--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anchor-platform-sep-server-preview-id-observer + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer + helm.sh/chart: sep-0.3.90 + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer + template: + metadata: + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer + app.kubernetes.io/instance: release-name + annotations: + prometheus.io/path: "/actuator/prometheus" + prometheus.io/port: "8082" + prometheus.io/scrape: "true" + spec: + containers: + - name: sep-observer + image: "docker-registry.services.stellar-ops.com/dev/anchor-platform-preview:preview-id" + args: ["--stellar-observer"] + imagePullPolicy: Always + startupProbe: + httpGet: + path: /health + port: 8083 + failureThreshold: 10 + periodSeconds: 15 + livenessProbe: + httpGet: + path: /health + port: 8083 + initialDelaySeconds: 60 + failureThreshold: 2 + periodSeconds: 15 + readinessProbe: + httpGet: + path: /health + port: 8083 + initialDelaySeconds: 60 + periodSeconds: 15 + volumeMounts: + - name: sep-config-volume + mountPath: /config + readOnly: true + - mountPath: assets + name: anchor-platform-sep-server-preview-id-assets-volume + ports: + - name: http + containerPort: 8083 + protocol: TCP + env: + - name: STELLAR_ANCHOR_CONFIG + value: file:/config/anchor-config.yaml + envFrom: + - secretRef: + name: anchor-platform-secret-common-previews + volumes: + - name: sep-config-volume + configMap: + name: anchor-platform-sep-server-preview-id + - name: anchor-platform-sep-server-preview-id-assets-volume + configMap: + name: anchor-platform-sep-server-preview-id-assets +--- +# Source: sep/templates/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id + annotations: + fluxcd.io/ignore: "true" + ingress.kubernetes.io/browser-xss-filter: "true" + ingress.kubernetes.io/content-type-nosniff: "true" + ingress.kubernetes.io/frame-deny: "true" + ingress.kubernetes.io/hsts-include-subdomains: "true" + ingress.kubernetes.io/hsts-max-age: "31536000" + ingress.kubernetes.io/ssl-redirect: "true" + kubernetes.io/ingress.class: "public" + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id + helm.sh/chart: sep-0.3.90 + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm +spec: + tls: + - hosts: + - anchor-sep-preview-id.previews.kube001.services.stellar-ops.com + secretName: star.previews.kube001.services.stellar-ops.com.tls + rules: + - host: anchor-sep-preview-id.previews.kube001.services.stellar-ops.com + http: + paths: + - path: / + pathType: "Prefix" + backend: + service: + name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id + port: + number: 8080 + +# Stellar Observer +--- +# Source: sep/templates/ingress.yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id-observer + annotations: + fluxcd.io/ignore: "true" + ingress.kubernetes.io/browser-xss-filter: "true" + ingress.kubernetes.io/content-type-nosniff: "true" + ingress.kubernetes.io/frame-deny: "true" + ingress.kubernetes.io/hsts-include-subdomains: "true" + ingress.kubernetes.io/hsts-max-age: "31536000" + ingress.kubernetes.io/ssl-redirect: "true" + kubernetes.io/ingress.class: "public" + labels: + app.kubernetes.io/name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id-observer + helm.sh/chart: sep-0.3.90 + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm +spec: + tls: + - hosts: + - anchor-sep-preview-id-observer.previews.kube001.services.stellar-ops.com + secretName: star.previews.kube001.services.stellar-ops.com.tls + rules: + - host: anchor-sep-preview-id-observer.previews.kube001.services.stellar-ops.com + http: + paths: + - path: / + pathType: "Prefix" + backend: + service: + name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id-observer + port: + number: 8083 +--- +# Source: sep/templates/ingress.yaml +# SEP Server \ No newline at end of file diff --git a/helm-charts/sep-service/templates/deployment.yaml b/helm-charts/sep-service/templates/deployment.yaml index 6c07347f8f..707a478fb0 100644 --- a/helm-charts/sep-service/templates/deployment.yaml +++ b/helm-charts/sep-service/templates/deployment.yaml @@ -71,6 +71,9 @@ spec: - name: http containerPort: {{ .Values.service.containerPort }} protocol: TCP + - name: metrics + containerPort: 8082 + protocol: TCP {{- if .Values.deployment.envVars }} {{- toYaml .Values.deployment.envVars | nindent 10 }} {{- end }} diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt index b5e5665c47..b48c7ae369 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt @@ -8,6 +8,7 @@ import java.time.format.DateTimeFormatter import java.util.* import java.util.concurrent.TimeUnit import okhttp3.OkHttpClient +import okhttp3.Request import org.junit.jupiter.api.* import org.junit.jupiter.api.Assertions.* import org.skyscreamer.jsonassert.JSONAssert @@ -37,6 +38,7 @@ class AnchorPlatformIntegrationTest { companion object { private const val SEP_SERVER_PORT = 8080 private const val REFERENCE_SERVER_PORT = 8081 + private const val OBSERVER_HEALTH_SERVER_PORT = 8083 private const val PLATFORM_TO_ANCHOR_SECRET = "myPlatformToAnchorSecret" private const val JWT_EXPIRATION_MILLISECONDS: Long = 10000 private const val FIAT_USD = "iso4217:USD" @@ -81,18 +83,15 @@ class AnchorPlatformIntegrationTest { @BeforeAll @JvmStatic fun setup() { - platformServerContext = - ServiceRunner.startSepServer( - SEP_SERVER_PORT, - "/", - mapOf( - "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", - "secret.sep10.jwt_secret" to "secret", - "secret.sep10.signing_seed" to - "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF" - ) + val envMap = + mapOf( + "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", + "secret.sep10.jwt_secret" to "secret", + "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF" ) - ServiceRunner.startStellarObserver() + + platformServerContext = ServiceRunner.startSepServer(SEP_SERVER_PORT, "/", envMap) + ServiceRunner.startStellarObserver(OBSERVER_HEALTH_SERVER_PORT, "/", envMap) AnchorReferenceServer.start(REFERENCE_SERVER_PORT, "/") } @@ -348,4 +347,30 @@ class AnchorPlatformIntegrationTest { val sep38Config = platformServerContext.getBean(Sep38Config::class.java) assertEquals(true, sep38Config.isEnabled) } + + @Test + fun testStellarObserverHealth() { + val httpRequest = + Request.Builder() + .url("http://localhost:$OBSERVER_HEALTH_SERVER_PORT/health") + .header("Content-Type", "application/json") + .get() + .build() + val response = httpClient.newCall(httpRequest).execute() + assertEquals(200, response.code) + + val responseBody = gson.fromJson(response.body!!.string(), HashMap::class.java) + assertEquals(5, responseBody.size) + assertNotNull(responseBody["started_at"]) + assertNotNull(responseBody["elapsed_time_ms"]) + assertNotNull(responseBody["number_of_checks"]) + assertEquals(2.0, responseBody["number_of_checks"]) + assertNotNull(responseBody["version"]) + assertNotNull(responseBody["checks"]) + + val checks = responseBody["checks"] as Map<*, *> + assertEquals(2, checks.size) + assertNotNull(checks["config"]) + assertNotNull(checks["stellar_payment_observer"]) + } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt index b6b18f83ee..4325d57456 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt @@ -44,7 +44,7 @@ fun main(args: Array) { if (cmd.hasOption("sep-server") || cmd.hasOption("all")) { ServiceRunner.startSepServer( ServiceRunner.DEFAULT_SEP_SERVER_PORT, - ServiceRunner.DEFAULT_CONTEXTPATH, + ServiceRunner.DEFAULT_CONTEXT_PATH, null ) } diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java index 35506c0ce5..f17d70cc32 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java @@ -4,7 +4,6 @@ import java.util.Map; import org.springframework.boot.SpringApplication; -import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -22,18 +21,20 @@ @EnableConfigurationProperties public class StellarObservingService implements WebMvcConfigurer { - public static ConfigurableApplicationContext start(Map environment) { + public static ConfigurableApplicationContext start( + int port, String contextPath, Map environment) { SpringApplicationBuilder builder = new SpringApplicationBuilder(StellarObservingService.class) .bannerMode(OFF) - .web(WebApplicationType.NONE) .properties( // TODO: update when the ticket // https://github.com/stellar/java-stellar-anchor-sdk/issues/297 is completed. "spring.mvc.converters.preferred-json-mapper=gson", // this allows a developer to use a .env file for local development "spring.config.import=optional:classpath:example.env[.properties]", - "spring.profiles.active=stellar-observer"); + "spring.profiles.active=stellar-observer", + String.format("server.port=%d", port), + String.format("server.contextPath=%s", contextPath)); if (environment != null) { builder.properties(environment); @@ -45,7 +46,7 @@ public static ConfigurableApplicationContext start(Map environme return springApplication.run(); } - public static ConfigurableApplicationContext start() { - return start(null); + public static ConfigurableApplicationContext start(int port, String contextPath) { + return start(port, contextPath, null); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/callback/PlatformIntegrationHelper.java b/platform/src/main/java/org/stellar/anchor/platform/callback/PlatformIntegrationHelper.java index 6e5a32dd2c..d70a7eb442 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/callback/PlatformIntegrationHelper.java +++ b/platform/src/main/java/org/stellar/anchor/platform/callback/PlatformIntegrationHelper.java @@ -50,34 +50,31 @@ public static AnchorException httpError(String responseContent, int responseCode "Error returned from the Anchor Backend.\nresponseCode={}\nContent={}", responseCode, responseContent); - ErrorResponse errorResponse; + ErrorResponse errorResponse = null; try { errorResponse = gson.fromJson(responseContent, ErrorResponse.class); - } catch (Exception e) { // cannot read body from response - return new ServerErrorException("internal server error", e); - } - - // Handle 422 - String errorMessage; - if (responseCode == HttpStatus.UNPROCESSABLE_ENTITY.value()) { - errorMessage = - (errorResponse != null) - ? errorResponse.getError() - : HttpStatus.BAD_REQUEST.getReasonPhrase(); - return new BadRequestException(errorMessage); + } catch (Exception e) { + // cannot read body from response + Log.warn("Failed to parse responseContent to an ErrorResponse object."); } - errorMessage = + String errorMessage = (errorResponse != null) ? errorResponse.getError() - : HttpStatus.valueOf(responseCode).getReasonPhrase(); - if (responseCode == HttpStatus.BAD_REQUEST.value()) { - return new BadRequestException(errorMessage); - } else if (responseCode == HttpStatus.NOT_FOUND.value()) { - return new NotFoundException(errorMessage); + : (responseCode == 422) + ? HttpStatus.BAD_REQUEST.getReasonPhrase() + : HttpStatus.valueOf(responseCode).getReasonPhrase(); + + switch (HttpStatus.valueOf(responseCode)) { + case UNPROCESSABLE_ENTITY: // 422 + case BAD_REQUEST: // 400 + return new BadRequestException(errorMessage); + case NOT_FOUND: // 404 + return new NotFoundException(errorMessage); + default: + Log.errorF("Unsupported status code {}.", responseCode); + return new ServerErrorException("internal server error"); } - Log.error(errorMessage); - return new ServerErrorException("internal server error"); } @Data diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java index 1c8741ec0e..d6102d5677 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java @@ -5,11 +5,13 @@ import com.google.gson.Gson; import java.util.Map; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestClientException; import org.stellar.anchor.api.exception.BadRequestException; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.api.exception.ServerErrorException; import org.stellar.anchor.api.exception.UnprocessableEntityException; import org.stellar.anchor.api.sep.SepExceptionResponse; @@ -18,6 +20,7 @@ @RestController @RequestMapping("/circle-observer") +@Profile("default") public class CirclePaymentObserverController { private final Gson gson = new Gson(); private final CirclePaymentObserverService circlePaymentObserverService; @@ -34,12 +37,12 @@ public CirclePaymentObserverController( consumes = {MediaType.APPLICATION_JSON_VALUE}) public void handleCircleNotificationJson( @RequestBody(required = false) Map requestBody) - throws UnprocessableEntityException, BadRequestException, ServerErrorException { + throws EventPublishException, BadRequestException, ServerErrorException { try { CircleNotification circleNotification = gson.fromJson(gson.toJson(requestBody), CircleNotification.class); circlePaymentObserverService.handleCircleNotification(circleNotification); - } catch (Exception ex) { + } catch (UnprocessableEntityException ex) { throw new BadRequestException("Error parsing the request."); } } @@ -50,7 +53,8 @@ public void handleCircleNotificationJson( method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.HEAD}, consumes = {MediaType.TEXT_PLAIN_VALUE}) public void handleCircleNotificationTextPlain(@RequestBody(required = false) String jsonBodyStr) - throws UnprocessableEntityException, BadRequestException, ServerErrorException { + throws UnprocessableEntityException, BadRequestException, ServerErrorException, + EventPublishException { CircleNotification circleNotification = gson.fromJson(jsonBodyStr, CircleNotification.class); circlePaymentObserverService.handleCircleNotification(circleNotification); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java index 00ae1f2709..7d8b26ee5a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/PlatformController.java @@ -1,5 +1,6 @@ package org.stellar.anchor.platform.controller; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; @@ -11,6 +12,7 @@ import org.stellar.anchor.platform.service.TransactionService; @RestController +@Profile("default") public class PlatformController { private final TransactionService transactionService; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java index 9ab1eaf668..105452734e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep10Controller.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.URISyntaxException; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; @@ -24,6 +25,7 @@ @RestController @CrossOrigin(origins = "*") @ConditionalOnAllSepsEnabled(seps = {"sep10"}) +@Profile("default") public class Sep10Controller { private final Sep10Service sep10Service; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep12Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep12Controller.java index 23aa9b741b..13f5926777 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep12Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep12Controller.java @@ -8,6 +8,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import lombok.SneakyThrows; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; @@ -21,6 +22,7 @@ @CrossOrigin(origins = "*") @RequestMapping("/sep12") @ConditionalOnAllSepsEnabled(seps = {"sep12"}) +@Profile("default") public class Sep12Controller { private final Sep12Service sep12Service; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep1Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep1Controller.java index 3ecdf5f645..467e8899b1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep1Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep1Controller.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.controller; import java.io.IOException; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -14,6 +15,7 @@ @RestController @CrossOrigin(origins = "*") @ConditionalOnAllSepsEnabled(seps = {"sep1"}) +@Profile("default") public class Sep1Controller { private final Sep1Config sep1Config; private final Sep1Service sep1Service; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep24Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep24Controller.java index 70cdf6869f..f8e9b7867f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep24Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep24Controller.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -26,6 +27,7 @@ @CrossOrigin(origins = "*") @RequestMapping("/sep24") @ConditionalOnAllSepsEnabled(seps = {"sep24"}) +@Profile("default") public class Sep24Controller { private final Sep24Service sep24Service; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java index 8dd4e62789..8b9912e5a3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep31Controller.java @@ -5,6 +5,7 @@ import static org.stellar.anchor.util.Log.errorEx; import javax.servlet.http.HttpServletRequest; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; @@ -21,6 +22,7 @@ @CrossOrigin(origins = "*") @RequestMapping("sep31") @ConditionalOnAllSepsEnabled(seps = {"sep31"}) +@Profile("default") public class Sep31Controller { private final Sep31Service sep31Service; diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep38Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep38Controller.java index 7fbf8c14f5..83d0328004 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/Sep38Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/Sep38Controller.java @@ -8,6 +8,7 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import lombok.SneakyThrows; +import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; @@ -23,6 +24,7 @@ @CrossOrigin(origins = "*") @RequestMapping("/sep38") @ConditionalOnAllSepsEnabled(seps = {"sep38"}) +@Profile("default") public class Sep38Controller { private final Sep38Service sep38Service; private static final Gson gson = GsonUtils.builder().create(); diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java index e0598746b0..4809f2e009 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java @@ -1,22 +1,21 @@ package org.stellar.anchor.platform.event; -import java.util.Properties; +import static org.apache.kafka.clients.producer.ProducerConfig.*; +import java.util.Properties; import lombok.NoArgsConstructor; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; -import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.header.internals.RecordHeader; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.kafka.support.serializer.JsonSerializer; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.event.EventPublisher; import org.stellar.anchor.event.models.AnchorEvent; import org.stellar.anchor.platform.config.KafkaConfig; import org.stellar.anchor.util.Log; -import static org.apache.kafka.clients.producer.ProducerConfig.*; - @NoArgsConstructor public class KafkaEventPublisher implements EventPublisher { Producer producer; @@ -40,11 +39,16 @@ protected void createPublisher(Properties props) { } @Override - public void publish(String queue, AnchorEvent event) { + public void publish(String queue, AnchorEvent event) throws EventPublishException { try { ProducerRecord record = new ProducerRecord<>(queue, event); record.headers().add(new RecordHeader("type", event.getType().getBytes())); - producer.send(record); + // If the queue is offline, throw an exception + try { + producer.send(record).get(); + } catch (Exception ex) { + throw new EventPublishException("Failed to publish event to Kafka.", ex); + } } catch (Exception ex) { Log.errorEx(ex); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java index aecce3187c..35df084816 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java @@ -1,15 +1,14 @@ package org.stellar.anchor.platform.event; +import static org.apache.kafka.clients.producer.ProducerConfig.*; + +import java.util.Properties; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.kafka.support.serializer.JsonSerializer; import org.stellar.anchor.platform.config.MskConfig; import org.stellar.anchor.util.Log; -import java.util.Properties; - -import static org.apache.kafka.clients.producer.ProducerConfig.*; - public class MskEventPublisher extends KafkaEventPublisher { public MskEventPublisher(MskConfig mskConfig) { Log.debugF("MskConfig: {}", mskConfig); diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java index bd0acfc3fa..f138d70f4d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java @@ -4,7 +4,9 @@ import com.amazonaws.services.sqs.AmazonSQSAsyncClientBuilder; import com.amazonaws.services.sqs.model.MessageAttributeValue; import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.sqs.model.SendMessageResult; import com.google.gson.Gson; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.event.EventPublisher; import org.stellar.anchor.event.models.AnchorEvent; import org.stellar.anchor.platform.config.SqsConfig; @@ -19,7 +21,7 @@ public SqsEventPublisher(SqsConfig sqsConfig) { } @Override - public void publish(String queue, AnchorEvent event) { + public void publish(String queue, AnchorEvent event) throws EventPublishException { try { // TODO implement batching // TODO retry logic? @@ -39,8 +41,19 @@ public void publish(String queue, AnchorEvent event) { new MessageAttributeValue() .withDataType("String") .withStringValue(event.getClass().getSimpleName())); - sqsClient.sendMessage(sendMessageRequest); + SendMessageResult sendMessageResult = sqsClient.sendMessage(sendMessageRequest); + // If the queue is offline, throw an exception + int statusCode = sendMessageResult.getSdkHttpMetadata().getHttpStatusCode(); + if (statusCode < 200 || statusCode > 299) { + Log.error("failed to send message to SQS"); + throw new EventPublishException( + String.format( + "Failed to publish event to AWS SQS. [StatusCode: %d] [Metadata: %s]", + statusCode, sendMessageResult.getSdkHttpMetadata().toString())); + } + } catch (EventPublishException ex) { + throw ex; } catch (Exception ex) { Log.errorEx(ex); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java index 9488485099..3ac8a45f84 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java @@ -1,9 +1,10 @@ package org.stellar.anchor.platform.payment.observer; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; public interface PaymentListener { - void onReceived(ObservedPayment payment); + void onReceived(ObservedPayment payment) throws EventPublishException; void onSent(ObservedPayment payment); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java index 0f38b9fade..8b064eea3f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java @@ -12,6 +12,7 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; +import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.exception.ServerErrorException; @@ -64,7 +65,8 @@ public CirclePaymentObserverService( } public void handleCircleNotification(CircleNotification circleNotification) - throws UnprocessableEntityException, BadRequestException, ServerErrorException { + throws UnprocessableEntityException, BadRequestException, ServerErrorException, + EventPublishException { String type = Objects.toString(circleNotification.getType(), ""); switch (type) { @@ -142,7 +144,8 @@ public void handleSubscriptionConfirmationNotification(CircleNotification circle * @throws ServerErrorException when there's an error trying to fetch the Stellar network. */ public void handleTransferNotification(CircleNotification circleNotification) - throws BadRequestException, UnprocessableEntityException, ServerErrorException { + throws BadRequestException, UnprocessableEntityException, ServerErrorException, + EventPublishException { if (circleNotification.getMessage() == null) { throw new BadRequestException("Notification body of type Notification is missing a message."); } @@ -207,8 +210,9 @@ public void handleTransferNotification(CircleNotification circleNotification) } if (isWalletTracked(destination)) { - final ObservedPayment finalObservedPayment = observedPayment; - observers.forEach(observer -> observer.onReceived(finalObservedPayment)); + for (PaymentListener listener : observers) { + listener.onReceived(observedPayment); + } } else { final ObservedPayment finalObservedPayment1 = observedPayment; observers.forEach(observer -> observer.onSent(finalObservedPayment1)); diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java index 17f2ab51ed..456caf8387 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java @@ -15,6 +15,7 @@ import lombok.Builder; import lombok.Data; import org.jetbrains.annotations.NotNull; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.exception.ValueValidationException; import org.stellar.anchor.api.platform.HealthCheckResult; @@ -22,6 +23,7 @@ import org.stellar.anchor.healthcheck.HealthCheckable; import org.stellar.anchor.platform.payment.observer.PaymentListener; import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; +import org.stellar.anchor.util.ExponentialBackoffTimer; import org.stellar.anchor.util.Log; import org.stellar.sdk.Server; import org.stellar.sdk.requests.EventListener; @@ -47,6 +49,8 @@ public class StellarPaymentObserver implements HealthCheckable { final PaymentObservingAccountsManager paymentObservingAccountsManager; SSEStream stream; + private final ExponentialBackoffTimer exponentialBackoffTimer = new ExponentialBackoffTimer(); + StellarPaymentObserver( String horizonServer, Set observers, @@ -66,6 +70,22 @@ public void start() { /** Graceful shutdown. */ public void shutdown() { this.stream.close(); + this.stream = null; + } + + private void restart() { + Log.info("Restarting the Stellar observer."); + if (this.stream != null) { + this.shutdown(); + } + + try { + exponentialBackoffTimer.sleep(); + exponentialBackoffTimer.increase(); + this.start(); + } catch (InterruptedException ex) { + Log.errorEx(ex); + } } /** @@ -100,7 +120,7 @@ String fetchStreamingCursor() { return pageOpResponse.getRecords().get(0).getPagingToken(); } - public SSEStream watch() { + SSEStream watch() { String latestCursor = fetchStreamingCursor(); PaymentsRequestBuilder paymentsRequest = server @@ -141,18 +161,34 @@ public void onEvent(OperationResponse operationResponse) { if (observedPayment != null) { try { if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getTo())) { - final ObservedPayment finalObservedPayment = observedPayment; - observers.forEach(observer -> observer.onReceived(finalObservedPayment)); + for (PaymentListener listener : observers) { + listener.onReceived(observedPayment); + } } + if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getFrom()) && !observedPayment.getTo().equals(observedPayment.getFrom())) { final ObservedPayment finalObservedPayment = observedPayment; observers.forEach(observer -> observer.onSent(finalObservedPayment)); } + + } catch (EventPublishException ex) { + // restart the observer from where it stopped, in case the queue fails to + // publish the message. + Log.errorEx("Failed to send event to observer.", ex); + restart(); + return; } catch (Throwable t) { - Log.errorEx(t); + Log.errorEx("Something went wrong in the streamer", t); + if (!Thread.interrupted()) { + restart(); + } + return; } + + exponentialBackoffTimer.reset(); } + paymentStreamerCursorStore.save(operationResponse.getPagingToken()); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index 2bfc23e268..b8102fbf9f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -15,6 +15,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.shared.Amount; import org.stellar.anchor.api.shared.StellarPayment; @@ -43,7 +44,7 @@ public class PaymentOperationToEventListener implements PaymentListener { } @Override - public void onReceived(ObservedPayment payment) { + public void onReceived(ObservedPayment payment) throws EventPublishException { // Check if payment is connected to a transaction if (Objects.toString(payment.getTransactionHash(), "").isEmpty() || Objects.toString(payment.getTransactionMemo(), "").isEmpty()) { @@ -170,10 +171,10 @@ public void onReceived(ObservedPayment payment) { @Override public void onSent(ObservedPayment payment) { - // noop + Log.debug("NOOP PaymentOperationToEventListener#onSent was called."); } - private void sendToQueue(TransactionEvent event) { + private void sendToQueue(TransactionEvent event) throws EventPublishException { eventService.publish(event); Log.infoF("Sent to event queue {}", GsonUtils.getInstance().toJson(event)); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java index 897cd9b4cb..8a7ee8ac67 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/TransactionService.java @@ -43,6 +43,7 @@ public class TransactionService { PENDING_RECEIVER.getName(), PENDING_EXTERNAL.getName(), COMPLETED.getName(), + REFUNDED.getName(), EXPIRED.getName(), ERROR.getName()); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt index 66dc71bc94..437d4ee1af 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt @@ -298,6 +298,7 @@ class TransactionServiceTest { "PENDING_RECEIVER", "PENDING_EXTERNAL", "COMPLETED", + "REFUNDED", "EXPIRED", "ERROR"] ) diff --git a/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java b/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java index 2be951d1cb..4c743a8405 100644 --- a/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java +++ b/service-runner/src/main/java/org/stellar/anchor/platform/ServiceRunner.java @@ -8,7 +8,8 @@ public class ServiceRunner { public static final int DEFAULT_SEP_SERVER_PORT = 8080; public static final int DEFAULT_ANCHOR_REFERENCE_SERVER_PORT = 8081; - public static final String DEFAULT_CONTEXTPATH = "/"; + public static final int DEFAULT_STELLAR_OBSERVER_SERVER_PORT = 8083; + public static final String DEFAULT_CONTEXT_PATH = "/"; public static void main(String[] args) { Options options = new Options(); @@ -25,12 +26,12 @@ public static void main(String[] args) { CommandLine cmd = parser.parse(options, args); boolean anyServerStarted = false; if (cmd.hasOption("sep-server") || cmd.hasOption("all")) { - startSepServer(DEFAULT_SEP_SERVER_PORT, DEFAULT_CONTEXTPATH, null); + startSepServer(DEFAULT_SEP_SERVER_PORT, DEFAULT_CONTEXT_PATH, null); anyServerStarted = true; } if (cmd.hasOption("stellar-observer") || cmd.hasOption("all")) { - startStellarObserver(); + startStellarObserver(DEFAULT_STELLAR_OBSERVER_SERVER_PORT, DEFAULT_CONTEXT_PATH, null); anyServerStarted = true; } @@ -52,8 +53,9 @@ static ConfigurableApplicationContext startSepServer( return AnchorPlatformServer.start(port, contextPath, env, true); } - static ConfigurableApplicationContext startStellarObserver() { - return StellarObservingService.start(); + static ConfigurableApplicationContext startStellarObserver( + int port, String contextPath, Map env) { + return StellarObservingService.start(port, contextPath, env); } static void startAnchorReferenceServer() { @@ -62,9 +64,9 @@ static void startAnchorReferenceServer() { if (strPort != null) { port = Integer.parseInt(strPort); } - String contextPath = System.getProperty("ANCHOR_REFERENCE_CONTEXTPATH"); + String contextPath = System.getProperty("ANCHOR_REFERENCE_CONTEXT_PATH"); if (contextPath == null) { - contextPath = DEFAULT_CONTEXTPATH; + contextPath = DEFAULT_CONTEXT_PATH; } AnchorReferenceServer.start(port, contextPath); } From 9dc59927517043de6e051099f0ba647db26ed108 Mon Sep 17 00:00:00 2001 From: Marcelo Salloum dos Santos Date: Fri, 7 Oct 2022 18:08:34 -0300 Subject: [PATCH 62/65] Feature: separate observer deployment (replicate #613) (#622) ### What Add an option to make the Stellar observer into a separate deployment service. ### Why Because it's recommended to use it as a separate service. Replicates #613 in the development environment. --- helm-charts/sep-service/Chart.yaml | 2 +- helm-charts/sep-service/example_values.yaml | 136 +++- helm-charts/sep-service/new-yaml-all | 648 ------------------ .../sep-service/templates/deployment.yaml | 111 ++- .../sep-service/templates/ingress.yaml | 61 +- .../sep-service/templates/service.yaml | 37 +- .../anchor/platform/config/KafkaConfig.java | 2 +- .../platform/config/PropertyEventConfig.java | 2 +- .../platform/event/KafkaEventPublisher.java | 2 +- .../platform/event/MskEventPublisher.java | 2 +- .../config/anchor-config-default-values.yaml | 2 +- .../config/anchor-config-schema-v1.yaml | 2 +- .../main/resources/example.anchor-config.yaml | 12 +- .../anchor/platform/config/EventConfigTest.kt | 2 +- 14 files changed, 324 insertions(+), 697 deletions(-) delete mode 100644 helm-charts/sep-service/new-yaml-all diff --git a/helm-charts/sep-service/Chart.yaml b/helm-charts/sep-service/Chart.yaml index 3ceb131a19..95cab8f563 100644 --- a/helm-charts/sep-service/Chart.yaml +++ b/helm-charts/sep-service/Chart.yaml @@ -7,4 +7,4 @@ maintainers: sources: - https://github.com/stellar/java-stellar-anchor-sdk name: sep -version: 0.4.0 +version: 0.4.1 diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index 6e6bb7d695..b0f9b747c2 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -2,7 +2,6 @@ fullName: anchor-platform service: containerPort: 8080 servicePort: 8080 - replicas: 1 type: NodePort name: sep image: @@ -11,21 +10,19 @@ image: tag: latest pullPolicy: Always deployment: - args: - - '"--sep-server"' - - '"--stellar-observer"' + replicas: 1 startupProbePeriodSeconds: 10 startupProbeFailureThreshold: 30 serviceAccountName: default volumeMounts: - secrets: + secrets: # TODO: add your secrets here: - name: apsigningseed mountPath: signingseed - configMaps: + configMaps: # TODO: add your config maps here: - name: assets mountPath: assets hosts: - - host: "your_anchor_domain.com" + - host: "" path: / pathType: Prefix backend: @@ -35,18 +32,20 @@ deployment: prometheus.io/path: /actuator/prometheus prometheus.io/port: "8082" prometheus.io/scrape: "true" - env: - - name: STELLAR_ANCHOR_CONFIG - value: file:/config/anchor-config.yaml - envFrom: - - secretRef: - name: anchor-platform-secrets - - configMapRef: - name: {{ .Values.fullName }}-config-env-vars + # Everything under envVars will be injected/propagated into the deployments. + envVars: + env: + - name: STELLAR_ANCHOR_CONFIG + value: file:/config/anchor-config.yaml + envFrom: + - secretRef: + name: anchor-platform-secrets + - configMapRef: + name: "{{ .Values.fullName }}-config-env-vars" # TODO: remember to rename this ingress: labels: - app: appy - test: label_test + app: + test: annotations: kubernetes.io/ingress.class: "public" cert-manager.io/cluster-issuer: "default" @@ -57,43 +56,110 @@ ingress: ingress.kubernetes.io/hsts-max-age: "31536000" ingress.kubernetes.io/hsts-include-subdomains: "true" tls: - host: "your_anchor_domain.com" - secretName: anchor-platform-sep-server-cert + host: "" + secretName: rules: hosts: - - host: "your_anchor_domain.com" + - host: "" path: / pathType: Prefix backend: servicePort: "{{ .Values.service.containerPort }}" serviceName: "{{ .Values.fullName }}-svc-{{ .Values.service.name }}" - +# +# If you want to have a dedicated stellar-observer service, you need to +# uncomment the `stellarObserver` section below. +# +# Attention! If you use the stellar observer as a separate service – by setting +# `stellarObserver.enabled` flag to `true` – you must use a shared database that +# will be accessible by both deployments (sep-server and stellar-observer). +# In-memory databases won't work. +# stellarObserver: +# # This will enable the stellar-observer service as a separate deployment: +# enabled: true +# # The stellarObserver.deployment section is optional, the templates have +# # default values. +# deployment: +# port: 8083 +# probePath: "/health" +# probePeriodSeconds: 15 +# initialDelaySeconds: 60 +# startupProbeFailureThreshold: 10 +# livenessProbeFailureThreshold: 2 +# ingress: +# tls: +# host: "" # Replace this with a valid host name +# rules: +# hosts: +# - host: "" # Replace this with a valid host name +# path: / +# pathType: Prefix +# +# The following configurations (`app_config`, `toml_config`, and `assets_config`) +# will be used to configure the app(s) (both sep-server and stellar-observer). app_config: - host_url: https://your_anchor_domain.com + version: 1 + # + # Database (h2): + data.type: h2 + # + # Database (PostgreSQL): + # data.type: postgres + # data.url: jdbc:postgresql://:/ + # data.username: POSTGRES_USERNAME # TODO: use secrets + # data.password: POSTGRES_PASSWORD # TODO: use secrets + # data.initial_connection_pool_size: 3 + # data.max_active_connections: 10 + # data.flyway_enabled: true + # data.ddl_auto: update + # data.flyway_location: /db/migration + # + host_url: https:// stellar_network.network: TESTNET - callback_api.base_url: https://your_anchor_domain.com + stellar_network.network_passphrase: Test SDF Network ; September 2015 + stellar_network.horizon_url: https://horizon-testnet.stellar.org + callback_api.base_url: https:// callback_api.auth.type: JWT_TOKEN + platform_api.auth.type: JWT_TOKEN + payment_observer.enabled: false + languages: en sep1.enabled: true + # `sep1.toml.value` is being set by the helm chart from `toml_config`. sep10.enabled: true - sep10.home_domain: your_anchor_domain.com + sep10.home_domain: sep12.enabled: true + sep24.enabled: false sep31.enabled: true + sep31.payment_type: STRICT_SEND sep31.deposit_info_generator_type: api sep38.enabled: true - data.type: h2 + metrics.enabled: true + metrics.extras_enabled: true + metrics.run_interval: 30 + # Events (kafka): + events.enabled: true + events.event_type_to_queue.quote_created: preview-id_ap_event_quote_created + events.event_type_to_queue.transaction_created: preview-id_ap_event_transaction_created + events.event_type_to_queue.transaction_status_changed: preview-id_ap_event_transaction_status_changed + events.event_type_to_queue.transaction_error: preview-id_ap_event_transaction_error + events.publisher.type: kafka + events.publisher.kafka.bootstrap_server: : + events.publisher.kafka.client_id: + events.publisher.kafka.retries: 1 + events.publisher.kafka.linger_ms: 1000 + events.publisher.kafka.batch_size: 10 assets.type: json + # `assets.value` is being set by the helm chart from `assets_config`. toml_config: | ACCOUNTS = [] VERSION = "0.1.0" NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" SIGNING_KEY = "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" - TRANSFER_SERVER = "https://your_anchor_domain.com/sep6" - TRANSFER_SERVER_SEP0024 = "https://your_anchor_domain.com/sep24" - WEB_AUTH_ENDPOINT = "https://your_anchor_domain.com/auth" - KYC_SERVER = "https://your_anchor_domain.com/sep12" - DIRECT_PAYMENT_SERVER = "https://your_anchor_domain.com/sep31" - ANCHOR_QUOTE_SERVER = "https://your_anchor_domain.com//sep38" + WEB_AUTH_ENDPOINT = "https:///auth" + KYC_SERVER = "https:///sep12" + DIRECT_PAYMENT_SERVER = "https:///sep31" + ANCHOR_QUOTE_SERVER = "https:////sep38" [[CURRENCIES]] code = "USDC" @@ -104,10 +170,10 @@ toml_config: | desc = "A test USDC issued by Circle." [DOCUMENTATION] - ORG_NAME = "Your Anchor Domain" - ORG_URL = "https://your_anchor_domain.org" - ORG_DESCRIPTION = "Your anchor domain" - ORG_SUPPORT_EMAIL="support@your_anchor_domain.org" + ORG_NAME = "" + ORG_URL = "https://" + ORG_DESCRIPTION = "" + ORG_SUPPORT_EMAIL="" assets_config: | { diff --git a/helm-charts/sep-service/new-yaml-all b/helm-charts/sep-service/new-yaml-all deleted file mode 100644 index 9a88f541fc..0000000000 --- a/helm-charts/sep-service/new-yaml-all +++ /dev/null @@ -1,648 +0,0 @@ ---- -# Source: sep/templates/configmap.yaml -kind: ConfigMap -apiVersion: v1 -metadata: - name: anchor-platform-sep-server-preview-id-assets -data: - assets.json: | - { - "assets": [ - { - "code": "USDC", - "deposit": { - "enabled": true, - "fee_minimum": 0, - "fee_percent": 0, - "max_amount": 1000000, - "min_amount": 0 - }, - "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "schema": "stellar", - "send": { - "fee_fixed": 0, - "fee_percent": 0, - "max_amount": 1000000, - "min_amount": 0 - }, - "sep24_enabled": true, - "sep31": { - "fields": { - "transaction": { - "receiver_account_number": { - "description": "bank account number of the destination" - }, - "receiver_routing_number": { - "description": "routing number of the destination bank account" - }, - "type": { - "choices": [ - "SEPA", - "SWIFT" - ], - "description": "type of deposit to make" - } - } - }, - "quotes_required": false, - "quotes_supported": true, - "sep12": { - "receiver": { - "types": { - "sep31-foreign-receiver": { - "description": "non-U.S. citizens receiving USD" - }, - "sep31-receiver": { - "description": "U.S. citizens receiving USD" - } - } - }, - "sender": { - "types": { - "sep31-foreign-sender": { - "description": "non-U.S. citizens sending payments of less than $10,000 in value" - }, - "sep31-large-sender": { - "description": "U.S. citizens that do not have sending limits" - }, - "sep31-sender": { - "description": "U.S. citizens limited to sending payments of less than $10,000 in value" - } - } - } - } - }, - "sep31_enabled": true, - "sep38": { - "exchangeable_assets": [ - "iso4217:USD" - ] - }, - "sep38_enabled": true, - "significant_decimals": 2, - "withdraw": { - "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, - "max_amount": 1000000, - "min_amount": 0 - } - }, - { - "code": "JPYC", - "deposit": { - "enabled": true, - "fee_minimum": 0, - "fee_percent": 0, - "max_amount": 1000000, - "min_amount": 0 - }, - "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "schema": "stellar", - "send": { - "fee_fixed": 0, - "fee_percent": 0, - "max_amount": 1000000, - "min_amount": 0 - }, - "sep24_enabled": false, - "sep31": { - "fields": { - "transaction": { - "receiver_account_number": { - "description": "bank account number of the destination" - }, - "receiver_routing_number": { - "description": "routing number of the destination bank account" - }, - "type": { - "choices": [ - "SEPA", - "SWIFT" - ], - "description": "type of deposit to make" - } - } - }, - "quotes_required": false, - "quotes_supported": true, - "sep12": { - "receiver": { - "types": { - "sep31-foreign-receiver": { - "description": "non-U.S. citizens receiving JPY" - }, - "sep31-receiver": { - "description": "U.S. citizens receiving JPY" - } - } - }, - "sender": { - "types": { - "sep31-foreign-sender": { - "description": "non-U.S. citizens sending payments of less than $10,000 in value" - }, - "sep31-large-sender": { - "description": "U.S. citizens that do not have sending limits" - }, - "sep31-sender": { - "description": "U.S. citizens limited to sending payments of less than $10,000 in value" - } - } - } - } - }, - "sep31_enabled": true, - "sep38": { - "exchangeable_assets": [ - "iso4217:USD" - ] - }, - "sep38_enabled": true, - "significant_decimals": 4, - "withdraw": { - "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, - "max_amount": 1000000, - "min_amount": 0 - } - }, - { - "code": "USD", - "deposit": { - "enabled": true, - "fee_minimum": 0, - "fee_percent": 0, - "max_amount": 10000, - "min_amount": 1 - }, - "schema": "iso4217", - "send": { - "fee_fixed": 0, - "fee_percent": 0, - "max_amount": 10000, - "min_amount": 1 - }, - "sep24_enabled": false, - "sep31_enabled": false, - "sep38": { - "buy_delivery_methods": [ - { - "description": "Have USD sent directly to your bank account.", - "name": "WIRE" - } - ], - "country_codes": [ - "USA" - ], - "decimals": 4, - "exchangeable_assets": [ - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - ], - "sell_delivery_methods": [ - { - "description": "Send USD directly to the Anchor's bank account.", - "name": "WIRE" - } - ] - }, - "sep38_enabled": true, - "withdraw": { - "enabled": false, - "fee_fixed": 0, - "fee_percent": 0, - "max_amount": 10000, - "min_amount": 1 - } - } - ] - } ---- -# Source: sep/templates/configmap.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: anchor-platform-sep-server-preview-id -data: - # TODO: remove all secrets (ex: move to env vars, k8s secrets, vault, etc...) - anchor-config.yaml: | - stellar: - anchor: - config: in-memory - app-config: - type: config-spring-property # Activate [config-spring-property] module - settings: app-config # The location of the configuration data - data-access: - type: data-spring-jdbc # Activate [config-spring-jdbc] module. - settings: data-spring-jdbc-sqlite # The location of the configuration data in this file. - logging: - type: logging-logback - settings: logging-logback-settings - app-config: - app: - stellarNetwork: TESTNET - stellarNetworkPassphrase: Test SDF Network ; September 2015 - horizonUrl: https://horizon-testnet.stellar.org - hostUrl: https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com - languages: en - assets: file:/assets/assets.json - jwtSecretKey: ${JWT_SECRET} - integration-auth: - authType: JWT_TOKEN - platformToAnchorSecret: ${PLATFORM_TO_ANCHOR_SECRET} - anchorToPlatformSecret: ${ANCHOR_TO_PLATFORM_SECRET} - expirationMilliseconds: 30000 - # The anchor callback configuration - anchor-callback: - # The CallbackAPI endpoint - endpoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com - # sep-1 - sep1: - enabled: true - stellarFile: file:/config/stellar-wks.toml - # sep-10 - sep10: - enabled: true - homeDomain: anchor-sep-preview-id.previews.kube001.services.stellar-ops.com - clientAttributionRequired: false - clientAttributionAllowList: lobstr.co,preview.lobstr.co - # clientAttributionDenyList: # use this if we want to black list. - authTimeout: 900 - jwtTimeout: 86400 - signingSeed: ${SEP10_SIGNING_SEED} - requireKnownOmnibusAccount: false - # sep-12 - sep12: - enabled: true - customerIntegrationEndpoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com - # sep-24 - sep24: - enabled: false - interactiveJwtExpiration: 3600 - interactiveUrl: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com - # sep-31 - sep31: - enabled: true - feeIntegrationEndPoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com - uniqueAddressIntegrationEndPoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com - depositInfoGeneratorType: api - paymentType: STRICT_SEND - # sep-38 - sep38: - enabled: true - quoteIntegrationEndPoint: https://anchor-ref-preview-id.previews.kube001.services.stellar-ops.com - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: ${CIRCLE_API_KEY} - payment-gateway: - circle: - name: circle - enabled: true - stellar: - enabled: false - name: stellar - horizonUrl: https://horizon-testnet.stellar.org - secretKey: ${PAYMENT_GATEWAY_STELLAR_SECRET_KEY} # stellar account secret key - circle-payment-observer: - enabled: true - horizonUrl: https://horizon-testnet.stellar.org - stellarNetwork: TESTNET - trackedWallet: all - event: - # If enabled, publish Events to a queue (publisherType) - # publisherType - the type of queue to use for event publishing - enabled: true - publisherType: kafka - metrics-service: - optionalMetricsEnabled: false # optional metrics that periodically query the database - runInterval: 30 # interval to query the database to generate the optional metrics - kafka.publisher: - bootstrapServer: anchor-platform-kafka.anchor-platform-dev.svc.cluster.local:9092 - useIAM: false - useSingleQueue: false - eventTypeToQueue: - all: preview-id_ap_event_single_queue - quoteCreated: preview-id_ap_event_quote_created - transactionCreated: preview-id_ap_event_transaction_created - transactionStatusChanged: preview-id_ap_event_transaction_status_changed - transactionError: preview-id_ap_event_transaction_error - data-spring-jdbc-sqlite: - spring.datasource.driver-class-name: org.sqlite.JDBC - spring.datasource.initial-size: 1 - spring.datasource.max-active: 1 - spring.datasource.password: ${SQLITE_PASSWORD} - spring.datasource.url: jdbc:sqlite:anchor-proxy.db - spring.datasource.username: ${SQLITE_USERNAME} - spring.jpa.database-platform: org.stellar.anchor.platform.sqlite.SQLiteDialect - spring.jpa.generate-ddl: true - spring.jpa.hibernate.ddl-auto: update - spring.jpa.hibernate.show_sql: false - spring.mvc.converters.preferred-json-mapper: gson - spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 - stellar-wks.toml: | - ACCOUNTS = ["GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY"] - VERSION = "0.1.0" - NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" - SIGNING_KEY = "GDZCLP2PHTWYQ3BWZMC5D4ZUJX5NU4S7YZHC5EBFAFKL6UJUQ5RKDGSY" - TRANSFER_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep6" - TRANSFER_SERVER_SEP0024 = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep24" - WEB_AUTH_ENDPOINT = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/auth" - KYC_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep12" - DIRECT_PAYMENT_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep31" - ANCHOR_QUOTE_SERVER = "https://anchor-sep-preview-id.previews.kube001.services.stellar-ops.com/sep38" - - [[CURRENCIES]] - code = "USDC" - issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - status = "test" - is_asset_anchored = true - anchor_asset_type = "fiat" - desc = "A test USDC issued by Circle." - - [DOCUMENTATION] - ORG_NAME = "Stellar Development Foundation" - ORG_URL = "https://www.stellar.org" - ORG_DESCRIPTION = "Stellar is an open network for storing and moving money." - ORG_LOGO = "https://assets-global.website-files.com/5deac75ecad2173c2ccccbc7/5dec8960504967fd31147f62_Stellar_lockup_black_RGB.svg" - ORG_SUPPORT_EMAIL = "jamie@stellar.org" ---- -# Source: sep/templates/service.yaml -# SEP Server -apiVersion: v1 -kind: Service -metadata: - name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id - helm.sh/chart: sep-0.3.90 - app.kubernetes.io/instance: release-name - app.kubernetes.io/managed-by: Helm -spec: - type: ClusterIP - ports: - - protocol: TCP - port: 8080 # port number the service will listen on - targetPort: 8080 # port number pods listen on - selector: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id - - -# Stellar Observer ---- -# Source: sep/templates/service.yaml -apiVersion: v1 -kind: Service -metadata: - name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id-observer - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer - helm.sh/chart: sep-0.3.90 - app.kubernetes.io/instance: release-name - app.kubernetes.io/managed-by: Helm -spec: - type: ClusterIP - ports: - - protocol: TCP - port: 8083 # port number the service will listen on - targetPort: 8083 # port number pods listen on - selector: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer ---- -# Source: sep/templates/deployment.yaml -# SEP Server -apiVersion: apps/v1 -kind: Deployment -metadata: - name: anchor-platform-sep-server-preview-id - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id - helm.sh/chart: sep-0.3.90 - app.kubernetes.io/instance: release-name - app.kubernetes.io/managed-by: Helm -spec: - replicas: - selector: - matchLabels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id - template: - metadata: - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id - app.kubernetes.io/instance: release-name - annotations: - prometheus.io/path: "/actuator/prometheus" - prometheus.io/port: "8082" - prometheus.io/scrape: "true" - spec: - containers: - - name: sep - image: "docker-registry.services.stellar-ops.com/dev/anchor-platform-preview:preview-id" - args: ["--sep-server"] - imagePullPolicy: Always - startupProbe: - httpGet: - path: /health - port: 8080 - failureThreshold: 10 - periodSeconds: 15 - livenessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 30 - failureThreshold: 2 - periodSeconds: 3 - readinessProbe: - httpGet: - path: /health - port: 8080 - initialDelaySeconds: 30 - periodSeconds: 3 - volumeMounts: - - name: sep-config-volume - mountPath: /config - readOnly: true - - mountPath: assets - name: anchor-platform-sep-server-preview-id-assets-volume - ports: - - name: http - containerPort: 8080 - protocol: TCP - - name: metrics - containerPort: 8082 - protocol: TCP - env: - - name: STELLAR_ANCHOR_CONFIG - value: file:/config/anchor-config.yaml - envFrom: - - secretRef: - name: anchor-platform-secret-common-previews - volumes: - - name: sep-config-volume - configMap: - name: anchor-platform-sep-server-preview-id - - name: anchor-platform-sep-server-preview-id-assets-volume - configMap: - name: anchor-platform-sep-server-preview-id-assets - -# Stellar Observer--- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: anchor-platform-sep-server-preview-id-observer - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer - helm.sh/chart: sep-0.3.90 - app.kubernetes.io/instance: release-name - app.kubernetes.io/managed-by: Helm -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer - template: - metadata: - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-observer - app.kubernetes.io/instance: release-name - annotations: - prometheus.io/path: "/actuator/prometheus" - prometheus.io/port: "8082" - prometheus.io/scrape: "true" - spec: - containers: - - name: sep-observer - image: "docker-registry.services.stellar-ops.com/dev/anchor-platform-preview:preview-id" - args: ["--stellar-observer"] - imagePullPolicy: Always - startupProbe: - httpGet: - path: /health - port: 8083 - failureThreshold: 10 - periodSeconds: 15 - livenessProbe: - httpGet: - path: /health - port: 8083 - initialDelaySeconds: 60 - failureThreshold: 2 - periodSeconds: 15 - readinessProbe: - httpGet: - path: /health - port: 8083 - initialDelaySeconds: 60 - periodSeconds: 15 - volumeMounts: - - name: sep-config-volume - mountPath: /config - readOnly: true - - mountPath: assets - name: anchor-platform-sep-server-preview-id-assets-volume - ports: - - name: http - containerPort: 8083 - protocol: TCP - env: - - name: STELLAR_ANCHOR_CONFIG - value: file:/config/anchor-config.yaml - envFrom: - - secretRef: - name: anchor-platform-secret-common-previews - volumes: - - name: sep-config-volume - configMap: - name: anchor-platform-sep-server-preview-id - - name: anchor-platform-sep-server-preview-id-assets-volume - configMap: - name: anchor-platform-sep-server-preview-id-assets ---- -# Source: sep/templates/ingress.yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id - annotations: - fluxcd.io/ignore: "true" - ingress.kubernetes.io/browser-xss-filter: "true" - ingress.kubernetes.io/content-type-nosniff: "true" - ingress.kubernetes.io/frame-deny: "true" - ingress.kubernetes.io/hsts-include-subdomains: "true" - ingress.kubernetes.io/hsts-max-age: "31536000" - ingress.kubernetes.io/ssl-redirect: "true" - kubernetes.io/ingress.class: "public" - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id - helm.sh/chart: sep-0.3.90 - app.kubernetes.io/instance: release-name - app.kubernetes.io/managed-by: Helm -spec: - tls: - - hosts: - - anchor-sep-preview-id.previews.kube001.services.stellar-ops.com - secretName: star.previews.kube001.services.stellar-ops.com.tls - rules: - - host: anchor-sep-preview-id.previews.kube001.services.stellar-ops.com - http: - paths: - - path: / - pathType: "Prefix" - backend: - service: - name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id - port: - number: 8080 - -# Stellar Observer ---- -# Source: sep/templates/ingress.yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id-observer - annotations: - fluxcd.io/ignore: "true" - ingress.kubernetes.io/browser-xss-filter: "true" - ingress.kubernetes.io/content-type-nosniff: "true" - ingress.kubernetes.io/frame-deny: "true" - ingress.kubernetes.io/hsts-include-subdomains: "true" - ingress.kubernetes.io/hsts-max-age: "31536000" - ingress.kubernetes.io/ssl-redirect: "true" - kubernetes.io/ingress.class: "public" - labels: - app.kubernetes.io/name: anchor-platform-sep-server-preview-id-ing-sep-server-preview-id-observer - helm.sh/chart: sep-0.3.90 - app.kubernetes.io/instance: release-name - app.kubernetes.io/managed-by: Helm -spec: - tls: - - hosts: - - anchor-sep-preview-id-observer.previews.kube001.services.stellar-ops.com - secretName: star.previews.kube001.services.stellar-ops.com.tls - rules: - - host: anchor-sep-preview-id-observer.previews.kube001.services.stellar-ops.com - http: - paths: - - path: / - pathType: "Prefix" - backend: - service: - name: anchor-platform-sep-server-preview-id-svc-sep-server-preview-id-observer - port: - number: 8083 ---- -# Source: sep/templates/ingress.yaml -# SEP Server \ No newline at end of file diff --git a/helm-charts/sep-service/templates/deployment.yaml b/helm-charts/sep-service/templates/deployment.yaml index 707a478fb0..d3fcc6b5f1 100644 --- a/helm-charts/sep-service/templates/deployment.yaml +++ b/helm-charts/sep-service/templates/deployment.yaml @@ -1,3 +1,5 @@ +--- +# SEP Server apiVersion: apps/v1 kind: Deployment metadata: @@ -8,7 +10,7 @@ metadata: app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} spec: - replicas: {{ .Values.replicaCount }} + replicas: {{ (.Values.deployment).replicas | default 1 }} selector: matchLabels: app.kubernetes.io/name: {{ .Values.fullName }} @@ -30,7 +32,11 @@ spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" - args: {{ printf "[ %s ]" ((join ", " .Values.deployment.args) | default "\"--sep-server\", \"--stellar-observer\"") }} + {{- if (.Values.stellarObserver).enabled }} + args: ["--sep-server"] + {{- else }} + args: ["--sep-server", "--stellar-observer"] + {{- end }} imagePullPolicy: {{ .Values.image.pullPolicy | default "Always" }} startupProbe: httpGet: @@ -95,3 +101,104 @@ spec: secretName: {{ $secret.name }} {{- end }} {{- end }} + +{{- $stellarObserverDeployment := ((.Values.stellarObserver).deployment) }} +{{- if (.Values.stellarObserver).enabled }} +--- +# Stellar Observer +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.fullName }}-observer + labels: + app.kubernetes.io/name: {{ .Values.fullName }}-observer + helm.sh/chart: {{ include "common.chart" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: {{ .Values.fullName }}-observer + template: + metadata: + labels: + app.kubernetes.io/name: {{ .Values.fullName }}-observer + app.kubernetes.io/instance: {{ .Release.Name }} + {{- if (.Values.deployment).annotations }} + annotations: + {{- range $key, $value := .Values.deployment.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + spec: + {{- if (.Values.deployment).serviceAccountName }} + serviceAccountName: {{ .Values.deployment.serviceAccountName | default "default" }} + {{- end }} + containers: + - name: {{ .Chart.Name }}-observer + image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ .Values.image.tag }}" + args: ["--stellar-observer"] + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always" }} + startupProbe: + httpGet: + path: {{ $stellarObserverDeployment.probePath | default "/health" }} + port: {{ $stellarObserverDeployment.port | default 8083 }} + failureThreshold: {{ $stellarObserverDeployment.startupProbeFailureThreshold | default 10 }} + periodSeconds: {{ $stellarObserverDeployment.probePeriodSeconds | default 15 }} + livenessProbe: + httpGet: + path: {{ $stellarObserverDeployment.probePath | default "/health" }} + port: {{ $stellarObserverDeployment.port | default 8083 }} + initialDelaySeconds: {{ $stellarObserverDeployment.initialDelaySeconds | default 30 }} + failureThreshold: {{ $stellarObserverDeployment.livenessProbeFailureThreshold | default 2 }} + periodSeconds: {{ $stellarObserverDeployment.probePeriodSeconds | default 15 }} + readinessProbe: + httpGet: + path: {{ $stellarObserverDeployment.probePath | default "/health" }} + port: {{ $stellarObserverDeployment.port | default 8083 }} + initialDelaySeconds: {{ $stellarObserverDeployment.initialDelaySeconds | default 30 }} + periodSeconds: {{ $stellarObserverDeployment.probePeriodSeconds | default 15 }} + volumeMounts: + - name: sep-config-volume + mountPath: /config + readOnly: true + {{- if ((.Values.deployment).volumeMounts).configMaps }} + {{- range $conf := .Values.deployment.volumeMounts.configMaps }} + - mountPath: {{ $conf.mountPath }} + name: {{ $conf.name }}-volume + {{- end }} + {{- end }} + {{- if ((.Values.deployment).volumeMounts).secrets }} + {{- range $secret := .Values.deployment.volumeMounts.secrets }} + - mountPath: {{ $secret.mountPath | default $secret.name }} + name: {{ $secret.name }}-volume + {{- end }} + {{- end }} + ports: + - name: http + containerPort: {{ $stellarObserverDeployment.port | default 8083 }} + protocol: TCP + {{- if .Values.deployment.envVars }} + {{- toYaml .Values.deployment.envVars | nindent 10 }} + {{- end }} + volumes: + - name: sep-config-volume + configMap: + name: {{ .Values.fullName }} + {{- if ((.Values.deployment).volumeMounts).configMaps }} + {{- range $conf := .Values.deployment.volumeMounts.configMaps }} + - name: {{ $conf.name }}-volume + configMap: + name: {{ $conf.name }} + {{- end }} + {{- end }} + {{- if ((.Values.deployment).volumeMounts).secrets }} + {{- range $secret := .Values.deployment.volumeMounts.secrets }} + - name: {{ $secret.name }}-volume + secret: + secretName: {{ $secret.name }} + {{- end }} + {{- end }} + +{{- end }} \ No newline at end of file diff --git a/helm-charts/sep-service/templates/ingress.yaml b/helm-charts/sep-service/templates/ingress.yaml index 4681efa0ee..b74a10c59d 100644 --- a/helm-charts/sep-service/templates/ingress.yaml +++ b/helm-charts/sep-service/templates/ingress.yaml @@ -1,3 +1,5 @@ +--- +# SEP Server apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -36,7 +38,7 @@ spec: - host: {{ tpl .host $ }} http: paths: - - path: {{ .path | quote }} + - path: {{ .path }} pathType: {{ .pathType| quote }} backend: service: @@ -44,4 +46,59 @@ spec: port: number: {{ tpl .backend.servicePort $ }} {{- end }} - {{- end }} \ No newline at end of file + {{- end }} + + +{{- $root := . }} +{{- $stellarObserver := (.Values.stellarObserver) }} +{{- if $stellarObserver.enabled }} +--- +# Stellar Observer +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Values.fullName }}-ing-{{ .Values.service.name }}-observer + {{- if .Values.ingress.metadata }} + {{- range $key, $value := .Values.ingress.metadata }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- if .Values.ingress.annotations }} + annotations: + {{- range $key, $value := .Values.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + labels: + app.kubernetes.io/name: {{ .Values.fullName }}-ing-{{ .Values.service.name }}-observer + helm.sh/chart: {{ $.Chart.Name }}-{{ $.Chart.Version }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + {{- if .Values.ingress.ingressClassName }} + ingressClassName: {{ .Values.ingress.ingressClassName }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + - hosts: + - {{ $stellarObserver.ingress.tls.host }} + {{- if .Values.ingress.tls.secretName }} + secretName: {{ .Values.ingress.tls.secretName }} + {{- end }} + {{- end }} + {{- if .Values.ingress.rules }} + rules: + {{- range $stellarObserver.ingress.rules.hosts }} + - host: {{ tpl .host $ }} + http: + paths: + - path: {{ .path }} + pathType: {{ .pathType | quote }} + backend: + service: + name: {{ $root.Values.fullName }}-svc-{{ $root.Values.service.name }}-observer + port: + number: {{ $stellarObserver.deployment.port | default 8083 }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/helm-charts/sep-service/templates/service.yaml b/helm-charts/sep-service/templates/service.yaml index 91df67a4ca..5d90d2c1b7 100644 --- a/helm-charts/sep-service/templates/service.yaml +++ b/helm-charts/sep-service/templates/service.yaml @@ -1,3 +1,5 @@ +--- +# SEP Server apiVersion: v1 kind: Service metadata: @@ -25,4 +27,37 @@ spec: port: {{ .Values.service.servicePort | default 8080 }} # port number the service will listen on targetPort: {{ .Values.service.targetPort | default 8080 }} # port number pods listen on selector: - app.kubernetes.io/name: {{ .Values.fullName }} \ No newline at end of file + app.kubernetes.io/name: {{ .Values.fullName }} + +{{- if (.Values.stellarObserver).enabled }} +--- +# Stellar Observer +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.fullName }}-svc-{{ .Values.service.name }}-observer + {{- if .Values.service.annotations }} + annotations: + {{- range $key, $value := .Values.service.annotations }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + labels: + {{- if .Values.service.labels }} + {{- range $key, $value := .Values.service.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- end }} + app.kubernetes.io/name: {{ .Values.fullName }}-observer + helm.sh/chart: {{ $.Chart.Name }}-{{ $.Chart.Version }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + ports: + - protocol: TCP + port: {{ ((.Values.stellarObserver).deployment).port | default 8083}} # port number the service will listen on + targetPort: {{ ((.Values.stellarObserver).deployment).port | default 8083 }} # port number pods listen on + selector: + app.kubernetes.io/name: {{ .Values.fullName }}-observer +{{- end }} \ No newline at end of file diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java index 587cf0819d..76e1e84507 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/KafkaConfig.java @@ -8,7 +8,7 @@ public class KafkaConfig { * A comma-separated list of host:port pairs that are the addresses of one or more brokers in a * Kafka cluster, e.g. localhost:9092 or localhost:9092,another.host:9092. */ - String bootstrapServers; + String bootstrapServer; /** The client ID. If left empty, it is randomly generated. */ String clientId; diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java index c851836f2d..5e16524f80 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyEventConfig.java @@ -41,7 +41,7 @@ public void validate(Object target, Errors errors) { // continue to the kafka case. DO NOT break case "kafka": ValidationUtils.rejectIfEmptyOrWhitespace( - errors, "publisher.kafka.bootstrapServers", "empty-bootstrapServer"); + errors, "publisher.kafka.bootstrapServer", "empty-bootstrapServer"); ValidationUtils.rejectIfEmptyOrWhitespace( errors, "publisher.kafka.retries", diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java index 4809f2e009..6e65a0e229 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java @@ -23,7 +23,7 @@ public class KafkaEventPublisher implements EventPublisher { public KafkaEventPublisher(KafkaConfig kafkaConfig) { Log.debugF("kafkaConfig: {}", kafkaConfig); Properties props = new Properties(); - props.put(BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServers()); + props.put(BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServer()); props.put(KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); props.put(CLIENT_ID_CONFIG, kafkaConfig.getClientId()); diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java index 35df084816..2765a79bc0 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/MskEventPublisher.java @@ -13,7 +13,7 @@ public class MskEventPublisher extends KafkaEventPublisher { public MskEventPublisher(MskConfig mskConfig) { Log.debugF("MskConfig: {}", mskConfig); Properties props = new Properties(); - props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, mskConfig.getBootstrapServers()); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, mskConfig.getBootstrapServer()); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); props.put(CLIENT_ID_CONFIG, mskConfig.getClientId()); diff --git a/platform/src/main/resources/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index 5c627a92f8..e6df364a87 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -359,7 +359,7 @@ events: ## A comma-separated list of host:port pairs that are the addresses of one or more brokers in a Kafka cluster, ## e.g. localhost:9092 or localhost:9092,another.host:9092. # - bootstrap_servers: localhost:29092 + bootstrap_server: localhost:29092 # The client ID. If left empty, it is randomly generated. client_id: # Determines how many times the producer will attempt to send a message before marking it as failed. diff --git a/platform/src/main/resources/config/anchor-config-schema-v1.yaml b/platform/src/main/resources/config/anchor-config-schema-v1.yaml index f121333405..194678cad6 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -18,7 +18,7 @@ events.event_type_to_queue.transaction_created: events.event_type_to_queue.transaction_error: events.event_type_to_queue.transaction_status_changed: events.publisher.kafka.batch_size: -events.publisher.kafka.bootstrap_servers: +events.publisher.kafka.bootstrap_server: events.publisher.kafka.client_id: events.publisher.kafka.linger_ms: events.publisher.kafka.retries: diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml index 1dea51cbcb..f6a95b974d 100644 --- a/platform/src/main/resources/example.anchor-config.yaml +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -39,6 +39,16 @@ data: flyway_enabled: true ddl_auto: update +#data: +# type: postgres +# url: jdbc:postgresql://localhost:5432/anchorplatform +# username: anchorplatform +# password: +# initial_connection_pool_size: 3 +# max_active_connections: 10 +# flyway_enabled: true +# ddl_auto: update + sep1: enabled: true toml: @@ -104,7 +114,7 @@ events: publisher: type: kafka kafka: - bootstrap_servers: localhost:29092 + bootstrap_server: localhost:29092 client_id: retries: 1 linger_ms: 1000 diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt index 9530a16bea..9290a13625 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/EventConfigTest.kt @@ -23,7 +23,7 @@ open class EventConfigTest { val eventConfig = PropertyEventConfig() val kafkaConfig = KafkaConfig() - kafkaConfig.bootstrapServers = "localhost:29092" + kafkaConfig.bootstrapServer = "localhost:29092" eventConfig.publisher = PropertyPublisherConfig() eventConfig.publisher.type = "kafka" eventConfig.publisher.kafka = kafkaConfig From d5bae046c4916c173dd52168891f0ff586caa73a Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 20 Oct 2022 13:58:54 -0700 Subject: [PATCH 63/65] Fix Sep31Transaction.refunds Gson serialization exception (#634) --- CHANGELOG.md | 3 + build.gradle.kts | 2 +- .../stellar/anchor/sep10/Sep10ServiceTest.kt | 37 +++++++++-- .../anchor/sep31/Sep31TransactionTest.kt | 4 +- .../platform/data/JdbcSep31RefundPayment.java | 12 ++-- .../platform/data/JdbcSep31Refunds.java | 26 +++++++- .../platform/data/JdbcSep31Transaction.java | 2 +- .../data/JdbcSep31TransactionStore.java | 2 +- .../platform/data/JdbcSep31TransactionTest.kt | 61 +++++++++++++++++++ .../service/TransactionServiceTest.kt | 9 ++- 10 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index db7f24d9cb..0578f57b60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 1.2.1 +* Fix gson serialization error of the Refunds object. [#626](https://github.com/stellar/java-stellar-anchor-sdk/issues/626) + ## 1.2.0 * Add Stellar observer retries with exponential back-off timer [#607](https://github.com/stellar/java-stellar-anchor-sdk/pull/607) * Add health check endpoint to the Stellar observer [#602](https://github.com/stellar/java-stellar-anchor-sdk/pull/602) diff --git a/build.gradle.kts b/build.gradle.kts index 2854596987..1d1f9d5b03 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -119,7 +119,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "1.2.0" + version = "1.2.1" tasks.jar { manifest { diff --git a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt index bc04dfe7a9..81e1449838 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -5,8 +5,12 @@ import io.mockk.* import io.mockk.impl.annotations.MockK import java.io.IOException import java.security.SecureRandom +import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit import java.util.stream.Stream +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.mockwebserver.MockResponse @@ -82,12 +86,8 @@ internal class Sep10ServiceTest { private lateinit var sep10Service: Sep10Service private val clientKeyPair = KeyPair.random() private val clientDomainKeyPair = KeyPair.random() - private val httpClient: OkHttpClient = - OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.MINUTES) - .readTimeout(10, TimeUnit.MINUTES) - .writeTimeout(10, TimeUnit.MINUTES) - .build() + + private lateinit var httpClient: OkHttpClient @BeforeEach fun setUp() { @@ -111,6 +111,7 @@ internal class Sep10ServiceTest { this.jwtService = spyk(JwtService(secretConfig)) this.sep10Service = Sep10Service(appConfig, secretConfig, sep10Config, horizon, jwtService) + this.httpClient = `create httpClient that trust all certificates`() } @AfterEach @@ -119,6 +120,30 @@ internal class Sep10ServiceTest { unmockkAll() } + fun `create httpClient that trust all certificates`(): OkHttpClient { + val trustAllCerts = + arrayOf( + object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) {} + + override fun checkServerTrusted(chain: Array?, authType: String?) {} + + override fun getAcceptedIssuers() = arrayOf() + } + ) + + // Install the all-trusting trust manager + val sslContext = SSLContext.getInstance("SSL") + sslContext.init(null, trustAllCerts, SecureRandom()) + return OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.MINUTES) + .readTimeout(10, TimeUnit.MINUTES) + .writeTimeout(10, TimeUnit.MINUTES) + .sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager) + .hostnameVerifier { _, _ -> true } + .build() + } + @Test fun `test the challenge with existent account, multisig, and client domain`() { // 1 ------ Create Test Transaction 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 c157ce416b..b88fd2639a 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -136,7 +136,7 @@ class Sep31TransactionTest { } @Test - fun test_toPlatformApiGetTransactionResponse() { + fun `test PlatformApiGetTransactionResponse correctness`() { val wantRefunds: Refund = Refund.builder() .amountRefunded(Amount("90.0000", fiatUSD)) @@ -196,7 +196,7 @@ class Sep31TransactionTest { } @Test - fun test_toSep31GetTransactionResponse() { + fun `test Sep31GetTransactionResponse correctness`() { val refunds = Sep31GetTransactionResponse.Refunds.builder() .amountRefunded("90.0000") diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java index 3adf81dbee..18d7f4b57f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31RefundPayment.java @@ -3,11 +3,9 @@ import lombok.Data; import org.stellar.anchor.sep31.RefundPayment; -public class JdbcSep31RefundPayment { - @Data - public static class JdbcRefundPayment implements RefundPayment { - String Id; - String amount; - String fee; - } +@Data +public class JdbcSep31RefundPayment implements RefundPayment { + String id; + String amount; + String fee; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java index 08ddbf58d5..5a8f372882 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Refunds.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.data; import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; import java.util.List; import lombok.Data; import org.stellar.anchor.sep31.RefundPayment; @@ -15,5 +16,28 @@ public class JdbcSep31Refunds implements Refunds { String amountFee; @SerializedName("payments") - List refundPayments; + List refundPayments; + + @Override + public List getRefundPayments() { + if (refundPayments == null) return null; + // getPayments() is made for Gson serialization. + List payments = new ArrayList<>(refundPayments.size()); + for (JdbcSep31RefundPayment refundPayment : refundPayments) { + payments.add(refundPayment); + } + return payments; + } + + @Override + public void setRefundPayments(List refundPayments) { + this.refundPayments = new ArrayList<>(refundPayments.size()); + for (RefundPayment rp : refundPayments) { + if (rp instanceof JdbcSep31RefundPayment) + this.refundPayments.add((JdbcSep31RefundPayment) rp); + else + throw new ClassCastException( + String.format("Error casting %s to JdbcSep31RefundPayment", rp.getClass())); + } + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java index 45b4163177..dfb14ec191 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31Transaction.java @@ -143,7 +143,7 @@ public String getRefundsJson() { public void setRefundsJson(String refundsJson) { if (refundsJson != null) { - this.refunds = gson.fromJson(refundsJson, Refunds.class); + this.refunds = gson.fromJson(refundsJson, JdbcSep31Refunds.class); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java index 72d97b932b..a483266ebe 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java @@ -31,7 +31,7 @@ public Refunds newRefunds() { @Override public RefundPayment newRefundPayment() { - return new JdbcSep31RefundPayment.JdbcRefundPayment(); + return new JdbcSep31RefundPayment(); } @Override diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt new file mode 100644 index 0000000000..1e64bfada7 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt @@ -0,0 +1,61 @@ +package org.stellar.anchor.platform.data + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.skyscreamer.jsonassert.JSONAssert + +class JdbcSep31TransactionTest { + private val refundsJsonNoRefundPayment = + """ + { + "amount_refunded": "10", + "amount_fee": "5", + "refundPayments": null + } + """.trimIndent() + + private val refundsJsonWithRefundPayment = + """ + { + "amount_refunded": "10", + "amount_fee": "5", + "payments": [ + { + "id": "1", + "amount": "5", + "fee": "1" + }, + { + "id": "2", + "amount": "5", + "fee": "4" + } + ] + } + """.trimIndent() + + @Test + fun `test JdbcSep31Transaction refunds Json conversion`() { + val txn = JdbcSep31Transaction() + txn.refundsJson = refundsJsonNoRefundPayment + // strict is set to false because refundPayments is omitted in txn.refundsJson when it is set to + // null. + JSONAssert.assertEquals(txn.refundsJson, refundsJsonNoRefundPayment, false) + assertEquals("10", txn.refunds.amountRefunded) + assertEquals("5", txn.refunds.amountFee) + assertNull(txn.refunds.refundPayments) + + txn.refundsJson = refundsJsonWithRefundPayment + JSONAssert.assertEquals(txn.refundsJson, refundsJsonWithRefundPayment, true) + assertEquals("10", txn.refunds.amountRefunded) + assertEquals("5", txn.refunds.amountFee) + assertEquals(2, txn.refunds.refundPayments.size) + assertEquals("1", txn.refunds.refundPayments[0].id) + assertEquals("5", txn.refunds.refundPayments[0].amount) + assertEquals("1", txn.refunds.refundPayments[0].fee) + assertEquals("2", txn.refunds.refundPayments[1].id) + assertEquals("5", txn.refunds.refundPayments[1].amount) + assertEquals("4", txn.refunds.refundPayments[1].fee) + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt index 437d4ee1af..ce716dfed0 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/TransactionServiceTest.kt @@ -3,8 +3,11 @@ package org.stellar.anchor.platform.service import io.mockk.* import io.mockk.impl.annotations.MockK import java.time.Instant -import org.junit.jupiter.api.* +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* +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.EnumSource import org.stellar.anchor.api.exception.AnchorException @@ -19,7 +22,7 @@ import org.stellar.anchor.api.shared.RefundPayment import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService import org.stellar.anchor.event.models.TransactionEvent -import org.stellar.anchor.platform.data.JdbcSep31RefundPayment.JdbcRefundPayment +import org.stellar.anchor.platform.data.JdbcSep31RefundPayment import org.stellar.anchor.platform.data.JdbcSep31Refunds import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.sep31.* @@ -78,7 +81,7 @@ class TransactionServiceTest { // Mock the store every { sep31TransactionStore.newTransaction() } returns JdbcSep31Transaction() every { sep31TransactionStore.newRefunds() } returns JdbcSep31Refunds() - every { sep31TransactionStore.newRefundPayment() } answers { JdbcRefundPayment() } + every { sep31TransactionStore.newRefundPayment() } answers { JdbcSep31RefundPayment() } // mock time val mockStartedAt = Instant.now().minusSeconds(180) From 92632ae5cb14be67b8f7f2cfc48c69df52e8a52f Mon Sep 17 00:00:00 2001 From: Stephen Date: Mon, 24 Oct 2022 15:15:48 -0400 Subject: [PATCH 64/65] Secrets config refactor (#621) * move data secrets to secret manager --- .../org/stellar/anchor/util/StringHelper.java | 9 +++++ .../platform/AnchorPlatformIntegrationTest.kt | 4 ++- .../anchor/platform/AnchorPlatformServer.java | 2 ++ .../platform/config/PropertySecretConfig.java | 24 +++++++------ .../configurator/ConfigEnvironment.java | 10 +++--- .../platform/configurator/ConfigHelper.java | 22 +++++++++--- .../platform/configurator/ConfigMap.java | 1 + .../configurator/DataConfigAdapter.java | 35 +++++++------------ .../platform/configurator/SecretManager.java | 11 ++++-- .../config/anchor-config-default-values.yaml | 15 +------- .../config/anchor-config-schema-v1.yaml | 6 ---- platform/src/main/resources/example.env | 12 +++---- 12 files changed, 80 insertions(+), 71 deletions(-) diff --git a/core/src/main/java/org/stellar/anchor/util/StringHelper.java b/core/src/main/java/org/stellar/anchor/util/StringHelper.java index 6a2f71d4e8..231a3b5052 100644 --- a/core/src/main/java/org/stellar/anchor/util/StringHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/StringHelper.java @@ -24,4 +24,13 @@ public static String camelToSnake(String camel) { .replaceAll("-", "_") .toLowerCase(); } + + public static String toPosixForm(String camel) { + return camel + .replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2") + .replaceAll("([a-z])([A-Z])", "$1_$2") + .replaceAll("-", "_") + .replaceAll("\\.", "_") + .toUpperCase(); + } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt index b48c7ae369..c2d8575f57 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt @@ -87,7 +87,9 @@ class AnchorPlatformIntegrationTest { mapOf( "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", "secret.sep10.jwt_secret" to "secret", - "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF" + "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF", + "secret.data.username" to "user1", + "secret.data.password" to "password" ) platformServerContext = ServiceRunner.startSepServer(SEP_SERVER_PORT, "/", envMap) diff --git a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java index 831f4e8e2f..ca037081fc 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -14,6 +14,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.stellar.anchor.platform.configurator.ConfigEnvironment; import org.stellar.anchor.platform.configurator.ConfigManager; +import org.stellar.anchor.platform.configurator.SecretManager; @Profile("default") @SpringBootApplication @@ -48,6 +49,7 @@ public static ConfigurableApplicationContext start( SpringApplication springApplication = builder.build(); + springApplication.addInitializers(SecretManager.getInstance()); springApplication.addInitializers(ConfigManager.getInstance()); return springApplication.run(); diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java index 5a3aa6b43b..c620385afa 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySecretConfig.java @@ -1,20 +1,22 @@ package org.stellar.anchor.platform.config; -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; import org.stellar.anchor.config.SecretConfig; +import org.stellar.anchor.platform.configurator.SecretManager; -@Data public class PropertySecretConfig implements SecretConfig { - @Value("${secret.sep10.jwt_secret:#{null}}") - private String sep10JwtSecretKey; + public String getSep10JwtSecretKey() { + return SecretManager.getInstance().get("secret.sep10.jwt_secret"); + } - @Value("${secret.sep10.signing_seed:#{null}}") - private String sep10SigningSeed; + public String getSep10SigningSeed() { + return SecretManager.getInstance().get("secret.sep10.signing_seed"); + } - @Value("${secret.callback_api.auth_secret:#{null}}") - private String callbackApiSecret = null; + public String getCallbackApiSecret() { + return SecretManager.getInstance().get("secret.callback_api.auth_secret"); + } - @Value("${secret.platform_api.auth_secret:#{null}}") - private String platformApiSecret = null; + public String getPlatformApiSecret() { + return SecretManager.getInstance().get("secret.platform_api.auth_secret"); + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java index ce5fd39f8f..2b92b22dee 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigEnvironment.java @@ -1,6 +1,6 @@ package org.stellar.anchor.platform.configurator; -import static org.stellar.anchor.platform.configurator.ConfigHelper.normalize; +import static org.stellar.anchor.util.StringHelper.toPosixForm; import java.util.Collection; import java.util.HashMap; @@ -16,13 +16,15 @@ public class ConfigEnvironment { public static void rebuild() { env = new HashMap<>(); + + // Read all env variables and convert everything to POSIX form for (Map.Entry entry : System.getenv().entrySet()) { - env.put(normalize(entry.getKey()), entry.getValue()); + env.put(toPosixForm(entry.getKey()), entry.getValue()); } Properties sysProps = System.getProperties(); for (Map.Entry entry : sysProps.entrySet()) { - env.put(normalize(String.valueOf(entry.getKey())), String.valueOf(entry.getValue())); + env.put(toPosixForm(String.valueOf(entry.getKey())), String.valueOf(entry.getValue())); } } @@ -34,7 +36,7 @@ public static void rebuild() { * @return the value of the environment variable. */ public static String getenv(String name) { - return env.get(ConfigHelper.normalize(name)); + return env.get(toPosixForm(name)); } public static Collection names() { diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java index 0950679b50..4b6c10cced 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigHelper.java @@ -5,6 +5,7 @@ import static org.stellar.anchor.util.StringHelper.isEmpty; import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.boot.env.YamlPropertySourceLoader; @@ -15,6 +16,7 @@ import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource; import org.stellar.anchor.util.Log; +import org.stellar.anchor.util.StringHelper; public class ConfigHelper { public static ConfigMap loadConfig(Resource resource, ConfigSource configSource) @@ -42,9 +44,9 @@ public static ConfigMap loadConfigFromEnv(int latestVersion) throws InvalidConfi ConfigMap config = new ConfigMap(); // Determine the version. - // if system.env['version'] is not defined, use the latest version. + // if system.env['VERSION'] is not defined, use the latest version. int version; - String strVersion = ConfigEnvironment.getenv("version"); + String strVersion = ConfigEnvironment.getenv("VERSION"); if (strVersion == null) { Log.infoF("System env['version'] is not defined. version:{} is assumed}", latestVersion); version = latestVersion; @@ -61,10 +63,20 @@ public static ConfigMap loadConfigFromEnv(int latestVersion) throws InvalidConfi ConfigReader configSchema = new ConfigReader(version); config.setVersion(version); + Map posixFormToNormalizedName = new HashMap<>(); + + // Maintain a map of system env variable names (POSIX standard - uppercase and underscores only) + // to the internal config name + for (String name : configSchema.configSchema.names()) { + posixFormToNormalizedName.put(StringHelper.toPosixForm(name), name); + } + for (String name : ConfigEnvironment.names()) { - if (!isEmpty(name) && configSchema.has(name) && !name.equals("version")) { + if (!isEmpty(name) + && configSchema.has(posixFormToNormalizedName.get(name)) + && !name.equals("VERSION")) { // the envarg is defined in this version - config.put(name, ConfigEnvironment.getenv(name), ENV); + config.put(posixFormToNormalizedName.get(name), ConfigEnvironment.getenv(name), ENV); } } return config; @@ -88,6 +100,8 @@ public static String suggestedSchema() throws IOException { config.put("secret.jwt_secret", "", VERSION_SCHEMA); config.put("secret.platform_api.secret", "", VERSION_SCHEMA); config.put("secret.callback_api.secret", "", VERSION_SCHEMA); + config.put("secret.data.username", "", VERSION_SCHEMA); + config.put("secret.data.password", "", VERSION_SCHEMA); return config.printToString(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java index fdd04608c4..83ba9d3fc8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigMap.java @@ -9,6 +9,7 @@ public class ConfigMap { int version; Map data; + // ConfigMap keys will be in normalized form (dot separated hierarchy) public ConfigMap() { data = new HashMap<>(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java index e734dd7ef8..05a5540498 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/DataConfigAdapter.java @@ -92,8 +92,6 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.datasource.url", "jdbc:h2:mem:test"); set("spring.jpa.database-platform", "org.hibernate.dialect.H2Dialect"); set("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.H2Dialect"); - copy(config, "data.username", "spring.datasource.username"); - copy(config, "data.password", "spring.datasource.password"); break; case "sqlite": set("spring.datasource.driver-class-name", "org.sqlite.JDBC"); @@ -101,15 +99,8 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.jpa.database-platform", "org.stellar.anchor.platform.sqlite.SQLiteDialect"); copy(config, "data.ddl_auto", "spring.jpa.hibernate.ddl-auto"); copy(config, "data.url", "spring.datasource.url"); - copy(config, "data.username", "spring.datasource.username"); - copy(config, "data.password", "spring.datasource.password"); - if (config.getString("flyway.enabled", "").equalsIgnoreCase("true")) { - set("spring.flyway.enabled", true); - set("spring.flyway.locations", "classpath:/db/migration"); - copy(config, "data.username", "spring.flyway.user"); - copy(config, "data.password", "spring.flyway.password"); - copy(config, "data.url", "spring.flyway.url"); - } + set("spring.datasource.username", SecretManager.getInstance().get("secret.data.username")); + set("spring.datasource.password", SecretManager.getInstance().get("secret.data.password")); break; case "aurora": set("spring.datasource.driver-class-name", "org.postgresql.Driver"); @@ -118,15 +109,14 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set( "spring.datasource.hikari.max-lifetime", 840000); // 14 minutes because IAM tokens are valid for 15 min - copy(config, "data.ddl_auto", "spring.jpa.hibernate.ddl-auto"); copy(config, "data.url", "spring.datasource.url"); - copy(config, "data.username", "spring.datasource.username"); - copy(config, "data.password", "spring.datasource.password"); - if (config.getString("flyway.enabled", "").equalsIgnoreCase("true")) { + set("spring.datasource.username", SecretManager.getInstance().get("secret.data.username")); + set("spring.datasource.password", SecretManager.getInstance().get("secret.data.password")); + if (config.getString("data.flyway_enabled", "").equalsIgnoreCase("true")) { set("spring.flyway.enabled", true); set("spring.flyway.locations", "classpath:/db/migration"); - copy(config, "data.username", "spring.flyway.user"); - copy(config, "data.password", "spring.flyway.password"); + set("spring.flyway.user", SecretManager.getInstance().get("secret.data.username")); + set("spring.flyway.password", SecretManager.getInstance().get("secret.data.password")); copy(config, "data.url", "spring.flyway.url"); } break; @@ -134,15 +124,14 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.datasource.driver-class-name", "org.postgresql.Driver"); set("spring.datasource.name", "anchor-platform"); set("spring.jpa.database-platform", "org.hibernate.dialect.PostgreSQL9Dialect"); - copy(config, "data.ddl_auto", "spring.jpa.hibernate.ddl-auto"); copy(config, "data.url", "spring.datasource.url"); - copy(config, "data.username", "spring.datasource.username"); - copy(config, "data.password", "spring.datasource.password"); - if (config.getString("flyway.enabled", "").equalsIgnoreCase("true")) { + set("spring.datasource.username", SecretManager.getInstance().get("secret.data.username")); + set("spring.datasource.password", SecretManager.getInstance().get("secret.data.password")); + if (config.getString("data.flyway_enabled", "").equalsIgnoreCase("true")) { set("spring.flyway.enabled", true); set("spring.flyway.locations", "classpath:/db/migration"); - copy(config, "data.username", "spring.flyway.user"); - copy(config, "data.password", "spring.flyway.password"); + set("spring.flyway.user", SecretManager.getInstance().get("secret.data.username")); + set("spring.flyway.password", SecretManager.getInstance().get("secret.data.password")); copy(config, "data.url", "spring.flyway.url"); } break; diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java index a8ad8b6d01..380c3df868 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/SecretManager.java @@ -18,7 +18,11 @@ public class SecretManager "secret.sep10.jwt_secret", "secret.sep10.signing_seed", "secret.callback_api.auth_secret", - "secret.platform_api.auth_secret"); + "secret.platform_api.auth_secret", + "secret.data.username", + "secret.data.password"); + + Properties props = new Properties(); static SecretManager secretManager = new SecretManager(); @@ -31,7 +35,6 @@ public static SecretManager getInstance() { @Override public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { info("Secret manager started."); - Properties props = new Properties(); secretVars.forEach( var -> { String secret = ConfigEnvironment.getenv(var); @@ -43,4 +46,8 @@ public void initialize(@NotNull ConfigurableApplicationContext applicationContex PropertiesPropertySource pps = new PropertiesPropertySource("secret", props); applicationContext.getEnvironment().getPropertySources().addFirst(pps); } + + public String get(String key) { + return props.getProperty(key); + } } diff --git a/platform/src/main/resources/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index e6df364a87..c05975e8c2 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -447,7 +447,7 @@ assets: ################################ data: - ## DB credentials are specified in @environment_variables DATA_USERNAME, DATA_PASSWORD + ## DB credentials are specified in @environment_variables SECRET_DATA_USERNAME, SECRET_DATA_PASSWORD ## @param: type ## @supported_values: @@ -465,19 +465,6 @@ data: # url: jdbc:h2:mem:anchor-platform - ## @param: username - ## @type: string - ## The username for database connection - # - username: - - - ## @param: username - ## @type: string - ## The password for database connection - # - password: - ## @param: initial_connection_pool_size ## @type: integer ## Initial number of connections diff --git a/platform/src/main/resources/config/anchor-config-schema-v1.yaml b/platform/src/main/resources/config/anchor-config-schema-v1.yaml index 194678cad6..e132c6cc4e 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -8,10 +8,8 @@ data.flyway_enabled: data.flyway_location: data.initial_connection_pool_size: data.max_active_connections: -data.password: data.type: data.url: -data.username: events.enabled: events.event_type_to_queue.quote_created: events.event_type_to_queue.transaction_created: @@ -43,10 +41,6 @@ payment_observer.enabled: payment_observer.tracked_wallet: platform_api.auth.expiration_milliseconds: platform_api.auth.type: -secret.callback_api.secret: -secret.jwt_secret: -secret.platform_api.secret: -secret.sep10_signing_seed: sep1.enabled: sep1.toml.type: sep1.toml.value: diff --git a/platform/src/main/resources/example.env b/platform/src/main/resources/example.env index 633ebf0865..78436f57ff 100644 --- a/platform/src/main/resources/example.env +++ b/platform/src/main/resources/example.env @@ -6,23 +6,23 @@ #################################################################################################### # REQUIRED - The secret key of JWT encryption -SECRET.SEP10.JWT_SECRET=secret +SECRET_SEP10_JWT_SECRET=secret # REQUIRED - The private key of the SEP-10 challenge. # We highly recommend that this private key should not be used to sign any transactions to submit to the Stellar # network. -SECRET.SEP10.SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X +SECRET_SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X # REQUIRED - JWT secrets used to communicate between Anchor and Platform. -SECRET.CALLBACK_API.AUTH_SECRET=myPlatformToAnchorSecret -SECRET.PLATFORM_API.AUTH_SECRET=myAnchorToPlatformSecret +SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret +SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret # OPTIONAL (only if using Circle) CIRCLE_API_KEY=QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # OPTIONAL - used for storage definition -DATA_USERNAME=admin -DATA_PASSWORD=admin +SECRET_DATA_USERNAME=admin +SECRET_DATA_PASSWORD=admin # OPTIONAL (only if using AWS credentials) AWS_ACCESS_KEY_ID= From f14c92af7a61aac49424859ec46e53fa67200865 Mon Sep 17 00:00:00 2001 From: Stephen Date: Wed, 26 Oct 2022 16:51:34 -0400 Subject: [PATCH 65/65] update e2e tests with new config format (#604) * update e2e tests with new config format --- ...d to End Testing with Different Configs.md | 1 + integration-tests/docker-compose-configs/.env | 3 +- .../anchor-platform-config.yaml | 132 ----- .../docker-compose-config.override.yaml | 3 +- .../anchor-platform-config.yaml | 473 +++++++++++++----- .../anchor-platform-config.yaml | 133 ----- .../docker-compose-config.override.yaml | 2 + .../docker-compose.base.yaml | 13 +- .../platform/StellarObservingService.java | 2 + .../platform/event/SqsEventPublisher.java | 1 + .../config/anchor-config-default-values.yaml | 3 +- 11 files changed, 370 insertions(+), 396 deletions(-) delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml diff --git a/docs/02 - Contributing/G - End to End Testing with Different Configs.md b/docs/02 - Contributing/G - End to End Testing with Different Configs.md index dbfb67203c..4656333d5a 100644 --- a/docs/02 - Contributing/G - End to End Testing with Different Configs.md +++ b/docs/02 - Contributing/G - End to End Testing with Different Configs.md @@ -92,6 +92,7 @@ directory. ```text E2E_SECRET= OMNIBUS_ALLOWLIST_KEYS= + SECRET_SEP10_SIGNING_SEED= ``` 3) Run the end-to-end test on all the different configs: ```text diff --git a/integration-tests/docker-compose-configs/.env b/integration-tests/docker-compose-configs/.env index a2efdd1ebd..53882b63ea 100644 --- a/integration-tests/docker-compose-configs/.env +++ b/integration-tests/docker-compose-configs/.env @@ -1,2 +1,3 @@ E2E_SECRET= -OMNIBUS_ALLOWLIST_KEYS= \ No newline at end of file +OMNIBUS_ALLOWLIST_KEYS= +SECRET_SEP10_SIGNING_SEED= \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml deleted file mode 100644 index 17049e7ad6..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-allowlist/anchor-platform-config.yaml +++ /dev/null @@ -1,132 +0,0 @@ - -stellar: - anchor: - config: in-memory - app-config: - type: config-spring-property - settings: app-config # The absolute location of the configuration data in this yaml file - - data-access: - type: data-spring-jdbc - settings: data-spring-jdbc-local-postgres # use local postgres instance - - logging: - type: logging-logback - settings: logging-logback-settings # The absolute location of the configuration data in this yaml file - -app-config: - app: - stellarNetworkPassphrase: Test SDF Network ; September 2015 - horizonUrl: https://horizon-testnet.stellar.org - hostUrl: http://host.docker.internal:8080 - languages: en - assets: file:config/assets-test.json - - jwtSecretKey: secret - - integration-auth: - authType: JWT_TOKEN - platformToAnchorSecret: myPlatformToAnchorSecret - anchorToPlatformSecret: myAnchorToPlatformSecret - expirationMilliseconds: 30000 - - sep1: - enabled: true - stellarFile: file:config/stellar.toml - - sep10: - enabled: true - homeDomain: host.docker.internal:8080 - clientAttributionRequired: false - clientAttributionAllowList: lobstr.co,preview.lobstr.co - authTimeout: 900 - jwtTimeout: 86400 - - signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - omnibusAccountList: ${OMNIBUS_ALLOWLIST_KEYS} - - # leaving this here for future testing - # omnibusAccountList: - # - GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAV - # - GAB24L6CPBKSMQOBXJ5PRA6I66X4TGZHPAEI2CMPNLC6I2WX646OYCAA - - requireKnownOmnibusAccount: true - - sep12: - enabled: true - customerIntegrationEndpoint: http://host.docker.internal:8081 - - sep24: - enabled: true - interactiveJwtExpiration: 3600 - - interactiveUrl: http://host.docker.internal:8081/sep24/interactive - - sep31: - enabled: true - feeIntegrationEndPoint: http://host.docker.internal:8081 - uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 - paymentType: STRICT_SEND - depositInfoGeneratorType: self # self or circle - - sep38: - enabled: true - quoteIntegrationEndPoint: http://host.docker.internal:8081 - - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # circle API key - - payment-gateway: - circle: - name: "circle" - enabled: true - - stellar: - enabled: false - name: "stellar" - horizonUrl: https://horizon-testnet.stellar.org - secretKey: secret # stellar account secret key - - circle-payment-observer: - enabled: true - horizonUrl: https://horizon-testnet.stellar.org - stellarNetwork: TESTNET # TESTNET or PUBLIC - trackedWallet: all - - event: - enabled: true - publisherType: kafka - - kafka.publisher: - bootstrapServer: host.docker.internal:29092 - useSingleQueue: false - eventTypeToQueue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error - -data-spring-jdbc-local-postgres: - spring.jpa.database: POSTGRESQL - spring.jpa.show-sql: false - spring.datasource.driver-class-name: org.postgresql.Driver - spring.datasource.url: jdbc:postgresql://host.docker.internal:5440/ - spring.datasource.username: postgres - spring.datasource.password: password - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.user: postgres - spring.flyway.password: password - spring.flyway.url: jdbc:postgresql://host.docker.internal:5440/ - spring.flyway.locations: classpath:/db/migration - -spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 diff --git a/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml index dbf1e1f46b..2fd8d7dbdb 100644 --- a/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml +++ b/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml @@ -6,7 +6,8 @@ services: # add mounts for the new config directory - ./anchor-platform-allowlist:/config_override environment: - - OMNIBUS_ALLOWLIST_KEYS=${OMNIBUS_ALLOWLIST_KEYS?err} + - SEP10_OMNIBUS_ACCOUNT_LIST=${OMNIBUS_ALLOWLIST_KEYS?err} + - SEP10_REQUIRE_KNOWN_OMNIBUS_ACCOUNT=true ports: # override ports, do not append - "8080:8080" diff --git a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml index a9dbe71f86..5766de8e98 100644 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml +++ b/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml @@ -1,128 +1,347 @@ +version: 1 -stellar: - anchor: - config: in-memory - app-config: - type: config-spring-property - settings: app-config # The absolute location of the configuration data in this yaml file - - data-access: - type: data-spring-jdbc - settings: data-spring-jdbc-local-postgres # use local postgres instance - - logging: - type: logging-logback - settings: logging-logback-settings # The absolute location of the configuration data in this yaml file - -app-config: - app: - stellarNetworkPassphrase: Test SDF Network ; September 2015 - horizonUrl: https://horizon-testnet.stellar.org - hostUrl: http://host.docker.internal:8080 - languages: en - assets: file:config/assets-test.json - - jwtSecretKey: secret - - integration-auth: - authType: JWT_TOKEN - platformToAnchorSecret: myPlatformToAnchorSecret - anchorToPlatformSecret: myAnchorToPlatformSecret - expirationMilliseconds: 30000 - - sep1: - enabled: true - stellarFile: file:config/stellar.toml - - sep10: - enabled: true - homeDomain: host.docker.internal:8080 - clientAttributionRequired: false - clientAttributionAllowList: lobstr.co,preview.lobstr.co - authTimeout: 900 - jwtTimeout: 86400 - - signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - - omnibusAccountList: - - requireKnownOmnibusAccount: false - - sep12: - enabled: true - customerIntegrationEndpoint: http://host.docker.internal:8081 - - sep24: - enabled: true - interactiveJwtExpiration: 3600 - - interactiveUrl: http://host.docker.internal:8081/sep24/interactive - - sep31: - enabled: true - feeIntegrationEndPoint: http://host.docker.internal:8081 - uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 - paymentType: STRICT_SEND - depositInfoGeneratorType: self # self or circle - - sep38: - enabled: true - quoteIntegrationEndPoint: http://host.docker.internal:8081 - - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # circle API key - - payment-gateway: - circle: - name: "circle" - enabled: true - - stellar: - enabled: false - name: "stellar" - horizonUrl: https://horizon-testnet.stellar.org - secretKey: secret # stellar account secret key - - circle-payment-observer: - enabled: true - horizonUrl: https://horizon-testnet.stellar.org - stellarNetwork: TESTNET # TESTNET or PUBLIC - trackedWallet: all - - event: - enabled: true - publisherType: kafka - - kafka.publisher: - bootstrapServer: host.docker.internal:29092 - useSingleQueue: false - eventTypeToQueue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error - -data-spring-jdbc-local-postgres: - spring.jpa.database: POSTGRESQL - spring.jpa.show-sql: false - spring.datasource.driver-class-name: org.postgresql.Driver - spring.datasource.url: jdbc:postgresql://host.docker.internal:5440/ - spring.datasource.username: postgres - spring.datasource.password: password - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.user: postgres - spring.flyway.password: password - spring.flyway.url: jdbc:postgresql://host.docker.internal:5440/ - spring.flyway.locations: classpath:/db/migration - -spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 +logging: + level: INFO + stellar_level: DEBUG + +callback_api: + base_url: http://host.docker.internal:8081 + auth: + type: JWT_TOKEN + expiration_milliseconds: 30000 + +platform_api: + auth: + type: JWT_TOKEN + expiration_milliseconds: 30000 + +events: + enabled: true + publisher: + type: kafka + kafka: + bootstrap_server: host.docker.internal:29092 + +sep1: + enabled: true + toml: + type: string + value: | + ACCOUNTS = [] + VERSION = "0.1.0" + NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" + SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" + + WEB_AUTH_ENDPOINT = "http://host.docker.internal:8080/auth" + KYC_SERVER = "http://host.docker.internal:8080/sep12" + TRANSFER_SERVER_SEP0024 = "http://host.docker.internal:8080/sep24" + DIRECT_PAYMENT_SERVER = "http://host.docker.internal:8080/sep31" + ANCHOR_QUOTE_SERVER = "http://host.docker.internal:8080/sep38" + + [[CURRENCIES]] + code = "USDC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + status = "test" + is_asset_anchored = true + anchor_asset_type = "fiat" + desc = "A test USDC issued by Stellar." + + [[CURRENCIES]] + code = "JPYC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + status = "test" + is_asset_anchored = true + anchor_asset_type = "fiat" + desc = "A test JPYC issued by Stellar." + +sep10: + enabled: true + home_domain: host.docker.internal:8080 + +sep12: + enabled: true + +sep24: + enabled: true + interactiveUrl: http://host.docker.internal:8081/sep24/interactive + +sep31: + enabled: true + +sep38: + enabled: true + +assets: + type: json + value: | + { + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 2, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving USD" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving USD" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "stellar", + "code": "JPYC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 4, + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep31" : { + "quotes_supported": true, + "quotes_required": false, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "U.S. citizens limited to sending payments of less than $10,000 in value" + }, + "sep31-large-sender": { + "description": "U.S. citizens that do not have sending limits" + }, + "sep31-foreign-sender": { + "description": "non-U.S. citizens sending payments of less than $10,000 in value" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "U.S. citizens receiving JPY" + }, + "sep31-foreign-receiver": { + "description": "non-U.S. citizens receiving JPY" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account" + }, + "receiver_account_number": { + "description": "bank account number of the destination" + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ] + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "iso4217:USD" + ] + }, + "sep24_enabled": false, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "schema": "iso4217", + "code": "USD", + "deposit" : { + "enabled": true, + "fee_minimum": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "sep38": { + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "country_codes": ["USA"], + "decimals": 4, + "sell_delivery_methods": [ + { + "name": "WIRE", + "description": "Send USD directly to the Anchor's bank account." + } + ], + "buy_delivery_methods": [ + { + "name": "WIRE", + "description": "Have USD sent directly to your bank account." + } + ] + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38_enabled": true + } + ] + } + +################################ +## Data Configuration +################################ +data: + + ## DB credentials are specified in @environment_variables SECRET_DATA_USERNAME, SECRET_DATA_PASSWORD + + ## @param: type + ## @supported_values: + ## `h2` (in-memory), `sqlite` (local), `postgres` (local), `aurora` (postgres on AWS) + ## Type of storage. + ## If this is set to `aurora`, + ## @required_secrets: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION + # + type: postgres + + ## @param: url + ## @type: string + ## Location of the database + # + url: jdbc:postgresql://host.docker.internal:5440/ + + ## @param: initial_connection_pool_size + ## @type: integer + ## Initial number of connections + ## For `sqlite`, set this to 1 to avoid database file lock exception + # + initial_connection_pool_size: 1 + + ## @param: max_active_connections + ## @type: integer + ## Maximum number of db active connections + ## For `sqlite`, set this to 1 to avoid database file lock exception + # + max_active_connections: 10 + + ## @param: flyway_enabled + ## @type: bool + ## Whether to enable flyway. + ## Should be disabled for `sqlite` because certain features that flyway uses + ## (ex: addForeignKeyConstraint) are not supported. + # + flyway_enabled: true + + ## @param: flyway_enabled + ## @type: string + ## Location on disk where migrations are stored if flyway is enabled. + # + flyway_location: /db/migration + + ## @param: hikari_max_lifetime + ## @type: integer + ## Maximum connection time before expiration + ## Only valid when database `type` is `aurora`. + ## Recommended setting is 14 minutes because IAM tokens are valid for 15 min. + # + # hikari_max_lifetime: 840000 diff --git a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml b/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml deleted file mode 100644 index 47b1acf039..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-platform-config.yaml +++ /dev/null @@ -1,133 +0,0 @@ - -stellar: - anchor: - config: in-memory - app-config: - type: config-spring-property - settings: app-config # The absolute location of the configuration data in this yaml file - - data-access: - type: data-spring-jdbc - settings: data-spring-jdbc-local-postgres # use local postgres instance - - logging: - type: logging-logback - settings: logging-logback-settings # The absolute location of the configuration data in this yaml file - -# ************************************ -# Application settings -# ************************************ -app-config: - # shared / general application configurations - app: - stellarNetworkPassphrase: Test SDF Network ; September 2015 - horizonUrl: https://horizon-testnet.stellar.org - hostUrl: http://host.docker.internal:8080 - languages: en - assets: file:config/assets-test.json - - jwtSecretKey: secret - - integration-auth: - authType: JWT_TOKEN - platformToAnchorSecret: myPlatformToAnchorSecret - anchorToPlatformSecret: myAnchorToPlatformSecret - expirationMilliseconds: 30000 - - sep1: - enabled: true - stellarFile: file:config/stellar.toml - - # sep-10 - sep10: - enabled: true - homeDomain: host.docker.internal:8080 - clientAttributionRequired: false - clientAttributionAllowList: lobstr.co,preview.lobstr.co - authTimeout: 900 - jwtTimeout: 86400 - - signingSeed: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - - omnibusAccountList: - - requireKnownOmnibusAccount: false - - sep12: - enabled: true - customerIntegrationEndpoint: http://host.docker.internal:8081 - - sep24: - enabled: true - interactiveJwtExpiration: 3600 - - interactiveUrl: http://host.docker.internal:8081/sep24/interactive - - sep31: - enabled: true - feeIntegrationEndPoint: http://host.docker.internal:8081 - uniqueAddressIntegrationEndPoint: http://host.docker.internal:8081 - paymentType: STRICT_SEND - depositInfoGeneratorType: api # self or circle or api - - sep38: - enabled: true - quoteIntegrationEndPoint: http://host.docker.internal:8081 - - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # circle API key - - payment-gateway: - circle: - name: "circle" - enabled: true - - stellar: - enabled: false - name: "stellar" - horizonUrl: https://horizon-testnet.stellar.org - secretKey: secret # stellar account secret key - - circle-payment-observer: - enabled: true - horizonUrl: https://horizon-testnet.stellar.org - stellarNetwork: TESTNET # TESTNET or PUBLIC - trackedWallet: all - - event: - enabled: true - publisherType: kafka - - kafka.publisher: - bootstrapServer: host.docker.internal:29092 - useSingleQueue: false - eventTypeToQueue: - all: ap_event_single_queue - quote_created: ap_event_quote_created - transaction_created: ap_event_transaction_created - transaction_status_changed: ap_event_transaction_status_changed - transaction_error: ap_event_transaction_error - -data-spring-jdbc-local-postgres: - spring.jpa.database: POSTGRESQL - spring.jpa.show-sql: false - spring.datasource.driver-class-name: org.postgresql.Driver - spring.datasource.url: jdbc:postgresql://host.docker.internal:5440/ - spring.datasource.username: postgres - spring.datasource.password: password - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.user: postgres - spring.flyway.password: password - spring.flyway.url: jdbc:postgresql://host.docker.internal:5440/ - spring.flyway.locations: classpath:/db/migration - -spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 diff --git a/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml index cc6aee7cbe..c3b172ea7a 100644 --- a/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml +++ b/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml @@ -11,6 +11,8 @@ services: ports: # override ports, do not append - "8080:8080" + environment: + - SEP31_DEPOSIT_INFO_GENERATOR_TYPE=api anchor-reference-server: volumes: diff --git a/integration-tests/docker-compose-configs/docker-compose.base.yaml b/integration-tests/docker-compose-configs/docker-compose.base.yaml index 86cc242983..666fbd322c 100644 --- a/integration-tests/docker-compose-configs/docker-compose.base.yaml +++ b/integration-tests/docker-compose-configs/docker-compose.base.yaml @@ -8,6 +8,12 @@ services: command: "--sep-server" environment: - STELLAR_ANCHOR_CONFIG=file:/config/anchor-platform-config.yaml + - SECRET_SEP10_SIGNING_SEED=${SECRET_SEP10_SIGNING_SEED?err} + - SECRET_SEP10_JWT_SECRET=secret + - SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret + - SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret + - SECRET_DATA_USERNAME=postgres + - SECRET_DATA_PASSWORD=password - LOG_APPENDER=console_appender volumes: # add mounts for the new config directory @@ -17,7 +23,6 @@ services: depends_on: - kafka - db - - stellar-observer extra_hosts: - "host.docker.internal:host-gateway" @@ -29,6 +34,12 @@ services: command: "--stellar-observer" environment: - STELLAR_ANCHOR_CONFIG=file:/config/anchor-platform-config.yaml + - SECRET_SEP10_SIGNING_SEED=${SECRET_SEP10_SIGNING_SEED?err} + - SECRET_SEP10_JWT_SECRET=secret + - SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret + - SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret + - SECRET_DATA_USERNAME=postgres + - SECRET_DATA_PASSWORD=password - LOG_APPENDER=console_appender volumes: # add mounts for the new config directory diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java index f17d70cc32..8b5d740c15 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java @@ -13,6 +13,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.stellar.anchor.platform.configurator.ConfigManager; +import org.stellar.anchor.platform.configurator.SecretManager; @Profile("stellar-observer") @SpringBootApplication @@ -42,6 +43,7 @@ public static ConfigurableApplicationContext start( SpringApplication springApplication = builder.build(); + springApplication.addInitializers(SecretManager.getInstance()); springApplication.addInitializers(ConfigManager.getInstance()); return springApplication.run(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java index f138d70f4d..4c4b51fa72 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java +++ b/platform/src/main/java/org/stellar/anchor/platform/event/SqsEventPublisher.java @@ -41,6 +41,7 @@ public void publish(String queue, AnchorEvent event) throws EventPublishExceptio new MessageAttributeValue() .withDataType("String") .withStringValue(event.getClass().getSimpleName())); + SendMessageResult sendMessageResult = sqsClient.sendMessage(sendMessageRequest); // If the queue is offline, throw an exception diff --git a/platform/src/main/resources/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index c05975e8c2..9b580d3e35 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -87,7 +87,8 @@ platform_api: ## JWT_TOKEN: The authentication is done using a JWT token added to the `Authorization` ## header. This token is generated from the secret key. ## - ## If the type is not NONE, the secret must be set by the following environment variable: `secret.platform_api.auth_secret` + ## If the type is not NONE, the secret must be set by the following environment variables: + ## `secret.platform_api.auth_secret`, `secret.callback_api.auth_secret` ## ## The value of the secret depends on the type specified. ## If type is JWT_TOKEN – the secret key used to encode/decode the JWT token.