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 0001/1439] 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 0002/1439] 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 0003/1439] 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 0004/1439] 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 0005/1439] 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 0006/1439] 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 0007/1439] 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 0008/1439] 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 0009/1439] 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 0010/1439] 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 0011/1439] 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 0012/1439] 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 0013/1439] 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 0014/1439] 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 0015/1439] 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 0016/1439] 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 0017/1439] 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 0018/1439] 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 0019/1439] 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 0020/1439] 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 0021/1439] 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 0022/1439] 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 0023/1439] 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 0024/1439] 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 0025/1439] 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 0026/1439] 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 0027/1439] 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 0028/1439] 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 0029/1439] 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 0030/1439] 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 0031/1439] 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 0032/1439] 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 0033/1439] 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 0034/1439] 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 0035/1439] 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 0036/1439] 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 0037/1439] 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 0038/1439] 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 0039/1439] 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 0040/1439] 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 0041/1439] 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 0042/1439] 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. From a62aec9ecec2d20a01a13b3275f18000a9ca27af Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 27 Oct 2022 16:25:25 -0700 Subject: [PATCH 0043/1439] observer: Merge 1.2.2 to `develop` (#641) * merge 1.2.2 to develop --- CHANGELOG.md | 4 + .../anchor/reference/event/KafkaListener.java | 6 +- .../api/platform/HealthCheckResult.java | 4 +- build.gradle.kts | 2 +- .../healthcheck/HealthCheckProcessor.java | 2 +- .../anchor/util/ExponentialBackoffTimer.java | 8 + .../docker-examples/kafka/docker-compose.yaml | 2 +- .../platform/AnchorPlatformIntegrationTest.kt | 27 +- .../platform/configurator/ConfigManager.java | 7 +- .../platform/controller/HealthController.java | 17 +- .../stellar/StellarPaymentObserver.java | 422 +++++++++++++----- .../platform/service/HealthCheckService.java | 9 +- .../controller/HealthControllerTest.kt | 73 +++ .../stellar/StellarPaymentObserverTest.kt | 21 +- 14 files changed, 463 insertions(+), 141 deletions(-) create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0578f57b60..afbe59e3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.2 +* Detects and handle silent and errored SSEStream. [#632](https://github.com/stellar/java-stellar-anchor-sdk/issues/632) +* When the health status is RED, set the status code to 500. + ## 1.2.1 * Fix gson serialization error of the Refunds object. [#626](https://github.com/stellar/java-stellar-anchor-sdk/issues/626) 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 46ec30d9f8..564836a4be 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 @@ -143,7 +143,7 @@ public HealthCheckResult check() { return KafkaHealthCheckResult.builder() .name(getName()) - .status(status.getName()) + .status(status) .kafkaAvailable(kafkaAvailable) .running(!executor.isTerminated()) .build(); @@ -164,9 +164,9 @@ boolean validateKafka() { class KafkaHealthCheckResult implements HealthCheckResult { transient String name; - List statuses; + List statuses = List.of(GREEN, RED); - String status; + HealthCheckStatus status; boolean running; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResult.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResult.java index d22ca3717b..747b1916ba 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResult.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckResult.java @@ -10,12 +10,12 @@ public interface HealthCheckResult { * * @return the list of health check status. */ - List getStatuses(); + List getStatuses(); /** * the current status of the health check result. * * @return the status. */ - String getStatus(); + HealthCheckStatus getStatus(); } diff --git a/build.gradle.kts b/build.gradle.kts index 1d1f9d5b03..375be7ca3a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -119,7 +119,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "1.2.1" + version = "1.2.2" tasks.jar { manifest { 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 a5a9c20778..8d457d04d0 100644 --- a/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckProcessor.java +++ b/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckProcessor.java @@ -21,7 +21,7 @@ public HealthCheckResponse check(List checkTags) { HealthCheckResponse healthCheckResponse = new HealthCheckResponse(); SortedSet checkSet = new TreeSet<>(); for (String checkTag : checkTags) { - List checkables = mapCheckable.get(checkTag); + List checkables = mapCheckable.get(checkTag.toLowerCase()); if (checkables != null) checkSet.addAll(checkables); } diff --git a/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java b/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java index ebf0059361..fccedc780e 100644 --- a/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java +++ b/core/src/main/java/org/stellar/anchor/util/ExponentialBackoffTimer.java @@ -42,4 +42,12 @@ public void reset() { public void sleep() throws InterruptedException { Thread.sleep(sleepSeconds * 1000); } + + public long currentTimer() { + return sleepSeconds; + } + + public boolean isTimerMaxed() { + return sleepSeconds >= maxSleepSeconds; + } } diff --git a/docs/resources/docker-examples/kafka/docker-compose.yaml b/docs/resources/docker-examples/kafka/docker-compose.yaml index b01b3f3238..5b26dfe082 100644 --- a/docs/resources/docker-examples/kafka/docker-compose.yaml +++ b/docs/resources/docker-examples/kafka/docker-compose.yaml @@ -1,4 +1,4 @@ -version: '2' +version: '2.4' services: zookeeper: platform: linux/x86_64 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 c2d8575f57..d30a48f453 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 @@ -178,7 +178,8 @@ class AnchorPlatformIntegrationTest { "sell_amount": "100", "buy_amount": "98.0392" } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(result), true) } @@ -214,14 +215,16 @@ class AnchorPlatformIntegrationTest { ] } } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(result), true) } @Test fun testRate_firm() { val rate = - rriClient.getRate( + rriClient + .getRate( GetRateRequest.builder() .type(FIRM) .context(SEP31) @@ -277,7 +280,8 @@ class AnchorPlatformIntegrationTest { ] } } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(gotQuote), true) } @@ -371,8 +375,23 @@ class AnchorPlatformIntegrationTest { assertNotNull(responseBody["checks"]) val checks = responseBody["checks"] as Map<*, *> + assertEquals(2, checks.size) assertNotNull(checks["config"]) assertNotNull(checks["stellar_payment_observer"]) + + val stellarPaymentObserverCheck = checks["stellar_payment_observer"] as Map<*, *> + assertEquals(2, stellarPaymentObserverCheck.size) + assertEquals("GREEN", stellarPaymentObserverCheck["status"]) + + val observerStreams = stellarPaymentObserverCheck["streams"] as List<*> + assertEquals(1, observerStreams.size) + + val stream1 = observerStreams[0] as Map<*, *> + assertEquals(5, stream1.size) + assertEquals(false, stream1["thread_shutdown"]) + assertEquals(false, stream1["thread_terminated"]) + assertEquals(false, stream1["stopped"]) + assertNotNull(stream1["last_event_id"]) } } 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 index eba0d6c188..1b8edebfa9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java @@ -19,6 +19,7 @@ import org.springframework.core.io.Resource; import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.api.platform.HealthCheckResult; +import org.stellar.anchor.api.platform.HealthCheckStatus; import org.stellar.anchor.healthcheck.HealthCheckable; import org.stellar.anchor.util.Log; @@ -184,12 +185,12 @@ public String name() { } @Override - public List getStatuses() { + public List getStatuses() { return List.of(); } @Override - public String getStatus() { - return GREEN.toString(); + public HealthCheckStatus getStatus() { + return GREEN; } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java index 57fedf862a..bf28bc962f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java @@ -1,8 +1,11 @@ package org.stellar.anchor.platform.controller; import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.stellar.anchor.api.platform.HealthCheckResponse; +import org.stellar.anchor.api.platform.HealthCheckStatus; import org.stellar.anchor.platform.service.HealthCheckService; @RestController @@ -16,10 +19,20 @@ public class HealthController { } @RequestMapping(method = {RequestMethod.GET}) - public HealthCheckResponse health(@RequestParam(required = false) List checks) { + public ResponseEntity health( + @RequestParam(required = false) List checks) { if (checks == null) { checks = List.of("all"); } - return healthCheckService.check(checks); + HealthCheckResponse healthCheckResponse = healthCheckService.check(checks); + + boolean unhealthy = + healthCheckResponse.getChecks().values().stream() + .anyMatch(result -> result.getStatus() == HealthCheckStatus.RED); + if (unhealthy) { + return new ResponseEntity<>(healthCheckResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } else { + return ResponseEntity.ok(healthCheckResponse); + } } } 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 456caf8387..4c0c602601 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 @@ -1,15 +1,22 @@ 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; +import static org.stellar.anchor.api.platform.HealthCheckStatus.*; import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.ALL; import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.EVENT; +import static org.stellar.anchor.platform.payment.observer.stellar.ObserverStatus.*; +import static org.stellar.anchor.util.Log.debugF; +import static org.stellar.anchor.util.Log.infoF; import static org.stellar.anchor.util.ReflectionUtil.getField; import com.google.gson.annotations.SerializedName; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.*; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import lombok.Builder; @@ -42,49 +49,198 @@ public class StellarPaymentObserver implements HealthCheckable { /** The minimum number of results the Stellar Blockchain can return. */ private static final int MIN_RESULTS = 1; + // If the observer had been silent for longer than SILENC_TIMEOUT, a SilenceTimeoutException will + // be thrown to trigger reconnections. + private static final long SILENCE_TIMEOUT = 90; + // If the observer has more than 2 SILENCE_TIMEOUT_RETRIES, it will be marked unhealthy + private static final long SILENCE_TIMEOUT_RETRIES = 2; + + // The time interval between silence checks + private static final long SILENCE_CHECK_INTERVAL = 5; + final Server server; - final Set observers; + final Set paymentListeners; final StellarPaymentStreamerCursorStore paymentStreamerCursorStore; final Map, String> mapStreamToAccount = new HashMap<>(); final PaymentObservingAccountsManager paymentObservingAccountsManager; SSEStream stream; - private final ExponentialBackoffTimer exponentialBackoffTimer = new ExponentialBackoffTimer(); + final ExponentialBackoffTimer publishingBackoffTimer = new ExponentialBackoffTimer(); + final ExponentialBackoffTimer streamBackoffTimer = new ExponentialBackoffTimer(); + int silenceTimeoutCount = 0; + + ObserverStatus status = RUNNING; + + Instant lastActivityTime; + + ScheduledExecutorService silenceWatcher = Executors.newSingleThreadScheduledExecutor(); + ScheduledExecutorService statusWatcher = Executors.newSingleThreadScheduledExecutor(); StellarPaymentObserver( String horizonServer, - Set observers, + Set paymentListeners, PaymentObservingAccountsManager paymentObservingAccountsManager, StellarPaymentStreamerCursorStore paymentStreamerCursorStore) { this.server = new Server(horizonServer); - this.observers = observers; + this.paymentListeners = paymentListeners; this.paymentObservingAccountsManager = paymentObservingAccountsManager; this.paymentStreamerCursorStore = paymentStreamerCursorStore; } - /** Start watching the accounts. */ + /** Start the observer. */ public void start() { - this.stream = watch(); + infoF("Starting the SSEStream"); + startStream(); + + infoF("Starting the observer silence watcher"); + silenceWatcher.scheduleAtFixedRate( + this::checkSilence, + 1, + SILENCE_CHECK_INTERVAL, + TimeUnit.SECONDS); // TODO: The period should be made configurable in version 2.x + + infoF("Starting the status watcher"); + statusWatcher.scheduleWithFixedDelay(this::checkStatus, 1, 1, TimeUnit.SECONDS); + + setStatus(RUNNING); } - /** Graceful shutdown. */ + /** Graceful shut down the observer */ public void shutdown() { - this.stream.close(); - this.stream = null; + infoF("Shutting down the SSEStream"); + stopStream(); + + infoF("Stopping the silence watcher"); + silenceWatcher.shutdown(); + + infoF("Stopping the status watcher"); + statusWatcher.shutdown(); + setStatus(SHUTDOWN); + } + + void startStream() { + this.stream = startSSEStream(); + } + + SSEStream startSSEStream() { + String latestCursor = fetchStreamingCursor(); + debugF("SSEStream last cursor={}", latestCursor); + + PaymentsRequestBuilder paymentsRequest = + server + .payments() + .includeTransactions(true) + .cursor(latestCursor) + .order(RequestBuilder.Order.ASC) + .limit(MAX_RESULTS); + return paymentsRequest.stream( + new EventListener<>() { + @Override + public void onEvent(OperationResponse operationResponse) { + debugF("received event {}", operationResponse.getId()); + // clear stream timeout/reconnect status + lastActivityTime = Instant.now(); + silenceTimeoutCount = 0; + streamBackoffTimer.reset(); + handleEvent(operationResponse); + } + + @Override + public void onFailure(Optional exception, Optional statusCode) { + handleFailure(exception); + } + }); } - private void restart() { - Log.info("Restarting the Stellar observer."); + void stopStream() { if (this.stream != null) { - this.shutdown(); + this.stream.close(); + this.stream = null; } + } - try { - exponentialBackoffTimer.sleep(); - exponentialBackoffTimer.increase(); - this.start(); - } catch (InterruptedException ex) { - Log.errorEx(ex); + void checkSilence() { + if (status != NEEDS_SHUTDOWN && status != SHUTDOWN) { + Instant now = Instant.now(); + if (lastActivityTime != null) { + Duration silenceDuration = Duration.between(lastActivityTime, now); + if (silenceDuration.getSeconds() > SILENCE_TIMEOUT) { + infoF("The observer had been silent for {} seconds.", silenceDuration.getSeconds()); + setStatus(SILENCE_ERROR); + } else { + debugF("The observer had been silent for {} seconds.", silenceDuration.getSeconds()); + } + } + } + } + + void restartStream() { + infoF("Restarting the stream"); + stopStream(); + startStream(); + setStatus(RUNNING); + } + + void checkStatus() { + switch (status) { + case NEEDS_SHUTDOWN: + infoF("shut down the observer"); + shutdown(); + break; + case STREAM_ERROR: + // We got stream connection error. We will use the backoff timer to reconnect. + // If the backoff timer reaches max, we will shut down the observer + if (streamBackoffTimer.isTimerMaxed()) { + infoF("The streamer backoff timer is maxed. Shutdown the observer"); + setStatus(NEEDS_SHUTDOWN); + } else { + try { + infoF( + "The streamer needs restart. Start backoff timer: {} seconds", + streamBackoffTimer.currentTimer()); + streamBackoffTimer.sleep(); + streamBackoffTimer.increase(); + restartStream(); + } catch (InterruptedException e) { + // if this thread is interrupted, we are shutting down the status watcher. + infoF("The status watcher is interrupted. Shutdown the observer"); + setStatus(NEEDS_SHUTDOWN); + } + } + break; + case SILENCE_ERROR: + infoF("The silence reconnection count: {}", silenceTimeoutCount); + // We got the silence error. If silence reconnect too many times, we will shut down the + // observer. + if (silenceTimeoutCount >= SILENCE_TIMEOUT_RETRIES) { + infoF( + "The silence error has happened for too many times:{}. Shutdown the observer", + silenceTimeoutCount); + setStatus(NEEDS_SHUTDOWN); + } else { + restartStream(); + lastActivityTime = Instant.now(); + silenceTimeoutCount++; + } + break; + case PUBLISHER_ERROR: + try { + infoF( + "Start the publishing backoff timer: {} seconds", + publishingBackoffTimer.currentTimer()); + publishingBackoffTimer.sleep(); + publishingBackoffTimer.increase(); + restartStream(); + } catch (InterruptedException e) { + // if this thread is interrupted, we are shutting down the status watcher. + setStatus(NEEDS_SHUTDOWN); + } + break; + case RUNNING: + case SHUTDOWN: + default: + // NOOP + break; } } @@ -120,85 +276,74 @@ String fetchStreamingCursor() { return pageOpResponse.getRecords().get(0).getPagingToken(); } - SSEStream watch() { - String latestCursor = fetchStreamingCursor(); - PaymentsRequestBuilder paymentsRequest = - server - .payments() - .includeTransactions(true) - .cursor(latestCursor) - .order(RequestBuilder.Order.ASC) - .limit(MAX_RESULTS); + void handleEvent(OperationResponse operationResponse) { + if (!operationResponse.isTransactionSuccessful()) { + paymentStreamerCursorStore.save(operationResponse.getPagingToken()); + return; + } - return paymentsRequest.stream( - new EventListener<>() { - @Override - public void onEvent(OperationResponse operationResponse) { - if (!operationResponse.isTransactionSuccessful()) { - paymentStreamerCursorStore.save(operationResponse.getPagingToken()); - return; - } - - ObservedPayment observedPayment = null; - try { - if (operationResponse instanceof PaymentOperationResponse) { - PaymentOperationResponse payment = (PaymentOperationResponse) operationResponse; - observedPayment = ObservedPayment.fromPaymentOperationResponse(payment); - } else if (operationResponse instanceof PathPaymentBaseOperationResponse) { - PathPaymentBaseOperationResponse pathPayment = - (PathPaymentBaseOperationResponse) operationResponse; - observedPayment = ObservedPayment.fromPathPaymentOperationResponse(pathPayment); - } - } catch (SepException ex) { - Log.warn( - String.format( - "Payment of id %s contains unsupported memo %s.", - operationResponse.getId(), - operationResponse.getTransaction().get().getMemo().toString())); - Log.warnEx(ex); - } - - if (observedPayment != null) { - try { - if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getTo())) { - 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("Something went wrong in the streamer", t); - if (!Thread.interrupted()) { - restart(); - } - return; - } - - exponentialBackoffTimer.reset(); - } - - paymentStreamerCursorStore.save(operationResponse.getPagingToken()); - } + ObservedPayment observedPayment = null; + try { + if (operationResponse instanceof PaymentOperationResponse) { + PaymentOperationResponse payment = (PaymentOperationResponse) operationResponse; + observedPayment = ObservedPayment.fromPaymentOperationResponse(payment); + } else if (operationResponse instanceof PathPaymentBaseOperationResponse) { + PathPaymentBaseOperationResponse pathPayment = + (PathPaymentBaseOperationResponse) operationResponse; + observedPayment = ObservedPayment.fromPathPaymentOperationResponse(pathPayment); + } + } catch (SepException ex) { + Log.warn( + String.format( + "Payment of id %s contains unsupported memo %s.", + operationResponse.getId(), + operationResponse.getTransaction().get().getMemo().toString())); + Log.warnEx(ex); + } - @Override - public void onFailure(Optional exception, Optional statusCode) { - Log.errorEx("stellar payment observer error: ", exception.get()); - // TODO: The stream seems closed when failure happens. Improve the reliability of the - // stream. + if (observedPayment == null) { + paymentStreamerCursorStore.save(operationResponse.getPagingToken()); + } else { + try { + if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getTo())) { + for (PaymentListener listener : paymentListeners) { + listener.onReceived(observedPayment); } - }); + } + + if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getFrom()) + && !observedPayment.getTo().equals(observedPayment.getFrom())) { + final ObservedPayment finalObservedPayment = observedPayment; + paymentListeners.forEach(observer -> observer.onSent(finalObservedPayment)); + } + + publishingBackoffTimer.reset(); + paymentStreamerCursorStore.save(operationResponse.getPagingToken()); + } 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 payment listeners.", ex); + setStatus(PUBLISHER_ERROR); + } catch (Throwable t) { + Log.errorEx("Something went wrong in the observer while sending the event", t); + setStatus(PUBLISHER_ERROR); + } + } + } + + void handleFailure(Optional exception) { + // The SSEStreamer has internal errors. We will give up and let the container + // manager to + // restart. + Log.errorEx("stellar payment observer stream error: ", exception.get()); + + // Mark the observer unhealthy + setStatus(STREAM_ERROR); + } + + void setStatus(ObserverStatus status) { + debugF("Setting status to {}", status); + this.status = status; } public static Builder builder() { @@ -260,40 +405,65 @@ public List getTags() { @Override public HealthCheckResult check() { List results = new ArrayList<>(); - HealthCheckStatus status = GREEN; + + HealthCheckStatus status; + switch (this.status) { + case STREAM_ERROR: + case SILENCE_ERROR: + case PUBLISHER_ERROR: + status = YELLOW; + break; + case NEEDS_SHUTDOWN: + case SHUTDOWN: + status = RED; + break; + case RUNNING: + default: + status = GREEN; + break; + } 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()) { + if (stream != null) { + 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; } - } else { - status = RED; - } - boolean isStopped = getField(stream, "isStopped", new AtomicBoolean(false)).get(); - healthBuilder.stopped(isStopped); - if (isStopped) { - status = RED; + AtomicBoolean isStopped = getField(stream, "isStopped", new AtomicBoolean(false)); + if (isStopped != null) { + healthBuilder.stopped(isStopped.get()); + if (isStopped.get()) { + status = RED; + } + } + + AtomicReference lastEventId = getField(stream, "lastEventId", null); + if (lastEventId != null && lastEventId.get() != null) { + healthBuilder.lastEventId(lastEventId.get()); + } else { + healthBuilder.lastEventId("-1"); + } } - AtomicReference lastEventId = getField(stream, "lastEventId", null); - if (lastEventId != null) { - healthBuilder.lastEventId(lastEventId.get()); + if (lastActivityTime == null) { + healthBuilder.silenceSinceLastEvent("0"); + } else { + healthBuilder.silenceSinceLastEvent( + String.valueOf(Duration.between(lastActivityTime, Instant.now()).getSeconds())); } results.add(healthBuilder.build()); - return SPOHealthCheckResult.builder() - .name(getName()) - .streams(results) - .status(status.getName()) - .build(); + return SPOHealthCheckResult.builder().name(getName()).streams(results).status(status).build(); } } @@ -303,9 +473,9 @@ public HealthCheckResult check() { class SPOHealthCheckResult implements HealthCheckResult { transient String name; - List statuses; + List statuses; - String status; + HealthCheckStatus status; List streams; @@ -326,5 +496,19 @@ class StreamHealth { boolean threadTerminated; boolean stopped; + + @SerializedName("last_event_id") String lastEventId; + + @SerializedName("seconds_since_last_event") + String silenceSinceLastEvent; +} + +enum ObserverStatus { + RUNNING, + STREAM_ERROR, + SILENCE_ERROR, + PUBLISHER_ERROR, + NEEDS_SHUTDOWN, + SHUTDOWN, } 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 ec329be10d..1235138857 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,15 +1,16 @@ 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; +import java.util.List; + +import static org.stellar.anchor.util.Log.debugF; +import static org.stellar.anchor.util.Log.warnF; + @Service @DependsOn("configManager") public class HealthCheckService { diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt new file mode 100644 index 0000000000..a0bafd354d --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt @@ -0,0 +1,73 @@ +package org.stellar.anchor.platform.controller + +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.unmockkAll +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.http.HttpStatus +import org.stellar.anchor.api.platform.HealthCheckResult +import org.stellar.anchor.api.platform.HealthCheckStatus +import org.stellar.anchor.api.platform.HealthCheckStatus.* +import org.stellar.anchor.healthcheck.HealthCheckable +import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentObserver +import org.stellar.anchor.platform.service.HealthCheckService + +class HealthControllerTest { + @MockK private lateinit var stellarPaymentObserver: StellarPaymentObserver + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, relaxed = true) + } + + @AfterEach + fun teardown() { + clearAllMocks() + unmockkAll() + } + + @Test + fun `health controller and stellar payment observer status code checks`() { + every { stellarPaymentObserver.tags } returns + listOf(HealthCheckable.Tags.ALL, HealthCheckable.Tags.EVENT) + + val healthCheckService = HealthCheckService(listOf(stellarPaymentObserver)) + val healthController = HealthController(healthCheckService) + + // RED should result 500 + every { stellarPaymentObserver.check() } returns PojoHealthCheckResult("observer", RED) + var response = healthController.health(listOf("all")) + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.statusCode) + + // GREEN should result 200 + every { stellarPaymentObserver.check() } returns PojoHealthCheckResult("observer", GREEN) + response = healthController.health(listOf("all")) + assertEquals(HttpStatus.OK, response.statusCode) + + // YELLOW should result 200 + every { stellarPaymentObserver.check() } returns PojoHealthCheckResult("observer", YELLOW) + response = healthController.health(listOf("all")) + assertEquals(HttpStatus.OK, response.statusCode) + } +} + +class PojoHealthCheckResult(private val name: String, private val status: HealthCheckStatus) : + HealthCheckResult { + + override fun name(): String { + return name + } + + override fun getStatuses(): MutableList? { + return mutableListOf(GREEN, RED) + } + + override fun getStatus(): HealthCheckStatus { + return status + } +} 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 index f4a22dfc78..4fc8d26b5e 100644 --- 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 @@ -4,15 +4,20 @@ import com.google.gson.reflect.TypeToken import io.mockk.* import io.mockk.impl.annotations.MockK import java.io.IOException +import javax.net.ssl.SSLProtocolException import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.stellar.anchor.api.platform.HealthCheckStatus.RED import org.stellar.sdk.Server import org.stellar.sdk.requests.RequestBuilder +import org.stellar.sdk.requests.SSEStream import org.stellar.sdk.responses.GsonSingleton import org.stellar.sdk.responses.Page import org.stellar.sdk.responses.operations.OperationResponse +import shadow.com.google.common.base.Optional class StellarPaymentObserverTest { companion object { @@ -125,4 +130,18 @@ class StellarPaymentObserverTest { } assertEquals("4322708489777153", gotCursor) } + + @Test + fun `test if SSEStream exception will leave the observer in STREAM_ERROR state`() { + val stream: SSEStream = mockk(relaxed = true) + val observer = + spyk(StellarPaymentObserver(TEST_HORIZON_URI, null, null, paymentStreamerCursorStore)) + every { observer.startSSEStream() } returns stream + observer.start() + observer.handleFailure(Optional.of(SSLProtocolException(""))) + assertEquals(ObserverStatus.STREAM_ERROR, observer.status) + + val checkResult = observer.check() + assertEquals(RED, checkResult.status) + } } From 4d18d7be1839e7b52ea75f82d03e14f4b7539e53 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 3 Nov 2022 14:19:26 -0700 Subject: [PATCH 0044/1439] Payment Observer Refactoring (#644) * Change yaml schema * Removed Circle implementation * Payment observer config refactoring * turn off SSL check in stellar anchor tests --- .github/workflows/basic_tests.yml | 1 + .../stellar/anchor/config/CircleConfig.java | 11 - .../anchor/config/PaymentObserverConfig.java | 7 - .../stellar/anchor/config/Sep31Config.java | 1 - .../platform/AnchorPlatformIntegrationTest.kt | 12 +- .../platform/ConfigManagementBeans.java | 19 +- ...ntBeans.java => PaymentObserverBeans.java} | 56 +- .../anchor/platform/SepServiceBeans.java | 16 +- .../platform/config/CallbackApiConfig.java | 5 +- .../config/PaymentObserverConfig.java | 90 + .../platform/config/PlatformApiConfig.java | 5 +- .../platform/config/PropertyAppConfig.java | 5 +- .../platform/config/PropertyAssetsConfig.java | 5 +- .../platform/config/PropertyCircleConfig.java | 23 - .../platform/config/PropertyEventConfig.java | 5 +- .../platform/config/PropertyMetricConfig.java | 5 +- .../config/PropertyPaymentObserverConfig.java | 27 - .../platform/config/PropertySep10Config.java | 5 +- .../platform/config/PropertySep12Config.java | 5 +- .../platform/config/PropertySep24Config.java | 5 +- .../platform/config/PropertySep31Config.java | 23 +- .../platform/config/PropertySep38Config.java | 5 +- .../CirclePaymentObserverController.java | 75 - .../platform/event/SqsEventPublisher.java | 2 +- .../circle => observer}/ObservedPayment.java | 2 +- .../observer/PaymentListener.java | 3 +- ...JdbcStellarPaymentStreamerCursorStore.java | 2 +- ...moryStellarPaymentStreamerCursorStore.java | 2 +- .../stellar/PaymentObservingAccountStore.java | 2 +- .../PaymentObservingAccountsManager.java | 2 +- .../stellar/StellarPaymentObserver.java | 96 +- .../StellarPaymentStreamerCursorStore.java | 2 +- .../platform/payment/common/Account.java | 110 - .../platform/payment/common/Balance.java | 27 - .../payment/common/DepositInstructions.java | 126 - .../payment/common/DepositRequirements.java | 116 - .../platform/payment/common/Payment.java | 38 - .../payment/common/PaymentHistory.java | 17 - .../payment/common/PaymentNetwork.java | 28 - .../payment/common/PaymentService.java | 120 - .../payment/config/CirclePaymentConfig.java | 9 - .../observer/circle/CircleGsonParsable.java | 16 - .../circle/CirclePaymentObserverService.java | 288 -- .../observer/circle/CirclePaymentService.java | 768 ------ .../circle/CircleResponseErrorHandler.java | 31 - .../circle/StellarReconciliation.java | 69 - .../observer/circle/model/CircleBalance.java | 40 - .../circle/model/CircleBlockchainAddress.java | 41 - .../circle/model/CircleNotification.java | 47 - .../observer/circle/model/CirclePayment.java | 81 - .../circle/model/CirclePaymentStatus.java | 59 - .../observer/circle/model/CirclePayout.java | 70 - .../circle/model/CircleTransactionParty.java | 107 - .../observer/circle/model/CircleTransfer.java | 66 - .../observer/circle/model/CircleWallet.java | 61 - .../model/CircleWireDepositInstructions.java | 53 - .../model/TransferNotificationBody.java | 13 - .../request/CircleSendTransactionRequest.java | 43 - .../CircleAccountBalancesResponse.java | 17 - .../response/CircleConfigurationResponse.java | 19 - .../model/response/CircleDetailResponse.java | 8 - .../circle/model/response/CircleError.java | 9 - .../model/response/CircleListResponse.java | 9 - .../response/CirclePaymentListResponse.java | 33 - .../response/CirclePayoutListResponse.java | 33 - .../response/CircleTransferListResponse.java | 34 - .../observer/circle/util/CircleAsset.java | 29 - .../observer/circle/util/NettyHttpClient.java | 52 - .../platform/service/HealthCheckService.java | 9 +- .../PaymentOperationToEventListener.java | 4 +- .../service/Sep31DepositInfoGeneratorApi.java | 4 +- .../Sep31DepositInfoGeneratorCircle.java | 35 - .../config/anchor-config-default-values.yaml | 46 +- .../config/anchor-config-schema-v1.yaml | 16 +- .../circle/CirclePaymentServiceTest.kt | 2315 ----------------- .../circle/CircleResponseErrorHandlerTest.kt | 51 - .../circle/ErrorHandlingTestCase.kt | 53 - .../circle/StellarReconciliationTest.kt | 90 - .../model/CircleTransferSerializationTest.kt | 132 - .../utils/NettyHttpClientTest.kt | 23 - .../anchor/platform/LogAppenderTest.kt | 18 +- ...ansTest.kt => PaymentObserverBeansTest.kt} | 52 +- .../anchor/platform/config/Sep31ConfigTest.kt | 36 +- .../controller/HealthControllerTest.kt | 2 +- .../PaymentObservingAccountsManagerTest.kt | 6 +- .../stellar/StellarPaymentObserverTest.kt | 34 +- .../CirclePaymentObserverServiceTest.kt | 632 ----- .../PaymentOperationToEventListenerTest.kt | 10 +- .../Sep31DepositInfoGeneratorApiTest.kt | 4 +- .../sep31/Sep31DepositInfoGeneratorTest.kt | 92 +- .../paymentservice/AccountCapabilitiesTest.kt | 28 - 91 files changed, 344 insertions(+), 6559 deletions(-) delete mode 100644 core/src/main/java/org/stellar/anchor/config/CircleConfig.java delete mode 100644 core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java rename platform/src/main/java/org/stellar/anchor/platform/{PaymentBeans.java => PaymentObserverBeans.java} (61%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyCircleConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java rename platform/src/main/java/org/stellar/anchor/platform/{payment/observer/circle => observer}/ObservedPayment.java (98%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/PaymentListener.java (62%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java (94%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java (85%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/stellar/PaymentObservingAccountStore.java (96%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/stellar/PaymentObservingAccountsManager.java (98%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/stellar/StellarPaymentObserver.java (84%) rename platform/src/main/java/org/stellar/anchor/platform/{payment => }/observer/stellar/StellarPaymentStreamerCursorStore.java (62%) delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/config/CirclePaymentConfig.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleGsonParsable.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt rename platform/src/test/kotlin/org/stellar/anchor/platform/{PaymentBeansTest.kt => PaymentObserverBeansTest.kt} (70%) rename platform/src/test/kotlin/org/stellar/anchor/platform/{payment => }/observer/stellar/PaymentObservingAccountsManagerTest.kt (95%) rename platform/src/test/kotlin/org/stellar/anchor/platform/{payment => }/observer/stellar/StellarPaymentObserverTest.kt (81%) delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt delete mode 100644 platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index c385d0ca4f..e7dc41e1eb 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -53,6 +53,7 @@ jobs: env: PR_NUMBER: ${{github.event.pull_request.number}} BRANCH_NAME: ${{github.ref}} # e.g. refs/heads/main + NODE_TLS_REJECT_UNAUTHORIZED: 0 steps: - uses: actions/checkout@v3 diff --git a/core/src/main/java/org/stellar/anchor/config/CircleConfig.java b/core/src/main/java/org/stellar/anchor/config/CircleConfig.java deleted file mode 100644 index b9af7d33d5..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/CircleConfig.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.stellar.anchor.config; - -import org.springframework.validation.Errors; - -public interface CircleConfig { - String getCircleUrl(); - - String getApiKey(); - - Errors validate(); -} diff --git a/core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java b/core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java deleted file mode 100644 index 60bf11a43f..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/PaymentObserverConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.config; - -public interface PaymentObserverConfig { - boolean isEnabled(); - - String getTrackedWallet(); -} 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 6ae98a62ed..596ef25c7a 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep31Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep31Config.java @@ -18,7 +18,6 @@ enum PaymentType { enum DepositInfoGeneratorType { SELF, - CIRCLE, API } } 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 d30a48f453..fcdb2541fd 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 @@ -178,8 +178,7 @@ class AnchorPlatformIntegrationTest { "sell_amount": "100", "buy_amount": "98.0392" } - }""" - .trimMargin() + }""".trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(result), true) } @@ -215,16 +214,14 @@ class AnchorPlatformIntegrationTest { ] } } - }""" - .trimMargin() + }""".trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(result), true) } @Test fun testRate_firm() { val rate = - rriClient - .getRate( + rriClient.getRate( GetRateRequest.builder() .type(FIRM) .context(SEP31) @@ -280,8 +277,7 @@ class AnchorPlatformIntegrationTest { ] } } - }""" - .trimMargin() + }""".trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(gotQuote), true) } 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 4a20dac01e..8400b97c2e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java @@ -7,7 +7,6 @@ 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 { @@ -69,8 +68,8 @@ Sep24Config sep24Config() { @Bean @ConfigurationProperties(prefix = "sep31") - Sep31Config sep31Config(CircleConfig circleConfig, CallbackApiConfig callbackApiConfig) { - return new PropertySep31Config(circleConfig, callbackApiConfig); + Sep31Config sep31Config(CallbackApiConfig callbackApiConfig) { + return new PropertySep31Config(callbackApiConfig); } @Bean @@ -82,21 +81,11 @@ Sep38Config sep38Config() { /********************************** * Payment observer configurations */ - @Bean - @ConfigurationProperties(prefix = "circle") - CircleConfig circleConfig() { - return new PropertyCircleConfig(); - } @Bean @ConfigurationProperties(prefix = "payment-observer") - PaymentObserverConfig paymentObserverConfig() { - return new PropertyPaymentObserverConfig(); - } - - @Bean - CirclePaymentConfig circlePaymentConfig() { - return new CirclePaymentConfig(); + public PaymentObserverConfig paymentObserverConfig() { + return new PaymentObserverConfig(); } /********************************** diff --git a/platform/src/main/java/org/stellar/anchor/platform/PaymentBeans.java b/platform/src/main/java/org/stellar/anchor/platform/PaymentObserverBeans.java similarity index 61% rename from platform/src/main/java/org/stellar/anchor/platform/PaymentBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/PaymentObserverBeans.java index 06fd304531..edecf68439 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PaymentBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/PaymentObserverBeans.java @@ -3,7 +3,6 @@ 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; import org.springframework.context.annotation.Profile; @@ -11,27 +10,26 @@ import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.config.AppConfig; -import org.stellar.anchor.config.PaymentObserverConfig; -import org.stellar.anchor.horizon.Horizon; +import org.stellar.anchor.platform.config.PaymentObserverConfig; 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; +import org.stellar.anchor.platform.observer.PaymentListener; +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountStore; +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager; +import org.stellar.anchor.platform.observer.stellar.StellarPaymentObserver; +import org.stellar.anchor.platform.observer.stellar.StellarPaymentStreamerCursorStore; @Configuration -public class PaymentBeans { +public class PaymentObserverBeans { @Bean @Profile("stellar-observer") @SneakyThrows - public StellarPaymentObserver stellarPaymentObserverService( + public StellarPaymentObserver stellarPaymentObserver( AssetService assetService, List paymentListeners, StellarPaymentStreamerCursorStore stellarPaymentStreamerCursorStore, PaymentObservingAccountsManager paymentObservingAccountsManager, - AppConfig appConfig) { + AppConfig appConfig, + PaymentObserverConfig paymentObserverConfig) { // validate assetService if (assetService == null || assetService.listAllAssets() == null) { throw new ServerErrorException("Asset service cannot be empty."); @@ -57,16 +55,20 @@ public StellarPaymentObserver stellarPaymentObserverService( // validate appConfig if (appConfig == null) { - throw new ServerErrorException("App config cannot be empty."); + throw new ServerErrorException("AppConfig cannot be empty."); } - StellarPaymentObserver stellarPaymentObserverService = - StellarPaymentObserver.builder() - .horizonServer(appConfig.getHorizonUrl()) - .paymentTokenStore(stellarPaymentStreamerCursorStore) - .observers(paymentListeners) - .paymentObservingAccountManager(paymentObservingAccountsManager) - .build(); + if (paymentObserverConfig == null) { + throw new ServerErrorException("PaymentObserverConfig cannot be empty."); + } + + StellarPaymentObserver stellarPaymentObserver = + new StellarPaymentObserver( + appConfig.getHorizonUrl(), + paymentObserverConfig.getStellar(), + paymentListeners, + paymentObservingAccountsManager, + stellarPaymentStreamerCursorStore); // Add distribution wallet to the observing list as type RESIDENTIAL for (AssetInfo assetInfo : stellarAssets) { @@ -77,8 +79,8 @@ public StellarPaymentObserver stellarPaymentObserverService( } } - stellarPaymentObserverService.start(); - return stellarPaymentObserverService; + stellarPaymentObserver.start(); + return stellarPaymentObserver; } @Bean @@ -91,14 +93,4 @@ public PaymentObservingAccountsManager observingAccounts( public PaymentObservingAccountStore observingAccountStore(PaymentObservingAccountRepo repo) { return new PaymentObservingAccountStore(repo); } - - @Bean - public CirclePaymentObserverService circlePaymentObserverService( - OkHttpClient httpClient, - PaymentObserverConfig circlePaymentObserverConfig, - Horizon horizon, - List paymentListeners) { - return new CirclePaymentObserverService( - httpClient, circlePaymentObserverConfig, horizon, paymentListeners); - } } 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 4c97da3c3f..153cbd802f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java @@ -18,12 +18,9 @@ import org.stellar.anchor.filter.JwtTokenFilter; 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.observer.stellar.PaymentObservingAccountsManager; 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; @@ -118,25 +115,14 @@ Sep24TransactionStore sep24TransactionStore(JdbcSep24TransactionRepo sep24Transa return new JdbcSep24TransactionStore(sep24TransactionRepo); } - @Bean - CirclePaymentService circlePaymentService( - CirclePaymentConfig circlePaymentConfig, CircleConfig circleConfig, Horizon horizon) { - return new CirclePaymentService(circlePaymentConfig, circleConfig, horizon); - } - @Bean Sep31DepositInfoGenerator sep31DepositInfoGenerator( Sep31Config sep31Config, - CirclePaymentService circlePaymentService, PaymentObservingAccountsManager paymentObservingAccountsManager, UniqueAddressIntegration uniqueAddressIntegration) { switch (sep31Config.getDepositInfoGeneratorType()) { case SELF: return new Sep31DepositInfoGeneratorSelf(); - - case CIRCLE: - return new Sep31DepositInfoGeneratorCircle(circlePaymentService); - case API: return new Sep31DepositInfoGeneratorApi( uniqueAddressIntegration, paymentObservingAccountsManager); 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 index fd0425bcb2..318fea84e8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java @@ -2,6 +2,7 @@ import java.util.List; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.auth.AuthInfo; @@ -26,12 +27,12 @@ public void setAuth(AuthInfo auth) { } @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return CallbackApiConfig.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { CallbackApiConfig config = (CallbackApiConfig) target; if (List.of(AuthType.API_KEY, AuthType.JWT_TOKEN).contains(config.getAuth().getType())) { diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java new file mode 100644 index 0000000000..46875b89ec --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java @@ -0,0 +1,90 @@ +package org.stellar.anchor.platform.config; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.jetbrains.annotations.NotNull; +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + +@Data +public class PaymentObserverConfig implements Validator { + PaymentObserverType type; + StellarPaymentObserverConfig stellar; + + public enum PaymentObserverType { + STELLAR + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class StellarPaymentObserverConfig { + int silenceCheckInterval; + int silenceTimeout; + int silenceTimeoutRetries; + int initialStreamBackoffTime; + int maxStreamBackoffTime; + int initialEventBackoffTime; + int maxEventBackoffTime; + } + + @Override + public boolean supports(@NotNull Class clazz) { + return PlatformApiConfig.class.isAssignableFrom(clazz); + } + + @Override + public void validate(@NotNull Object target, @NotNull Errors errors) { + PaymentObserverConfig config = (PaymentObserverConfig) target; + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, + "payment_observer.type", + "empty-payment-observer-type", + "payment_observer.type must not be empty"); + if (config.type.equals(PaymentObserverType.STELLAR)) { + if (config.stellar == null) { + errors.reject( + "empty-payment-observer-stellar", + "The payment_observer.type is set ast STELLAR. Please populate the payment_observer.stellar fields."); + } else { + if (config.stellar.silenceCheckInterval < 1) { + errors.reject( + "invalid-payment-observer-silence-check-interval", + "The payment_observer.stellar.silence_check_interval must be greater than 1"); + } + if (config.stellar.silenceTimeout < 5) { + errors.reject( + "invalid-payment-observer-stellar-silence-timeout", + "The payment_observer.stellar.silence_timeout must be greater than 5"); + } + if (config.stellar.silenceTimeoutRetries < 1) { + errors.reject( + "invalid-payment-observer-stellar-silence-timeout-retries", + "The payment_observer.stellar.silence_timeout_retries must be equal or greater than 1"); + } + if (config.stellar.initialStreamBackoffTime <= 1) { + errors.reject( + "invalid-payment-observer-stellar-initial-stream-backoff-time", + "The payment_observer.stellar.initial_stream_backoff must be equal or greater than 1"); + } + if (config.stellar.maxStreamBackoffTime <= 2) { + errors.reject( + "invalid-payment-observer-stellar-max-stream-backoff-time", + "The payment_observer.stellar.max_stream_backoff must be equal or greater than 2"); + } + if (config.stellar.initialEventBackoffTime <= 1) { + errors.reject( + "invalid-payment-observer-stellar-initial-event-backoff-time", + "The payment_observer.stellar.initial_event_backoff_time must be equal or greater than 1"); + } + if (config.stellar.maxEventBackoffTime <= 2) { + errors.reject( + "invalid-payment-observer-stellar-max-event-backoff-time", + "The payment_observer.stellar.max_event_backoff_time must be equal or greater than 2"); + } + } + } + } +} 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 index fa918667f7..bd673aa2a6 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PlatformApiConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PlatformApiConfig.java @@ -5,6 +5,7 @@ import java.util.List; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.auth.AuthInfo; @@ -24,12 +25,12 @@ public void setAuth(AuthInfo auth) { } @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return PlatformApiConfig.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { PlatformApiConfig config = (PlatformApiConfig) target; if (List.of(API_KEY, JWT_TOKEN).contains(config.getAuth().getType())) { if (config.getAuth().getSecret() == null) { 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 e4e1ac312d..3929db2fe6 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,6 +2,7 @@ import java.util.List; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; @@ -23,12 +24,12 @@ public class PropertyAppConfig implements AppConfig, Validator { private List languages; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return AppConfig.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, Errors errors) { AppConfig config = (AppConfig) target; ValidationUtils.rejectIfEmpty( 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 index 73b7689de6..cda58463d2 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.AssetsConfig; @@ -11,10 +12,10 @@ public class PropertyAssetsConfig implements AssetsConfig, Validator { String value; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return AssetsConfig.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) {} + public void validate(@NotNull Object target, @NotNull Errors errors) {} } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyCircleConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyCircleConfig.java deleted file mode 100644 index 3fb546e0d2..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyCircleConfig.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.stellar.anchor.platform.config; - -import lombok.Data; -import org.springframework.validation.BindException; -import org.springframework.validation.ValidationUtils; -import org.stellar.anchor.config.CircleConfig; -import org.stellar.anchor.util.UrlValidationUtil; - -@Data -public class PropertyCircleConfig implements CircleConfig { - String circleUrl; - - String apiKey; - - public BindException validate() { - BindException errors = new BindException(this, "circleConfig"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "circleUrl", "empty-circleUrl"); - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "apiKey", "empty-apiKey"); - - UrlValidationUtil.rejectIfMalformed(circleUrl, "circleUrl", errors); - return 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 5e16524f80..6780e2c761 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 @@ -2,6 +2,7 @@ import java.util.Map; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; @@ -15,12 +16,12 @@ public class PropertyEventConfig implements EventConfig, Validator { private Map eventTypeToQueue; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return PropertyEventConfig.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { EventConfig config = (EventConfig) target; if (!config.isEnabled()) { return; 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 3fb647eff9..1c673632ae 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,6 +1,7 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.AppConfig; @@ -13,12 +14,12 @@ public class PropertyMetricConfig implements MetricConfig, Validator { private Integer runInterval = 30; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return AppConfig.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { System.out.println("here"); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java deleted file mode 100644 index 1a0e660030..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyPaymentObserverConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.stellar.anchor.platform.config; - -import lombok.Data; -import org.springframework.validation.Errors; -import org.springframework.validation.ValidationUtils; -import org.springframework.validation.Validator; -import org.stellar.anchor.config.PaymentObserverConfig; - -@Data -public class PropertyPaymentObserverConfig implements PaymentObserverConfig, Validator { - private boolean enabled = false; - private String trackedWallet = "all"; - - @Override - public boolean supports(Class clazz) { - return PaymentObserverConfig.class.isAssignableFrom(clazz); - } - - @Override - public void validate(Object target, Errors errors) { - 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/PropertySep10Config.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertySep10Config.java index 21f93d8ca5..45c562d670 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 @@ -4,6 +4,7 @@ import java.util.List; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; @@ -31,12 +32,12 @@ public PropertySep10Config(SecretConfig secretConfig, JwtService jwtService) { } @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return Sep10Config.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { Sep10Config config = (Sep10Config) target; if (config.getEnabled()) { 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 523ca512d2..57da720f79 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 @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep12Config; @@ -15,12 +16,12 @@ public PropertySep12Config(CallbackApiConfig callbackApiConfig) { } @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return Sep12Config.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { Sep12Config config = (Sep12Config) target; } } 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 f3726f3416..3b72a6c60b 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,6 +1,7 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep24Config; @@ -12,12 +13,12 @@ public class PropertySep24Config implements Sep24Config, Validator { String interactiveUrl; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return Sep24Config.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull 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 5623d444d7..7f7e5398b6 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,13 +1,14 @@ package org.stellar.anchor.platform.config; -import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.*; +import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.API; +import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.SELF; import static org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND; import java.util.Objects; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; -import org.stellar.anchor.config.CircleConfig; import org.stellar.anchor.config.Sep31Config; @Data @@ -18,31 +19,21 @@ public class PropertySep31Config implements Sep31Config, Validator { PaymentType paymentType = STRICT_SEND; DepositInfoGeneratorType depositInfoGeneratorType = SELF; - CircleConfig circleConfig; - - public PropertySep31Config(CircleConfig circleConfig, CallbackApiConfig callbackApiConfig) { - this.circleConfig = circleConfig; + public PropertySep31Config(CallbackApiConfig callbackApiConfig) { this.feeIntegrationEndPoint = callbackApiConfig.getBaseUrl(); this.uniqueAddressIntegrationEndPoint = callbackApiConfig.getBaseUrl(); } @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return Sep31Config.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { Sep31Config config = (Sep31Config) target; if (config.isEnabled()) { - if (config.getDepositInfoGeneratorType().equals(CIRCLE)) { - if (circleConfig.validate().hasErrors()) { - errors.rejectValue( - "circleConfig", - "badConfig-circle", - "depositInfoGeneratorType set as circle, but circle config not properly configured"); - } - } else if (config.getDepositInfoGeneratorType().equals(API)) { + if (config.getDepositInfoGeneratorType().equals(API)) { if (Objects.toString(uniqueAddressIntegrationEndPoint, "").isEmpty()) { errors.rejectValue( "uniqueAddressIntegrationEndPoint", 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 6b159aef18..fcc1654236 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 @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.config; import lombok.Data; +import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep38Config; @@ -10,12 +11,12 @@ public class PropertySep38Config implements Sep38Config, Validator { boolean enabled; @Override - public boolean supports(Class clazz) { + public boolean supports(@NotNull Class clazz) { return Sep38Config.class.isAssignableFrom(clazz); } @Override - public void validate(Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { Sep38Config config = (Sep38Config) target; } } 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 deleted file mode 100644 index d6102d5677..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/CirclePaymentObserverController.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.stellar.anchor.platform.controller; - -import static org.stellar.anchor.util.Log.errorEx; -import static org.stellar.anchor.util.Log.warnEx; - -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; -import org.stellar.anchor.platform.payment.observer.circle.CirclePaymentObserverService; -import org.stellar.anchor.platform.payment.observer.circle.model.CircleNotification; - -@RestController -@RequestMapping("/circle-observer") -@Profile("default") -public class CirclePaymentObserverController { - private final Gson gson = new Gson(); - private final CirclePaymentObserverService circlePaymentObserverService; - - public CirclePaymentObserverController( - CirclePaymentObserverService circlePaymentObserverService) { - this.circlePaymentObserverService = circlePaymentObserverService; - } - - @CrossOrigin(origins = "*") - @RequestMapping( - value = "", - method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.HEAD}, - consumes = {MediaType.APPLICATION_JSON_VALUE}) - public void handleCircleNotificationJson( - @RequestBody(required = false) Map requestBody) - throws EventPublishException, BadRequestException, ServerErrorException { - try { - CircleNotification circleNotification = - gson.fromJson(gson.toJson(requestBody), CircleNotification.class); - circlePaymentObserverService.handleCircleNotification(circleNotification); - } catch (UnprocessableEntityException ex) { - throw new BadRequestException("Error parsing the request."); - } - } - - @CrossOrigin(origins = "*") - @RequestMapping( - value = "", - method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.HEAD}, - consumes = {MediaType.TEXT_PLAIN_VALUE}) - public void handleCircleNotificationTextPlain(@RequestBody(required = false) String jsonBodyStr) - throws UnprocessableEntityException, BadRequestException, ServerErrorException, - EventPublishException { - CircleNotification circleNotification = gson.fromJson(jsonBodyStr, CircleNotification.class); - circlePaymentObserverService.handleCircleNotification(circleNotification); - } - - @ExceptionHandler(UnprocessableEntityException.class) - @ResponseStatus(value = HttpStatus.NO_CONTENT) - public SepExceptionResponse handleUnhandledCaseException(RestClientException ex) { - warnEx(ex); - return new SepExceptionResponse(ex.getMessage()); - } - - @ExceptionHandler(BadRequestException.class) - @ResponseStatus(value = HttpStatus.BAD_REQUEST) - public SepExceptionResponse handleBadRequestException(RestClientException ex) { - errorEx(ex); - return new SepExceptionResponse(ex.getMessage()); - } -} 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 4c4b51fa72..c22a12a2e1 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,7 +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/java/org/stellar/anchor/platform/payment/observer/circle/ObservedPayment.java b/platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java similarity index 98% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/ObservedPayment.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java index 9d621de859..dae892dbd2 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/ObservedPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.circle; +package org.stellar.anchor.platform.observer; import com.google.gson.annotations.SerializedName; import lombok.Builder; diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java b/platform/src/main/java/org/stellar/anchor/platform/observer/PaymentListener.java similarity index 62% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/PaymentListener.java index 3ac8a45f84..8bb8e96feb 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/PaymentListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/PaymentListener.java @@ -1,7 +1,6 @@ -package org.stellar.anchor.platform.payment.observer; +package org.stellar.anchor.platform.observer; import org.stellar.anchor.api.exception.EventPublishException; -import org.stellar.anchor.platform.payment.observer.circle.ObservedPayment; public interface PaymentListener { void onReceived(ObservedPayment payment) throws EventPublishException; 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/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java similarity index 94% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java index 6129be480a..96cecd39fe 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar; +package org.stellar.anchor.platform.observer.stellar; import static org.stellar.anchor.platform.data.PaymentStreamerCursor.SINGLETON_ID; 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/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java similarity index 85% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java index 71e8c736c6..b705a5f0f4 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar; +package org.stellar.anchor.platform.observer.stellar; import java.util.HashMap; import java.util.Map; 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/observer/stellar/PaymentObservingAccountStore.java similarity index 96% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountStore.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountStore.java index 89d40e3002..afedcd2614 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountStore.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar; +package org.stellar.anchor.platform.observer.stellar; import java.time.Instant; import java.util.ArrayList; 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/observer/stellar/PaymentObservingAccountsManager.java similarity index 98% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManager.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManager.java index 0fa9846a01..88f1f2ddc1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManager.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar; +package org.stellar.anchor.platform.observer.stellar; import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.ChronoUnit.MINUTES; 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/observer/stellar/StellarPaymentObserver.java similarity index 84% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java index 4c0c602601..579749b686 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java @@ -1,9 +1,9 @@ -package org.stellar.anchor.platform.payment.observer.stellar; +package org.stellar.anchor.platform.observer.stellar; import static org.stellar.anchor.api.platform.HealthCheckStatus.*; import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.ALL; import static org.stellar.anchor.healthcheck.HealthCheckable.Tags.EVENT; -import static org.stellar.anchor.platform.payment.observer.stellar.ObserverStatus.*; +import static org.stellar.anchor.platform.observer.stellar.ObserverStatus.*; import static org.stellar.anchor.util.Log.debugF; import static org.stellar.anchor.util.Log.infoF; import static org.stellar.anchor.util.ReflectionUtil.getField; @@ -12,7 +12,10 @@ import java.io.IOException; import java.time.Duration; import java.time.Instant; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -24,12 +27,12 @@ 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; 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.platform.config.PaymentObserverConfig.StellarPaymentObserverConfig; +import org.stellar.anchor.platform.observer.ObservedPayment; +import org.stellar.anchor.platform.observer.PaymentListener; import org.stellar.anchor.util.ExponentialBackoffTimer; import org.stellar.anchor.util.Log; import org.stellar.sdk.Server; @@ -49,24 +52,16 @@ public class StellarPaymentObserver implements HealthCheckable { /** The minimum number of results the Stellar Blockchain can return. */ private static final int MIN_RESULTS = 1; - // If the observer had been silent for longer than SILENC_TIMEOUT, a SilenceTimeoutException will - // be thrown to trigger reconnections. - private static final long SILENCE_TIMEOUT = 90; - // If the observer has more than 2 SILENCE_TIMEOUT_RETRIES, it will be marked unhealthy - private static final long SILENCE_TIMEOUT_RETRIES = 2; - - // The time interval between silence checks - private static final long SILENCE_CHECK_INTERVAL = 5; - final Server server; - final Set paymentListeners; + private StellarPaymentObserverConfig config; + final List paymentListeners; final StellarPaymentStreamerCursorStore paymentStreamerCursorStore; final Map, String> mapStreamToAccount = new HashMap<>(); final PaymentObservingAccountsManager paymentObservingAccountsManager; SSEStream stream; - final ExponentialBackoffTimer publishingBackoffTimer = new ExponentialBackoffTimer(); - final ExponentialBackoffTimer streamBackoffTimer = new ExponentialBackoffTimer(); + ExponentialBackoffTimer publishingBackoffTimer; + ExponentialBackoffTimer streamBackoffTimer; int silenceTimeoutCount = 0; ObserverStatus status = RUNNING; @@ -76,15 +71,24 @@ public class StellarPaymentObserver implements HealthCheckable { ScheduledExecutorService silenceWatcher = Executors.newSingleThreadScheduledExecutor(); ScheduledExecutorService statusWatcher = Executors.newSingleThreadScheduledExecutor(); - StellarPaymentObserver( + public StellarPaymentObserver( String horizonServer, - Set paymentListeners, + StellarPaymentObserverConfig config, + List paymentListeners, PaymentObservingAccountsManager paymentObservingAccountsManager, StellarPaymentStreamerCursorStore paymentStreamerCursorStore) { this.server = new Server(horizonServer); + this.config = config; this.paymentListeners = paymentListeners; this.paymentObservingAccountsManager = paymentObservingAccountsManager; this.paymentStreamerCursorStore = paymentStreamerCursorStore; + + publishingBackoffTimer = + new ExponentialBackoffTimer( + config.getInitialEventBackoffTime(), config.getMaxEventBackoffTime()); + streamBackoffTimer = + new ExponentialBackoffTimer( + config.getInitialStreamBackoffTime(), config.getMaxStreamBackoffTime()); } /** Start the observer. */ @@ -96,7 +100,7 @@ public void start() { silenceWatcher.scheduleAtFixedRate( this::checkSilence, 1, - SILENCE_CHECK_INTERVAL, + config.getSilenceCheckInterval(), TimeUnit.SECONDS); // TODO: The period should be made configurable in version 2.x infoF("Starting the status watcher"); @@ -164,7 +168,7 @@ void checkSilence() { Instant now = Instant.now(); if (lastActivityTime != null) { Duration silenceDuration = Duration.between(lastActivityTime, now); - if (silenceDuration.getSeconds() > SILENCE_TIMEOUT) { + if (silenceDuration.getSeconds() > config.getSilenceTimeout()) { infoF("The observer had been silent for {} seconds.", silenceDuration.getSeconds()); setStatus(SILENCE_ERROR); } else { @@ -210,9 +214,10 @@ void checkStatus() { break; case SILENCE_ERROR: infoF("The silence reconnection count: {}", silenceTimeoutCount); - // We got the silence error. If silence reconnect too many times, we will shut down the - // observer. - if (silenceTimeoutCount >= SILENCE_TIMEOUT_RETRIES) { + // We got the silence error. If silence reconnect too many times and the max retries is + // greater than zero, we will shut down the observer. + if (config.getSilenceTimeoutRetries() > 0 + && silenceTimeoutCount >= config.getSilenceTimeoutRetries()) { infoF( "The silence error has happened for too many times:{}. Shutdown the observer", silenceTimeoutCount); @@ -346,52 +351,11 @@ void setStatus(ObserverStatus status) { this.status = status; } - public static Builder builder() { - return new Builder(); - } - @Override public int compareTo(@NotNull HealthCheckable other) { return this.getName().compareTo(other.getName()); } - public static class Builder { - String horizonServer = "https://horizon-testnet.stellar.org"; - Set observers = new HashSet<>(); - StellarPaymentStreamerCursorStore paymentStreamerCursorStore = - new MemoryStellarPaymentStreamerCursorStore(); - private PaymentObservingAccountsManager paymentObservingAccountsManager; - - public Builder() {} - - public Builder horizonServer(String horizonServer) { - this.horizonServer = horizonServer; - return this; - } - - public Builder observers(List observers) { - this.observers.addAll(observers); - return this; - } - - public Builder paymentTokenStore( - StellarPaymentStreamerCursorStore stellarPaymentStreamerCursorStore) { - this.paymentStreamerCursorStore = stellarPaymentStreamerCursorStore; - return this; - } - - public Builder paymentObservingAccountManager( - PaymentObservingAccountsManager paymentObservingAccountsManager) { - this.paymentObservingAccountsManager = paymentObservingAccountsManager; - return this; - } - - public StellarPaymentObserver build() throws ValueValidationException { - return new StellarPaymentObserver( - horizonServer, observers, paymentObservingAccountsManager, paymentStreamerCursorStore); - } - } - @Override public String getName() { return "stellar_payment_observer"; 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/observer/stellar/StellarPaymentStreamerCursorStore.java similarity index 62% rename from platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java rename to platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentStreamerCursorStore.java index 841fda73ef..597cc90dd9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentStreamerCursorStore.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar; +package org.stellar.anchor.platform.observer.stellar; public interface StellarPaymentStreamerCursorStore { void save(String cursor); diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java deleted file mode 100644 index 2114513924..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/Account.java +++ /dev/null @@ -1,110 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import java.util.*; -import lombok.Data; -import lombok.Getter; -import reactor.util.annotation.NonNull; -import reactor.util.annotation.Nullable; - -@Data -public class Account { - @NonNull public PaymentNetwork paymentNetwork; - - @NonNull public String id; - - /** - * A complementary identifier of the account. It might be considered mandatory depending on the - * use case. - */ - @Nullable public String idTag; - - /** - * An object indicating which networks this account can interact with for sending and/or receiving - * funds. - */ - @NonNull public Account.Capabilities capabilities; - - @Nullable public List balances; - - /** - * The list of not-yet-available balances that are expected to settle shortly. These balances - * could be cancelled or returned, in which cases they may never become available in the user - * account. - */ - @Nullable public List unsettledBalances; - - public Account( - @NonNull PaymentNetwork paymentNetwork, - @NonNull String id, - @Nullable String idTag, - @NonNull Account.Capabilities capabilities) { - this.paymentNetwork = paymentNetwork; - this.id = id; - this.idTag = idTag; - this.capabilities = capabilities; - } - - public Account( - @NonNull PaymentNetwork paymentNetwork, - @NonNull String id, - @NonNull Account.Capabilities capabilities) { - this.id = id; - this.paymentNetwork = paymentNetwork; - this.capabilities = capabilities; - } - - @Getter - public static class Capabilities { - private final Map send = new HashMap<>(); - - private final Map receive = new HashMap<>(); - - /** - * Capabilities is an object indicating which networks this account can interact with for - * sending and/or receiving funds. - * - * @param send contains a list of networks this account can send funds to. - * @param receive contains a list of networks this account can receive funds from. - */ - public Capabilities(List send, List receive) { - for (PaymentNetwork paymentNetwork : PaymentNetwork.values()) { - this.send.put(paymentNetwork, false); - this.receive.put(paymentNetwork, false); - } - - if (send != null) { - for (PaymentNetwork paymentNetwork : send) { - this.send.put(paymentNetwork, true); - } - } - - if (receive != null) { - for (PaymentNetwork paymentNetwork : receive) { - this.receive.put(paymentNetwork, true); - } - } - } - - public Capabilities(PaymentNetwork... sendAndReceive) { - this(List.of(sendAndReceive), List.of(sendAndReceive)); - } - - public void set(PaymentNetwork paymentNetwork, Boolean supportEnabled) { - this.send.put(paymentNetwork, supportEnabled); - this.receive.put(paymentNetwork, supportEnabled); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Capabilities that = (Capabilities) o; - return this.send.equals(that.send) && this.receive.equals(that.receive); - } - - @Override - public String toString() { - return "Capabilities{" + "send=" + send + ", receive=" + receive + '}'; - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java deleted file mode 100644 index 7a555625bd..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/Balance.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import lombok.Data; - -@Data -public class Balance { - String amount; - /** - * The name of the currency that will be ultimately credited into the beneficiary user account. It - * should obey the {scheme}:{identifier} format described in SEP-38. - */ - String currencyName; - - /** - * A balance object representing amount and currency name. - * - * @param amount The amount of assets in the balance or payment. - * @param currencyName The name of the currency that will be ultimately credited into the - * destination account. It should obey the {scheme}:{identifier} format described in SEP-38. - */ - public Balance(String amount, String currencyName) { - this.amount = amount; - this.currencyName = currencyName; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java deleted file mode 100644 index 0fb49efec0..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositInstructions.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import java.util.Map; -import lombok.Data; -import reactor.util.annotation.Nullable; - -/** - * The instructions needed to make a deposit into a beneficiary account using an intermediary - * account. - * - * @see DepositRequirements - * @see PaymentService#getDepositInstructions(DepositRequirements) - */ -@Data -public class DepositInstructions { - /** - * The internal identifier of the beneficiary account, i.e. the account who will receive the - * payment. - */ - String beneficiaryAccountId; - - /** - * A complementary identifier of the beneficiary account who will receive the payment. It might be - * considered mandatory depending on the use case. - */ - @Nullable String beneficiaryAccountIdTag; - - /** - * The network of the deposit beneficiary. It is the network where the deposit will be ultimately - * credited. - */ - PaymentNetwork beneficiaryPaymentNetwork; - - /** The identifier of the intermediary account who will receive the deposit. */ - String intermediaryAccountId; - - /** - * A complementary identifier of the intermediary account who will receive the deposit. It might - * be considered mandatory depending on the use case. - */ - @Nullable String intermediaryAccountIdTag; - - /** - * The network where the deposit will be made. Some time after the deposit is performed on that - * network it will be reflected in the beneficiary user balance. Time for confirmation and - * reconciliation may vary depending on the network used. - */ - PaymentNetwork intermediaryPaymentNetwork; - - /** - * The name of the currency to be deposited into the intermediary network. It should obey the - * {scheme}:{identifier} format described in SEP-38. - */ - String intermediaryCurrencyName; - - /** Extra information needed to perform the deposit. */ - @Nullable Map extra; - - /** - * Constructor for the DepositInstructions class - * - * @param beneficiaryAccountId Identifier of the account who will receive the payment. - * @param beneficiaryAccountIdTag Complementary identifier of the account who will receive the - * payment. May be mandatory depending on the implementation. - * @param beneficiaryPaymentNetwork A complementary identifier of the intermediary account who - * will receive the deposit. It might be considered mandatory depending on the use case. - * @param intermediaryAccountId The identifier of the intermediary account who will receive the - * deposit. - * @param intermediaryAccountIdTag A complementary identifier of the beneficiary account who will - * receive the payment. - * @param intermediaryPaymentNetwork The network where the deposit will be made. After the deposit - * is performed on that network it will be reflected in the beneficiary user balance - * @param intermediaryCurrencyName The name of the currency to be deposited into the intermediary - * network. - * @param extra Extra information needed to perform the deposit. - */ - public DepositInstructions( - String beneficiaryAccountId, - @Nullable String beneficiaryAccountIdTag, - PaymentNetwork beneficiaryPaymentNetwork, - String intermediaryAccountId, - @Nullable String intermediaryAccountIdTag, - PaymentNetwork intermediaryPaymentNetwork, - String intermediaryCurrencyName, - @Nullable Map extra) { - this.beneficiaryAccountId = beneficiaryAccountId; - this.beneficiaryAccountIdTag = beneficiaryAccountIdTag; - this.beneficiaryPaymentNetwork = beneficiaryPaymentNetwork; - this.intermediaryAccountId = intermediaryAccountId; - this.intermediaryAccountIdTag = intermediaryAccountIdTag; - this.intermediaryPaymentNetwork = intermediaryPaymentNetwork; - this.intermediaryCurrencyName = intermediaryCurrencyName; - this.extra = extra; - } - - /** - * Constructor for the DepositInstructions class - * - * @param beneficiaryAccountId Identifier of the account who will receive the payment. - * @param beneficiaryPaymentNetwork A complementary identifier of the intermediary account who - * will receive the deposit. It might be considered mandatory depending on the use case. - * @param intermediaryAccountId The identifier of the intermediary account who will receive the - * deposit. - * @param intermediaryPaymentNetwork The network where the deposit will be made. After the deposit - * is performed on that network it will be reflected in the beneficiary user balance - * @param intermediaryCurrencyName The name of the currency to be deposited into the intermediary - * network. - */ - public DepositInstructions( - String beneficiaryAccountId, - PaymentNetwork beneficiaryPaymentNetwork, - String intermediaryAccountId, - PaymentNetwork intermediaryPaymentNetwork, - String intermediaryCurrencyName) { - this( - beneficiaryAccountId, - null, - beneficiaryPaymentNetwork, - intermediaryAccountId, - null, - intermediaryPaymentNetwork, - intermediaryCurrencyName, - null); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java deleted file mode 100644 index 5785837dc4..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/DepositRequirements.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import lombok.Data; -import reactor.util.annotation.Nullable; - -/** - * Contains the configuration options needed for an external user to make a deposit into the desired - * account. This class will be needed if the implementation allows users to make deposits using - * intermediary networks. For instance, when a user wants to make a deposit to their Circle account - * through a Stellar payment. - * - * @see DepositInstructions - * @see PaymentService#getDepositInstructions(DepositRequirements) - */ -@Data -public class DepositRequirements { - /** - * The internal identifier of the beneficiary account, i.e. the account who will receive the - * payment. - */ - String beneficiaryAccountId; - - /** - * A complementary identifier of the beneficiary account who will receive the payment. It might be - * considered mandatory depending on the use case. - */ - @Nullable String beneficiaryAccountIdTag; - - /** - * The network where the deposit will happen. After the deposit is performed on that network it - * will be reflected in the beneficiary user balance. Time for confirmation and reconciliation may - * be needed depending on the network used. - */ - PaymentNetwork intermediaryPaymentNetwork; - - @Nullable String intermediaryAccountId; - - /** - * The name of the currency that will be ultimately credited into the beneficiary user account. It - * should obey the {scheme}:{identifier} format described in SEP-38. - */ - String beneficiaryCurrencyName; - - /** - * Constructor of the DepositConfiguration class. - * - * @param beneficiaryAccountId Identifier of the account who will receive the payment. - * @param intermediaryPaymentNetwork The network where the deposit will be made. After the deposit - * is performed on that network it will be reflected in the beneficiary user balance - * @param beneficiaryCurrencyName The name of the currency that will be ultimately credited into - * the beneficiary user account. For instance, if you want the instructions to receive USD - * credits in a Circle account using Stellar as an intermediary medium, the currency name - * should be "circle:USD". - */ - public DepositRequirements( - String beneficiaryAccountId, - PaymentNetwork intermediaryPaymentNetwork, - String beneficiaryCurrencyName) { - this(beneficiaryAccountId, null, intermediaryPaymentNetwork, null, beneficiaryCurrencyName); - } - - /** - * Constructor of the DepositConfiguration class. - * - * @param beneficiaryAccountId Identifier of the account who will receive the payment. - * @param beneficiaryAccountIdTag Complementary identifier of the account who will receive the - * payment. May be mandatory depending on the implementation. - * @param intermediaryPaymentNetwork The network where the deposit will be made. After the deposit - * is performed on that network it will be reflected in the beneficiary user balance - * @param beneficiaryCurrencyName The name of the currency that will be ultimately credited into - * the beneficiary user account. For instance, if you want the instructions to receive USD - * credits in a Circle account using Stellar as an intermediary medium, the currency name - * should be "circle:USD". - */ - public DepositRequirements( - String beneficiaryAccountId, - @Nullable String beneficiaryAccountIdTag, - PaymentNetwork intermediaryPaymentNetwork, - String beneficiaryCurrencyName) { - this( - beneficiaryAccountId, - beneficiaryAccountIdTag, - intermediaryPaymentNetwork, - null, - beneficiaryCurrencyName); - } - - /** - * Constructor of the DepositConfiguration class. - * - * @param beneficiaryAccountId Identifier of the account who will receive the payment. - * @param beneficiaryAccountIdTag Complementary identifier of the account who will receive the - * payment. May be mandatory depending on the implementation. - * @param intermediaryPaymentNetwork The network where the deposit will be made. After the deposit - * is performed on that network it will be reflected in the beneficiary user balance - * @param intermediaryAccountId The id of the intermediary account. It may be mandatory for some - * cases. - * @param beneficiaryCurrencyName The name of the currency that will be ultimately credited into - * the beneficiary user account. For instance, if you want the instructions to receive USD - * credits in a Circle account using Stellar as an intermediary medium, the currency name - * should be "circle:USD". - */ - public DepositRequirements( - String beneficiaryAccountId, - @Nullable String beneficiaryAccountIdTag, - PaymentNetwork intermediaryPaymentNetwork, - @Nullable String intermediaryAccountId, - String beneficiaryCurrencyName) { - this.beneficiaryAccountId = beneficiaryAccountId; - this.beneficiaryAccountIdTag = beneficiaryAccountIdTag; - this.intermediaryPaymentNetwork = intermediaryPaymentNetwork; - this.intermediaryAccountId = intermediaryAccountId; - this.beneficiaryCurrencyName = beneficiaryCurrencyName; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java deleted file mode 100644 index de7c77cc04..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/Payment.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import java.time.Instant; -import java.util.Map; -import lombok.Data; - -@Data -public class Payment { - String id; - String idTag; - Account sourceAccount; - Account destinationAccount; - /** The balance currency name contains the scheme of the destination network of the payment. */ - Balance balance; - - Status status; - String errorCode; - Instant createdAt; - Instant updatedAt; - Map originalResponse; - - public enum Status { - PENDING("pending"), - SUCCESSFUL("successful"), - FAILED("failed"); - - private final String name; - - Status(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java deleted file mode 100644 index 7799ec0076..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentHistory.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import java.util.ArrayList; -import java.util.List; -import lombok.Data; - -@Data -public class PaymentHistory { - Account account; - String afterCursor; - String beforeCursor; - List payments = new ArrayList<>(); - - public PaymentHistory(Account account) { - this.account = account; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java deleted file mode 100644 index f5b060145d..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentNetwork.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import lombok.Getter; - -@Getter -public enum PaymentNetwork { - CIRCLE("circle", "circle"), - STELLAR("stellar", "stellar"), - BANK_WIRE("bank_wire", "iso4217"); - - private final String name; - - /** - * Currency prefix is based on SEP-38 identification format: - * https://github.com/stellar/stellar-protocol/blob/65c9c20/ecosystem/sep-0038.md#asset-identification-format - */ - private final String currencyPrefix; - - PaymentNetwork(String name, String currencyPrefix) { - this.name = name; - this.currencyPrefix = currencyPrefix; - } - - @Override - public String toString() { - return name; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java deleted file mode 100644 index 70f8c3f49e..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/common/PaymentService.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.stellar.anchor.platform.payment.common; - -import java.math.BigDecimal; -import org.stellar.anchor.api.exception.HttpException; -import reactor.core.publisher.Mono; -import reactor.util.annotation.Nullable; - -/** - * Contains the interface methods for a payment service. It can be implemented in different - * networks, like Stellar, Circle, Wyre or others. - */ -public interface PaymentService { - PaymentNetwork getPaymentNetwork(); - - /** - * Gets the name of the service. - * - * @return The name of the service - */ - String getName(); - - /** - * API request that pings the server to make sure it's up and running. - * - * @return asynchronous stream with a Void value. If no exception is thrown it means the request - * was successful and the remote server is operational. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - Mono ping() throws HttpException; - - /** - * API request that returns the id of the distribution account managed by the secret key. - * - * @return asynchronous stream with the id of the distribution account managed by the secret key. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - Mono getDistributionAccountAddress() throws HttpException; - - /** - * API request that retrieves the account with the given id. - * - * @param accountId is the existing account identifier. - * @return asynchronous stream with the account object. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - Mono getAccount(String accountId) throws HttpException; - - /** - * API request that creates an account with the given id. - * - * @param accountId is the identifier of the account to be created. It could be mandatory or - * optional depending on the implementation. - * @return asynchronous stream with the account object. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - Mono createAccount(@Nullable String accountId) throws HttpException; - - /** - * API request that returns the history of payments involving a given account. - * - * @param accountID the id of the account whose payment history we want to fetch. - * @param beforeCursor the value used to limit payments to only those before it - * @param afterCursor the value used to limit payments to only those before it - * @return asynchronous stream with the payment history. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - Mono getAccountPaymentHistory( - String accountID, @Nullable String beforeCursor, @Nullable String afterCursor) - throws HttpException; - - /** - * API request that executes a payment between accounts. The APIKey needs to have access to the - * source account for this request to succeed. - * - * @param sourceAccount the account making the payment. Only the network and id fields are needed. - * @param destinationAccount the account receiving the payment. The network field and a subset of - * (id, address and addressTag) will be mandatory. - * @param currencyName the name of the currency used in the payment. It should obey the - * {scheme}:{identifier} format described in SEP-38. - * @param amount the payment amount. - * @return asynchronous stream with the payment object. - * @throws HttpException If the provided input parameters are invalid. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - Mono sendPayment( - Account sourceAccount, Account destinationAccount, String currencyName, BigDecimal amount) - throws HttpException; - - /** - * API request that returns the info needed to make a deposit into a user account. This method - * will be needed if the implementation allows users to make deposits using external networks. For - * instance, when a user wants to make a deposit to their Circle account through a Stellar - * payment: - * - *
{@code
-   * // Here we want to check how we can top up a Circle account using USDC funds from the Stellar network.
-   * String circleWalletId = "1000066041";
-   * Network fromNetwork = Network.STELLAR;
-   * String currencyName = "USD";  // or "USDC"
-   * DepositConfiguration config = new DepositConfiguration(circleWalletId, fromNetwork, currencyName);
-   *
-   * // Here are the instructions with the Stellar account that will receive the payment:
-   * DepositInfo depositInfo = getDepositInstructions(config).block();
-   * System.out.println("PublicKey: " + depositInfo.accountId);        // "PublicKey: G..."
-   * System.out.println("Memo: " + depositInfo.accountIdTag);          // "Memo: 2454278437550473431"
-   * System.out.println("Network: " + depositInfo.network);            // "Network: stellar"
-   * System.out.println("CurrencyName: " + depositInfo.currencyName);  // "CurrencyName: stellar:USDC:"
-   * System.out.println("Extra: " + depositInfo.extra);                // "Extra: null"
-   * }
- * - * @param config an object containing all configuration options needed for an external user to - * make a deposit to the desired internal account. Different fields may be mandatory depending - * on the interface implementation. - * @return asynchronous stream with the info needed to make the deposit. - * @throws HttpException If the http response status code is 4xx or 5xx or if the configuration is - * not supported by the network. - */ - Mono getDepositInstructions(DepositRequirements config) throws 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 deleted file mode 100644 index bfaeaf24a1..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/config/CirclePaymentConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -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/circle/CircleGsonParsable.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleGsonParsable.java deleted file mode 100644 index aee6b945b0..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleGsonParsable.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle; - -import com.google.gson.Gson; -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 { - Gson gson = - GsonUtils.builder() - .registerTypeAdapter(CircleTransfer.class, new CircleTransfer.Serialization()) - .registerTypeAdapter(CirclePayout.class, new CirclePayout.Deserializer()) - .registerTypeAdapter(CirclePayment.class, new CirclePayment.Deserializer()) - .create(); -} 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 deleted file mode 100644 index 8b064eea3f..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentObserverService.java +++ /dev/null @@ -1,288 +0,0 @@ -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; -import java.math.BigDecimal; -import java.util.List; -import java.util.Objects; -import okhttp3.OkHttpClient; -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; -import org.stellar.anchor.api.exception.UnprocessableEntityException; -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; -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; -import org.stellar.sdk.Network; -import org.stellar.sdk.Server; -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; - -public class CirclePaymentObserverService { - private final OkHttpClient httpClient; - private final String usdcIssuer; - private final Server horizonServer; - private final String trackedWallet; - private final List observers; - - private final Gson gson = - GsonUtils.builder() - .registerTypeAdapter(CircleTransfer.class, new CircleTransfer.Serialization()) - .setPrettyPrinting() - .create(); - - public CirclePaymentObserverService( - OkHttpClient httpClient, - PaymentObserverConfig circlePaymentObserverConfig, - Horizon horizon, - List observers) { - this.httpClient = httpClient; - Network stellarNetwork = - StellarNetworkHelper.toStellarNetwork(horizon.getStellarNetworkPassphrase()); - String[] assetIdPieces = CircleAsset.stellarUSDC(stellarNetwork).split(":"); - this.usdcIssuer = assetIdPieces[assetIdPieces.length - 1]; - this.horizonServer = horizon.getServer(); - this.trackedWallet = circlePaymentObserverConfig.getTrackedWallet(); - this.observers = observers; - } - - public void handleCircleNotification(CircleNotification circleNotification) - throws UnprocessableEntityException, BadRequestException, ServerErrorException, - EventPublishException { - String type = Objects.toString(circleNotification.getType(), ""); - - switch (type) { - case "SubscriptionConfirmation": - handleSubscriptionConfirmationNotification(circleNotification); - return; - - case "Notification": - handleTransferNotification(circleNotification); - return; - - default: - throw new UnprocessableEntityException( - "Not handling notification of unsupported type \"" + type + "\"."); - } - } - - /** - * This will auto-subscribe to Circle when we receive a subscription available notification. - * - * @param circleNotification is the circle notification object. - * @throws BadRequestException when the incoming circle subscription notification does not contain - * a SubscribeURL, or it can't be reached. - */ - public void handleSubscriptionConfirmationNotification(CircleNotification circleNotification) - throws BadRequestException { - String subscribeUrl = circleNotification.getSubscribeUrl(); - 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) - .header("Content-Type", "application/json") - .get() - .build(); - Response response; - try { - response = httpClient.newCall(httpRequest).execute(); - } catch (IOException e) { - throw new BadRequestException("Failed to call \"SubscribeURL\" endpoint."); - } - - if (!response.isSuccessful()) { - try (ResponseBody responseBody = response.body()) { - if (responseBody != null) { - Log.error(responseBody.string()); - } - } catch (IOException e) { - Log.errorEx(e); - } - throw new BadRequestException("Calling the \"SubscribeURL\" endpoint didn't succeed."); - } - - Log.info("Successfully called subscribeUrl and got status code ", response.code()); - } - - /** - * Handle incoming circle notifications of type "transfers". A transfer notification can contain - * circle<>circle or circle<>stellar events. - * - * @param circleNotification is the circle notification object. - * @throws BadRequestException when the incoming notification format is inconsistent with Circle - * documentation. This will return an error to Circle and Circle will try to submit the - * notification again. - * @throws UnprocessableEntityException when the incoming notification doesn't match the - * characteristics we want to watch. This should not return any error to Circle. - * @throws ServerErrorException when there's an error trying to fetch the Stellar network. - */ - public void handleTransferNotification(CircleNotification circleNotification) - throws BadRequestException, UnprocessableEntityException, ServerErrorException, - EventPublishException { - if (circleNotification.getMessage() == null) { - throw new BadRequestException("Notification body of type Notification is missing a message."); - } - - TransferNotificationBody transferNotification = - gson.fromJson(circleNotification.getMessage(), TransferNotificationBody.class); - - String notificationType = transferNotification.getNotificationType(); - if (!Objects.equals("transfers", notificationType)) { - throw new UnprocessableEntityException( - String.format("Won't handle notification of type \"%s\".", notificationType)); - } - - CircleTransfer circleTransfer = transferNotification.getTransfer(); - if (circleTransfer == null) { - throw new BadRequestException( - "Missing \"transfer\" value in notification of type \"transfers\"."); - } - - if (!CirclePaymentStatus.COMPLETE.equals(circleTransfer.getStatus())) { - throw new UnprocessableEntityException("Not a complete transfer."); - } - - Log.info("Completed transfer:\n" + gson.toJson(circleTransfer)); - CircleTransactionParty source = circleTransfer.getSource(); - boolean isSourceOnStellar = - source.getType().equals(CircleTransactionParty.Type.BLOCKCHAIN) - && source.getChain().equals("XLM"); - CircleTransactionParty destination = circleTransfer.getDestination(); - boolean isDestinationOnStellar = - destination.getType().equals(CircleTransactionParty.Type.BLOCKCHAIN) - && destination.getChain().equals("XLM"); - if (!isSourceOnStellar && !isDestinationOnStellar) { - throw new UnprocessableEntityException( - "Neither source nor destination are Stellar accounts."); - } - - if (!isWalletTracked(source) && !isWalletTracked(destination)) { - throw new UnprocessableEntityException("None of the transfer wallets is being tracked."); - } - - if (!circleTransfer.getAmount().getCurrency().equals("USD")) { - throw new UnprocessableEntityException("The only supported Circle currency is USDC."); - } - - ObservedPayment observedPayment; - try { - observedPayment = fetchCircleTransferOnStellar(circleTransfer); - } catch (IOException ex) { - throw new ServerErrorException( - "Something went wrong when trying to fetch the Stellar network", ex); - } catch (SepException ex) { - String exMessage = - String.format( - "Payment from transaction %s contains an unsupported memo.", - circleTransfer.getTransactionHash()); - throw new UnprocessableEntityException(exMessage, ex); - } - - if (observedPayment == null) { - throw new UnprocessableEntityException("Observed payment could not be fetched."); - } - - if (isWalletTracked(destination)) { - for (PaymentListener listener : observers) { - listener.onReceived(observedPayment); - } - } else { - final ObservedPayment finalObservedPayment1 = observedPayment; - observers.forEach(observer -> observer.onSent(finalObservedPayment1)); - } - } - - public boolean isWalletTracked(CircleTransactionParty party) { - if (party.getType() != CircleTransactionParty.Type.WALLET) { - return false; - } - - if (Objects.equals(trackedWallet, "all")) { - return true; - } - - return Objects.equals(trackedWallet, party.getId()); - } - - /** - * This will fetch the Stellar payment (or path payment) that originated the Circle transfer. - * - * @param circleTransfer the Circle transfer - * @return an ObservedPayment or null if unable to convert - * @throws IOException if an error happens fetching data from Stellar - */ - public ObservedPayment fetchCircleTransferOnStellar(CircleTransfer circleTransfer) - throws IOException, SepException { - String txHash = circleTransfer.getTransactionHash(); - Page responsePage = - horizonServer - .payments() - .forTransaction(txHash) - .limit(200) - .includeTransactions(true) - .execute(); - - for (OperationResponse opResponse : responsePage.getRecords()) { - if (!opResponse.isTransactionSuccessful()) continue; - - if (!List.of("payment", "path_payment_strict_send", "path_payment_strict_receive") - .contains(opResponse.getType())) continue; - - ObservedPayment observedPayment; - if (opResponse instanceof PaymentOperationResponse) { - PaymentOperationResponse payment = (PaymentOperationResponse) opResponse; - observedPayment = ObservedPayment.fromPaymentOperationResponse(payment); - } else if (opResponse instanceof PathPaymentBaseOperationResponse) { - PathPaymentBaseOperationResponse pathPayment = - (PathPaymentBaseOperationResponse) opResponse; - observedPayment = ObservedPayment.fromPathPaymentOperationResponse(pathPayment); - } else { - continue; - } - - if (!observedPayment.assetCode.equals("USDC") - || !observedPayment.assetIssuer.equals(usdcIssuer)) { - continue; - } - - BigDecimal transferAmount = decimal(circleTransfer.getAmount().getAmount()); - BigDecimal paymentAmount = decimal(observedPayment.amount); - if (transferAmount.compareTo(paymentAmount) != 0) { - continue; - } - - observedPayment.setExternalTransactionId(circleTransfer.getId()); - observedPayment.setType(ObservedPayment.Type.CIRCLE_TRANSFER); - return observedPayment; - } - - return null; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java deleted file mode 100644 index 3bfeb28803..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CirclePaymentService.java +++ /dev/null @@ -1,768 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle; - -import static org.stellar.anchor.util.StellarNetworkHelper.toStellarNetwork; - -import com.google.gson.JsonObject; -import io.netty.handler.codec.http.HttpHeaderNames; -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.util.*; -import java.util.stream.Collectors; -import org.apache.commons.validator.routines.EmailValidator; -import org.stellar.anchor.api.exception.HttpException; -import org.stellar.anchor.config.CircleConfig; -import org.stellar.anchor.horizon.Horizon; -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 reactor.core.publisher.Mono; -import reactor.netty.ByteBufMono; -import reactor.netty.http.client.HttpClient; -import reactor.util.annotation.NonNull; -import reactor.util.annotation.Nullable; -import shadow.com.google.common.reflect.TypeToken; - -public class CirclePaymentService - implements PaymentService, CircleResponseErrorHandler, StellarReconciliation { - private final CirclePaymentConfig circlePaymentConfig; - - private final CircleConfig circleConfig; - - private final Server horizonServer; - - private final Network stellarNetwork; - - private final PaymentNetwork paymentNetwork = PaymentNetwork.CIRCLE; - - private HttpClient webClient; - - private String mainAccountAddress; - - /** - * For all service methods to work correctly, make sure your circle account has a valid business - * wallet and a bank account configured. - */ - public CirclePaymentService( - CirclePaymentConfig circlePaymentConfig, CircleConfig circleConfig, Horizon horizon) { - super(); - this.circlePaymentConfig = circlePaymentConfig; - this.circleConfig = circleConfig; - this.horizonServer = horizon.getServer(); - - this.stellarNetwork = toStellarNetwork(horizon.getStellarNetworkPassphrase()); - } - - @Override - public Server getHorizonServer() { - return horizonServer; - } - - @Override - public PaymentNetwork getPaymentNetwork() { - return this.paymentNetwork; - } - - @Override - public String getName() { - return circlePaymentConfig.getName(); - } - - public HttpClient getWebClient(boolean authenticated) { - if (webClient == null) { - this.webClient = NettyHttpClient.withBaseUrl(this.circleConfig.getCircleUrl()); - } - if (!authenticated) { - return webClient; - } - return webClient.headers( - h -> h.add(HttpHeaderNames.AUTHORIZATION, "Bearer " + this.circleConfig.getApiKey())); - } - - /** - * API request that pings the server to make sure it's up and running. - * - * @return asynchronous stream with a Void value. If no exception is thrown it means the request - * was successful and the remote server is operational. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - public Mono ping() throws HttpException { - return getWebClient(false) - .get() - .uri("/ping") - .responseSingle(handleResponseSingle()) - .flatMap(s -> Mono.empty()); - } - - /** - * API request that returns the id of the distribution account managed by the secret key. - * - * @return asynchronous stream with the id of the distribution account managed by the secret key. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - public Mono getDistributionAccountAddress() throws HttpException { - if (mainAccountAddress != null) { - return Mono.just(mainAccountAddress); - } - - return getWebClient(true) - .get() - .uri("/v1/configuration") - .responseSingle(handleResponseSingle()) - .map( - body -> { - CircleConfigurationResponse response = - gson.fromJson(body, CircleConfigurationResponse.class); - mainAccountAddress = response.getData().payments.masterWalletId; - return mainAccountAddress; - }); - } - - /** - * Get the merchant account unsettled balances in circle - * - * @return asynchronous stream with a list of the unsettled balances. - */ - @NonNull - private Mono> getMerchantAccountUnsettledBalances() { - return getWebClient(true) - .get() - .uri("/v1/businessAccount/balances") - .responseSingle(handleResponseSingle()) - .map( - body -> { - CircleAccountBalancesResponse response = - gson.fromJson(body, CircleAccountBalancesResponse.class); - - List unsettledBalances = new ArrayList<>(); - for (CircleBalance uBalance : response.getData().unsettled) { - unsettledBalances.add(uBalance.toBalance(PaymentNetwork.CIRCLE)); - } - - return unsettledBalances; - }); - } - - /** - * API request that retrieves the circle wallet with the given id. - * - * @param walletId is the existing wallet identifier. - * @return asynchronous stream with the CircleWallet object. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - @NonNull - private Mono getCircleWallet(String walletId) throws HttpException { - return getWebClient(true) - .get() - .uri("/v1/wallets/" + walletId) - .responseSingle(handleResponseSingle()) - .map( - body -> { - Type type = new TypeToken>() {}.getType(); - CircleDetailResponse circleWalletResponse = gson.fromJson(body, type); - return circleWalletResponse.getData(); - }); - } - - /** - * API request that retrieves the account with the given id. - * - * @param accountId is the existing account identifier of the circle account. - * @return asynchronous stream with the account object. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - public Mono getAccount(String accountId) throws HttpException { - return getDistributionAccountAddress() - .flatMap( - distAccountId -> { - Mono> unsettledBalancesMono = Mono.just(new ArrayList<>()); - if (distAccountId.equals(accountId)) { - unsettledBalancesMono = getMerchantAccountUnsettledBalances(); - } - return Mono.zip(unsettledBalancesMono, getCircleWallet(accountId)); - }) - .map( - args -> { - List unsettledBalances = args.getT1(); - CircleWallet circleWallet = args.getT2(); - Account account = circleWallet.toAccount(); - account.setUnsettledBalances(unsettledBalances); - return account; - }); - } - - /** - * API request that creates an account with the given id. - * - * @param accountId is the identifier of the account to be created. It is used as an optional - * description in the Circle implementation. - * @return asynchronous stream with the account object. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - public Mono createAccount(@Nullable String accountId) throws HttpException { - JsonObject postBody = new JsonObject(); - postBody.addProperty("idempotencyKey", UUID.randomUUID().toString()); - if (accountId != null && !accountId.isEmpty()) { - postBody.addProperty("description", accountId); - } - - return getWebClient(true) - .post() - .uri("/v1/wallets") - .send(ByteBufMono.fromString(Mono.just(postBody.toString()))) - .responseSingle(handleResponseSingle()) - .map( - body -> { - Type type = new TypeToken>() {}.getType(); - CircleDetailResponse circleWalletResponse = gson.fromJson(body, type); - CircleWallet circleWallet = circleWalletResponse.getData(); - return circleWallet.toAccount(); - }); - } - - public Mono getTransfers( - String accountID, String beforeCursor, String afterCursor, Integer pageSize) - throws HttpException { - // build query parameters for GET requests - int _pageSize = pageSize != null ? pageSize : 50; - LinkedHashMap queryParams = new LinkedHashMap<>(); - queryParams.put("pageSize", Integer.toString(_pageSize)); - queryParams.put("walletId", accountID); - - if (afterCursor != null && !afterCursor.isEmpty()) { - queryParams.put("pageAfter", afterCursor); - // we can't use both pageBefore and pageAfter at the same time, that's why I'm using 'else if' - } else if (beforeCursor != null && !beforeCursor.isEmpty()) { - queryParams.put("pageBefore", beforeCursor); - } - - return getWebClient(true) - .get() - .uri(NettyHttpClient.buildUri("/v1/transfers", queryParams)) - .responseSingle(handleResponseSingle()) - .flatMap( - body -> { - CircleTransferListResponse response = - gson.fromJson(body, CircleTransferListResponse.class); - Mono originalResponseMono = Mono.just(response); - - // Build Mono.zip to retrieve the Stellar info from Stellar->CircleWallet transfers in - // order to update the sender address that's not disclosed in Circle's API. - List> monoList = - response.getData().stream() - .map(this::updatedStellarSenderAddress) - .collect(Collectors.toList()); - Mono> updatedTransfersMono = Mono.just(new ArrayList<>()); - if (monoList.size() > 0) { - updatedTransfersMono = - Mono.zip( - monoList, - objects -> List.of(Arrays.stream(objects).toArray(CircleTransfer[]::new))); - } - - return Mono.zip(originalResponseMono, updatedTransfersMono); - }) - .map( - args -> { - CircleTransferListResponse response = args.getT1(); - List updatedTransfers = args.getT2(); - - response.setData(updatedTransfers); - return response; - }); - } - - public Mono getPayouts( - String accountID, String beforeCursor, String afterCursor, Integer pageSize) - throws HttpException { - // build query parameters for GET requests - int _pageSize = pageSize != null ? pageSize : 50; - LinkedHashMap queryParams = new LinkedHashMap<>(); - queryParams.put("pageSize", Integer.toString(_pageSize)); - queryParams.put("source", accountID); - - if (afterCursor != null && !afterCursor.isEmpty()) { - queryParams.put("pageAfter", afterCursor); - // we can't use both pageBefore and pageAfter at the same time, that's why I'm using 'else if' - } else if (beforeCursor != null && !beforeCursor.isEmpty()) { - queryParams.put("pageBefore", beforeCursor); - } - - return getWebClient(true) - .get() - .uri(NettyHttpClient.buildUri("/v1/payouts", queryParams)) - .responseSingle(handleResponseSingle()) - .map(body -> gson.fromJson(body, CirclePayoutListResponse.class)); - } - - public Mono getIncomingPayments( - @NonNull String accountID, String beforeCursor, String afterCursor, Integer pageSize) { - return getDistributionAccountAddress() - .flatMap( - distributionAccountId -> { - if (!distributionAccountId.equals(accountID)) { - return Mono.just(new CirclePaymentListResponse()); - } - - // build query parameters for GET requests - int _pageSize = pageSize != null ? pageSize : 50; - LinkedHashMap queryParams = new LinkedHashMap<>(); - queryParams.put("pageSize", Integer.toString(_pageSize)); - - if (afterCursor != null && !afterCursor.isEmpty()) { - queryParams.put("pageAfter", afterCursor); - // we can't use both pageBefore and pageAfter at the same time, that's why I'm using - // 'else if' - } else if (beforeCursor != null && !beforeCursor.isEmpty()) { - queryParams.put("pageBefore", beforeCursor); - } - - return getWebClient(true) - .get() - .uri(NettyHttpClient.buildUri("/v1/payments", queryParams)) - .responseSingle(handleResponseSingle()) - .map(body -> gson.fromJson(body, CirclePaymentListResponse.class)); - }); - } - - /** - * API request that returns the history of payments involving a given account. - * - * @param accountID the id of the account whose payment history we want to fetch. - * @param beforeCursor the value used to limit payments to only those before it - * @param afterCursor the value used to limit payments to only those before it - * @return asynchronous stream with the payment history. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - public Mono getAccountPaymentHistory( - String accountID, @Nullable String beforeCursor, @Nullable String afterCursor) - throws HttpException { - // Parse cursor - String beforeTransfer = null, beforePayout = null, beforePayment = null; - String afterTransfer = null, afterPayout = null, afterPayment = null; - if (beforeCursor != null) { - String[] beforeCursors = beforeCursor.split(":"); - if (beforeCursors.length < 3) { - throw new HttpException(400, "invalid before cursor"); - } - beforeTransfer = beforeCursors[0]; - beforePayout = beforeCursors[1]; - beforePayment = beforeCursors[2]; - } - if (afterCursor != null) { - String[] afterCursors = afterCursor.split(":"); - if (afterCursors.length < 3) { - throw new HttpException(400, "invalid after cursor"); - } - afterTransfer = afterCursors[0]; - afterPayout = afterCursors[1]; - afterPayment = afterCursors[2]; - } - - int pageSize = 50; - return Mono.zip( - getDistributionAccountAddress(), - getTransfers(accountID, beforeTransfer, afterTransfer, pageSize), - getPayouts(accountID, beforePayout, afterPayout, pageSize), - getIncomingPayments(accountID, beforePayment, afterPayment, pageSize)) - .map( - args -> { - String distributionAccId = args.getT1(); - boolean isMerchantAccount = distributionAccId.equals(accountID); - Account.Capabilities capabilities = - isMerchantAccount - ? CircleWallet.merchantAccountCapabilities() - : CircleWallet.defaultCapabilities(); - Account account = new Account(PaymentNetwork.CIRCLE, accountID, capabilities); - - PaymentHistory transfersHistory = - args.getT2().toPaymentHistory(pageSize, account, distributionAccId); - PaymentHistory payoutsHistory = args.getT3().toPaymentHistory(pageSize, account); - PaymentHistory paymentsHistory = args.getT4().toPaymentHistory(pageSize, account); - PaymentHistory result = new PaymentHistory(account); - - String befTransfer = Objects.toString(transfersHistory.getBeforeCursor(), ""); - String befPayout = Objects.toString(payoutsHistory.getBeforeCursor(), ""); - String befPayment = Objects.toString(paymentsHistory.getBeforeCursor(), ""); - if (!befTransfer.isEmpty() || !befPayout.isEmpty() || !befPayment.isEmpty()) { - result.setBeforeCursor(befTransfer + ":" + befPayout + ":" + befPayment); - } - - String aftTransfer = Objects.toString(transfersHistory.getAfterCursor(), ""); - String aftPayout = Objects.toString(payoutsHistory.getAfterCursor(), ""); - String aftPayment = Objects.toString(paymentsHistory.getAfterCursor(), ""); - if (!aftTransfer.isEmpty() || !aftPayout.isEmpty() || !aftPayment.isEmpty()) { - result.setAfterCursor(aftTransfer + ":" + aftPayout + ":" + aftPayment); - } - - List allPayments = new ArrayList<>(); - allPayments.addAll(transfersHistory.getPayments()); - allPayments.addAll(payoutsHistory.getPayments()); - allPayments.addAll(paymentsHistory.getPayments()); - allPayments = - allPayments.stream() - .sorted((p1, p2) -> p2.getCreatedAt().compareTo(p1.getCreatedAt())) - .collect(Collectors.toList()); - for (Payment p : allPayments) { - updatePaymentWireCapability(p, distributionAccId); - } - result.setPayments(allPayments); - - return result; - }); - } - - /** - * Validates if the fields needed to send a payment are valid. - * - * @param sourceAccount the account where the payment will be sent from. - * @param destinationAccount the account that will receive the payment. - * @param currencyName the name of the currency used in the payment. It should obey the - * {scheme}:{identifier} format described in SEP-38. - * @throws HttpException if the source account network is not CIRCLE. - * @throws HttpException if the destination account network is not supported. - * @throws HttpException if the destination account is a bank and the idTag is not a valid email. - * @throws HttpException if the currencyName prefix does not reflect the destination account - * network. - */ - private void validateSendPaymentInput( - @NonNull Account sourceAccount, - @NonNull Account destinationAccount, - @NonNull String currencyName) - throws HttpException { - if (sourceAccount.paymentNetwork != PaymentNetwork.CIRCLE) { - throw new HttpException(400, "the only supported network for the source account is circle"); - } - if (!List.of(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR, PaymentNetwork.BANK_WIRE) - .contains(destinationAccount.paymentNetwork)) { - throw new HttpException( - 400, - "the only supported networks for the destination account are circle, stellar and bank_wire"); - } - if (destinationAccount.paymentNetwork == PaymentNetwork.BANK_WIRE - && !EmailValidator.getInstance().isValid(destinationAccount.idTag)) { - throw new HttpException( - 400, - "for bank transfers, please provide a valid beneficiary email address in the destination idTag"); - } - if (!currencyName.startsWith(destinationAccount.paymentNetwork.getCurrencyPrefix())) { - throw new HttpException( - 400, "the currency to be sent must contain the destination network schema"); - } - if (!CircleAsset.isSupported(currencyName, stellarNetwork)) { - throw new HttpException( - 400, - String.format( - "the only supported currencies are %s, %s and %s.", - "circle:USD", "iso4217:USD", CircleAsset.stellarUSDC(stellarNetwork))); - } - } - - /** - * API request that sends a Circle transfer, i.e. a payment from a Circle account to another - * Circle account or to a blockchain wallet. - * - * @param sourceAccount the account where the payment will be sent from. - * @param destinationAccount the account that will receive the payment, it can be an internal - * Circle account or a Stellar wallet. - * @param balance the balance to be transferred. - * @return asynchronous stream with the payment object. - * @throws HttpException If the Circle http response status code is 4xx or 5xx. - * @throws HttpException If the destination network is not a Circle account nor a Stellar wallet. - */ - private Mono sendTransfer( - Account sourceAccount, Account destinationAccount, CircleBalance balance) - throws HttpException { - CircleTransactionParty source = CircleTransactionParty.wallet(sourceAccount.getId()); - CircleTransactionParty destination; - switch (destinationAccount.paymentNetwork) { - case CIRCLE: - destination = CircleTransactionParty.wallet(destinationAccount.getId()); - break; - case STELLAR: - destination = - CircleTransactionParty.stellar(destinationAccount.id, destinationAccount.getIdTag()); - break; - default: - throw new HttpException( - 400, "the destination network is not supported for Circle transfers"); - } - CircleSendTransactionRequest req = - CircleSendTransactionRequest.forTransfer( - source, destination, balance, UUID.randomUUID().toString()); - String jsonBody = gson.toJson(req); - - return getWebClient(true) - .post() - .uri("/v1/transfers") - .send(ByteBufMono.fromString(Mono.just(jsonBody))) - .responseSingle(handleResponseSingle()) - .flatMap(body -> Mono.zip(getDistributionAccountAddress(), Mono.just(body))) - .map( - args -> { - String distributionAccountId = args.getT1(); - String body = args.getT2(); - Type type = new TypeToken>() {}.getType(); - CircleDetailResponse transfer = gson.fromJson(body, type); - return transfer.getData().toPayment(distributionAccountId); - }); - } - - /** - * API request that sends a Circle payout, i.e. a payment from a Circle wallet to a bank account - * registered in Circle. - * - * @param sourceAccount the account where the payment will be sent from. - * @param destinationAccount the bank wire account that will receive the payment. - * @param balance the balance to be transferred. - * @return asynchronous stream with the payment object. - * @throws HttpException If the Circle http response status code is 4xx or 5xx. - * @throws HttpException If the destination network is not a bank wire. - */ - private Mono sendPayout( - Account sourceAccount, Account destinationAccount, CircleBalance balance) - throws HttpException { - if (destinationAccount.paymentNetwork != PaymentNetwork.BANK_WIRE) { - throw new HttpException( - 500, "something went wrong, the destination account network is invalid"); - } - - CircleTransactionParty source = CircleTransactionParty.wallet(sourceAccount.getId()); - CircleTransactionParty destination = - CircleTransactionParty.wire(destinationAccount.id, destinationAccount.idTag); - CircleSendTransactionRequest req = - CircleSendTransactionRequest.forPayout( - source, destination, balance, UUID.randomUUID().toString()); - String jsonBody = gson.toJson(req); - - return getWebClient(true) - .post() - .uri("/v1/payouts") - .send(ByteBufMono.fromString(Mono.just(jsonBody))) - .responseSingle(handleResponseSingle()) - .map( - body -> { - Type type = new TypeToken>() {}.getType(); - CircleDetailResponse payout = gson.fromJson(body, type); - return payout.getData().toPayment(); - }); - } - - private void updatePaymentWireCapability(Payment payment, String distributionAccountId) { - if (distributionAccountId == null) { - return; - } - - // fill source account level - Account sourceAcc = payment.getSourceAccount(); - Boolean isSourceWireEnabled = - sourceAcc.paymentNetwork.equals(PaymentNetwork.BANK_WIRE) - || distributionAccountId.equals(sourceAcc.id); - sourceAcc.capabilities.getReceive().put(PaymentNetwork.BANK_WIRE, isSourceWireEnabled); - - // fill destination account level - Account destinationAcc = payment.getDestinationAccount(); - Boolean isDestinationWireEnabled = - destinationAcc.paymentNetwork.equals(PaymentNetwork.BANK_WIRE) - || distributionAccountId.equals(destinationAcc.id); - destinationAcc - .capabilities - .getReceive() - .put(PaymentNetwork.BANK_WIRE, isDestinationWireEnabled); - } - - /** - * API request that executes a payment between accounts. The APIKey needs to have access to the - * source account for this request to succeed. - * - * @param sourceAccount the account making the payment. Only the network and id fields are needed. - * @param destinationAccount the account receiving the payment. The network field and a subset of - * (id, address and addressTag) may be needed. - * @param currencyName the name of the currency used in the payment. It should obey the - * {scheme}:{identifier} format described in SEP-38. - * @param amount the payment amount. - * @return asynchronous stream with the payment object. - * @throws HttpException If the provided input parameters are invalid. - * @throws HttpException If the http response status code is 4xx or 5xx. - */ - public Mono sendPayment( - Account sourceAccount, Account destinationAccount, String currencyName, BigDecimal amount) - throws HttpException { - // validate input - validateSendPaymentInput(sourceAccount, destinationAccount, currencyName); - - CircleBalance circleBalance = new CircleBalance("USD", amount.toString(), stellarNetwork); - - switch (destinationAccount.paymentNetwork) { - case CIRCLE: - case STELLAR: - return sendTransfer(sourceAccount, destinationAccount, circleBalance); - case BANK_WIRE: - return sendPayout(sourceAccount, destinationAccount, circleBalance); - default: - throw new RuntimeException( - "unsupported destination network '" + destinationAccount.paymentNetwork + "'"); - } - } - - public Mono> getListOfAddresses( - @NonNull String walletId) { - return getWebClient(true) - .get() - .uri("/v1/wallets/" + walletId + "/addresses") - .responseSingle(handleResponseSingle()) - .map( - body -> { - Type type = new TypeToken>() {}.getType(); - return gson.fromJson(body, type); - }); - } - - public Mono> createNewStellarAddress( - @NonNull String walletId) { - JsonObject postBody = new JsonObject(); - postBody.addProperty("idempotencyKey", UUID.randomUUID().toString()); - postBody.addProperty("currency", "USD"); - postBody.addProperty("chain", "XLM"); - - return getWebClient(true) - .post() - .send(ByteBufMono.fromString(Mono.just(postBody.toString()))) - .uri("/v1/wallets/" + walletId + "/addresses") - .responseSingle(handleResponseSingle()) - .map( - body -> { - Type type = - new TypeToken>() {}.getType(); - return gson.fromJson(body, type); - }); - } - - public Mono getOrCreateStellarAddress(@NonNull String walletId) { - return getListOfAddresses(walletId) - .flatMap( - addressListResponse -> { - for (CircleBlockchainAddress address : addressListResponse.getData()) { - if (address.getChain().equals("XLM") && address.getCurrency().equals("USD")) { - return Mono.just(address); - } - } - - return createNewStellarAddress(walletId) - .map(CircleDetailResponse::getData); - }); - } - - public Mono> getWireDepositInstructions( - @NonNull String walletId, @NonNull String bankWireId) { - return getDistributionAccountAddress() - .flatMap( - distributionAccountId -> { - if (!distributionAccountId.equals(walletId)) { - return Mono.error( - new HttpException( - 400, - "in circle, only the distribution account id can receive wire payments")); - } - - return getWebClient(true) - .get() - .uri("/v1/banks/wires/" + bankWireId + "/instructions") - .responseSingle(handleResponseSingle()) - .map( - body -> { - Type type = - new TypeToken< - CircleDetailResponse>() {}.getType(); - return gson.fromJson(body, type); - }); - }); - } - - private void validateDepositRequirements(@NonNull DepositRequirements config) - throws HttpException { - String beneficiaryId = config.getBeneficiaryAccountId(); - if (beneficiaryId == null || beneficiaryId.isEmpty()) { - throw new HttpException(400, "beneficiary account id cannot be empty"); - } - - if (!CircleAsset.circleUSD().equals(config.getBeneficiaryCurrencyName())) { - throw new HttpException( - 400, "the only receiving currency in a circle account is \"circle:USD\""); - } - - PaymentNetwork intermediaryNetwork = config.getIntermediaryPaymentNetwork(); - if (intermediaryNetwork == null - || !List.of(PaymentNetwork.STELLAR, PaymentNetwork.CIRCLE, PaymentNetwork.BANK_WIRE) - .contains(intermediaryNetwork)) { - throw new HttpException( - 400, - "the only supported intermediary payment networks are \"stellar\", \"circle\" and \"bank_wire\""); - } - - if (PaymentNetwork.BANK_WIRE.equals(intermediaryNetwork) - && config.getIntermediaryAccountId() == null) { - throw new HttpException( - 400, - "please provide a valid Circle bank id for the intermediaryAccountId field when requesting instructions for bank wire deposits"); - } - } - - /** - * API request that returns the info needed to make a deposit into a user account. This method - * will be needed if the implementation allows users to make deposits using external networks. For - * instance, when a user wants to make a deposit to their Circle account through a Stellar - * payment: - * - *
{@code
-   * // Here we want to check how we can top up a Circle account using USDC funds from the Stellar network.
-   * String circleWalletId = "1000066041";
-   * Network fromNetwork = Network.STELLAR;
-   * String currencyName = "USD";  // or "USDC"
-   * DepositConfiguration config = new DepositConfiguration(circleWalletId, fromNetwork, currencyName);
-   *
-   * // Here are the instructions with the Stellar account that will receive the payment:
-   * DepositInfo depositInfo = getInfoForDeposit(config).block();
-   * System.out.println("PublicKey: " + depositInfo.accountId);        // "PublicKey: G..."
-   * System.out.println("Memo: " + depositInfo.accountIdTag);          // "Memo: 2454278437550473431"
-   * System.out.println("Network: " + depositInfo.network);            // "Network: stellar"
-   * System.out.println("CurrencyName: " + depositInfo.currencyName);  // "CurrencyName: stellar:USDC:"
-   * System.out.println("Extra: " + depositInfo.extra);                // "Extra: null"
-   * }
- * - * @param config an object containing all configuration options needed for an external user to - * make a deposit to the desired internal account. Different fields may be mandatory depending - * on the interface implementation. - * @return asynchronous stream with the info needed to make the deposit. - * @throws HttpException If the http response status code is 4xx or 5xx or if the configuration is - * not supported by the network. - */ - public Mono getDepositInstructions(DepositRequirements config) - throws HttpException { - validateDepositRequirements(config); - - String walletId = config.getBeneficiaryAccountId(); - switch (config.getIntermediaryPaymentNetwork()) { - case STELLAR: - return getOrCreateStellarAddress(walletId) - .map(address -> address.toDepositInstructions(walletId, stellarNetwork)); - - case CIRCLE: - return Mono.just(new CircleWallet(walletId).toDepositInstructions()); - - case BANK_WIRE: - return getWireDepositInstructions(walletId, config.getIntermediaryAccountId()) - .map(response -> response.getData().toDepositInstructions(walletId)); - - default: - return null; - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java deleted file mode 100644 index d0f9c8ca74..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/CircleResponseErrorHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -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.platform.payment.observer.circle.model.response.CircleError; -import reactor.core.publisher.Mono; -import reactor.netty.ByteBufMono; -import reactor.netty.http.client.HttpClientResponse; - -public interface CircleResponseErrorHandler extends CircleGsonParsable { - @NotNull - default BiFunction> handleResponseSingle() { - return (response, bodyBytesMono) -> { - if (response.status().code() >= 400) { - return bodyBytesMono - .asString() - .map( - body -> { - CircleError circleError = gson.fromJson(body, CircleError.class); - throw new HttpException( - response.status().code(), - circleError.getMessage(), - circleError.getCode().toString()); - }); - } - - return bodyBytesMono.asString(); - }; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java deleted file mode 100644 index 75838f9d67..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/StellarReconciliation.java +++ /dev/null @@ -1,69 +0,0 @@ -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.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; -import org.stellar.sdk.responses.operations.PaymentOperationResponse; -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; - -public interface StellarReconciliation extends CircleResponseErrorHandler { - - HttpClient getWebClient(boolean authenticated); - - Server getHorizonServer(); - - default Mono updatedStellarSenderAddress(CircleTransfer transfer) - throws HttpException { - CircleTransfer transferCopy = gson.fromJson(gson.toJson(transfer), CircleTransfer.class); - - if (transferCopy.getSource().getId() != null) return Mono.just(transferCopy); - - // Only Stellar->CircleWallet transfers should arrive here with id == null, and they should also - // have chain == "XLM" and type == BLOCKCHAIN. Let's validate if that's true: - if (!transferCopy.getSource().getChain().equals("XLM") - || transferCopy.getSource().getType() != CircleTransactionParty.Type.BLOCKCHAIN) { - throw new HttpException(500, "invalid source account"); - } - - String txHash = transferCopy.getTransactionHash(); - return Mono.fromCallable(() -> getHorizonServer().payments().forTransaction(txHash).execute()) - .mapNotNull( - responsePage -> { - for (OperationResponse opResponse : responsePage.getRecords()) { - if (!opResponse.isTransactionSuccessful()) continue; - - if (!List.of("payment", "path_payment_strict_send", "path_payment_strict_receive") - .contains(opResponse.getType())) continue; - - String amount, from; - if ("payment".equals(opResponse.getType())) { - PaymentOperationResponse paymentResponse = (PaymentOperationResponse) opResponse; - amount = paymentResponse.getAmount(); - from = paymentResponse.getFrom(); - } else { - PathPaymentBaseOperationResponse pathPaymentResponse = - (PathPaymentBaseOperationResponse) opResponse; - amount = pathPaymentResponse.getAmount(); - from = pathPaymentResponse.getFrom(); - } - - BigDecimal wantAmount = decimal(transferCopy.getAmount().getAmount()); - BigDecimal gotAmount = decimal(amount); - if (wantAmount.compareTo(gotAmount) != 0) continue; - - transferCopy.getSource().setAddress(from); - return transferCopy; - } - - return null; - }); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java deleted file mode 100644 index 108eb4bd4b..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBalance.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import lombok.Data; -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; - -@Data -public class CircleBalance { - @NonNull String amount; - @NonNull String currency; - @Nullable org.stellar.sdk.Network stellarNetwork; - - public CircleBalance( - @NonNull String currency, - @NonNull String amount, - @Nullable org.stellar.sdk.Network stellarNetwork) { - this.currency = currency; - this.amount = amount; - this.stellarNetwork = stellarNetwork; - } - - public CircleBalance(@NonNull String currency, @NonNull String amount) { - this(currency, amount, null); - } - - public Balance toBalance(@NonNull PaymentNetwork destinationPaymentNetwork) { - String currencyName = destinationPaymentNetwork.getCurrencyPrefix() + ":" + currency; - if (currencyName.equals("stellar:USD")) currencyName = stellarUSDC(); - - return new Balance(amount, currencyName); - } - - @NonNull - public String stellarUSDC() { - return CircleAsset.stellarUSDC(stellarNetwork); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java deleted file mode 100644 index 251851cb4f..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleBlockchainAddress.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import lombok.Data; -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 reactor.util.annotation.NonNull; -import reactor.util.annotation.Nullable; - -@Data -public class CircleBlockchainAddress { - @NonNull String address; - @Nullable String addressTag; - @NonNull String currency; - @NonNull String chain; - - public CircleBlockchainAddress( - @NonNull String address, - @Nullable String addressTag, - @NonNull String currency, - @NonNull String chain) { - this.address = address; - this.addressTag = addressTag; - this.currency = currency; - this.chain = chain; - } - - public DepositInstructions toDepositInstructions( - String beneficiaryAccountId, Network stellarNetwork) { - return new DepositInstructions( - beneficiaryAccountId, - null, - PaymentNetwork.CIRCLE, - address, - addressTag, - PaymentNetwork.STELLAR, - CircleAsset.stellarUSDC(stellarNetwork), - null); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java deleted file mode 100644 index b5fc70094c..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleNotification.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import com.google.gson.annotations.SerializedName; -import java.time.Instant; -import java.util.Map; -import lombok.Data; - -@Data -public class CircleNotification { - @SerializedName("Type") - String type; - - @SerializedName("MessageId") - String messageId; - - @SerializedName("Message") - String message; - - @SerializedName("TopicArn") - String topicArn; - - @SerializedName("Timestamp") - Instant Timestamp; - - @SerializedName("SignatureVersion") - String signatureVersion; - - @SerializedName("Signature") - String signature; - - @SerializedName("SigningCertURL") - String signingCertURL; - - // For Type == "SubscriptionConfirmation" - @SerializedName("Token") - String token; - - @SerializedName("SubscribeURL") - String subscribeUrl; - - // For Type == "Notification" - @SerializedName("UnsubscribeURL") - String unsubscribeURL; - - @SerializedName("MessageAttributes") - Map messageAttributes; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java deleted file mode 100644 index 6d3a038cf5..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayment.java +++ /dev/null @@ -1,81 +0,0 @@ -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.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 shadow.com.google.common.reflect.TypeToken; - -@Data -public class CirclePayment { - String id; - String type; // payment - String merchantId; - String merchantWalletId; - CircleTransactionParty source; - String description; - CircleBalance amount; - CircleBalance fees; - CirclePaymentStatus status; - List> refunds; - Instant updateDate; - Instant createDate; - - Map riskEvaluation; - String trackingRef; - String errorCode; - Map cancel; - Map metadata; - Verification verification; - - Map originalResponse; - - @Data - public static class Verification { - String avs; - String cvv; - } - - public Payment toPayment() { - Payment p = new Payment(); - p.setId(id); - p.setSourceAccount(source.toAccount(null)); - p.setDestinationAccount( - new Account( - PaymentNetwork.CIRCLE, - merchantWalletId, - new Account.Capabilities( - PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR, PaymentNetwork.BANK_WIRE))); - p.setBalance(amount.toBalance(PaymentNetwork.CIRCLE)); - p.setStatus(status.toPaymentStatus()); - p.setErrorCode(errorCode); - p.setCreatedAt(createDate); - p.setUpdatedAt(updateDate); - p.setOriginalResponse(originalResponse); - - return p; - } - - public static class Deserializer implements JsonDeserializer { - private static final Gson gson = GsonUtils.getInstance(); - - @Override - public CirclePayment deserialize( - JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - JsonObject jsonObject = json.getAsJsonObject(); - CirclePayment payment = gson.fromJson(jsonObject, CirclePayment.class); - - Type type = new TypeToken>() {}.getType(); - Map originalResponse = gson.fromJson(jsonObject, type); - payment.setOriginalResponse(originalResponse); - return payment; - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java deleted file mode 100644 index 9885ab355b..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePaymentStatus.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import com.google.gson.annotations.SerializedName; -import lombok.Getter; -import org.stellar.anchor.platform.payment.common.Payment; - -@Getter -public enum CirclePaymentStatus { - @SerializedName("pending") - PENDING("pending"), - - /** - * Confirmed means almost completed in Circle incoming payments. - * - * @link https://developers.circle.com/docs/circle-api-resources#payment-attributes - */ - @SerializedName("confirmed") - CONFIRMED("confirmed"), - - @SerializedName("failed") - FAILED("failed"), - - /** - * Paid means successful in Circle incoming payments. - * - * @link https://developers.circle.com/docs/circle-api-resources#payment-attributes - */ - @SerializedName("paid") - PAID("paid"), - - @SerializedName("complete") - COMPLETE("complete"); - - private final String name; - - CirclePaymentStatus(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - - public Payment.Status toPaymentStatus() { - switch (this) { - case PENDING: - case CONFIRMED: - return Payment.Status.PENDING; - case COMPLETE: - case PAID: - return Payment.Status.SUCCESSFUL; - case FAILED: - return Payment.Status.FAILED; - default: - throw new RuntimeException("unsupported CirclePaymentStatus -> Payment.Status conversion"); - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java deleted file mode 100644 index 6c91ee33ef..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CirclePayout.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import com.google.gson.*; -import com.google.gson.annotations.SerializedName; -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.platform.payment.common.PaymentNetwork; -import org.stellar.anchor.util.GsonUtils; -import shadow.com.google.common.reflect.TypeToken; - -@Data -public class CirclePayout { - String id; - String sourceWalletId; - CircleTransactionParty destination; - CircleBalance amount; - CircleBalance fees; - CirclePaymentStatus status; - String trackingRef; - String errorCode; - Instant updateDate; - Instant createDate; - Map riskEvaluation; - Map originalResponse; - - @SerializedName("return") - Map returnVal; - - public Payment toPayment() { - Payment p = new Payment(); - p.setId(id); - p.setSourceAccount( - new Account( - PaymentNetwork.CIRCLE, - sourceWalletId, - new Account.Capabilities( - PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR, PaymentNetwork.BANK_WIRE))); - Account destinationAccount = destination.toAccount(null); - p.setDestinationAccount(destinationAccount); - p.setBalance(amount.toBalance(destinationAccount.paymentNetwork)); - p.setStatus(status.toPaymentStatus()); - p.setErrorCode(errorCode); - p.setCreatedAt(createDate); - p.setUpdatedAt(updateDate); - p.setOriginalResponse(originalResponse); - - return p; - } - - public static class Deserializer implements JsonDeserializer { - private static final Gson gson = GsonUtils.getInstance(); - - @Override - public CirclePayout deserialize( - JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - JsonObject jsonObject = json.getAsJsonObject(); - CirclePayout payout = gson.fromJson(jsonObject, CirclePayout.class); - - Type type = new TypeToken>() {}.getType(); - Map originalResponse = gson.fromJson(jsonObject, type); - payout.setOriginalResponse(originalResponse); - return payout; - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java deleted file mode 100644 index d9ced13ec4..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransactionParty.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import com.google.gson.annotations.SerializedName; -import org.stellar.anchor.platform.payment.common.Account; -import org.stellar.anchor.platform.payment.common.PaymentNetwork; -import reactor.util.annotation.Nullable; - -@lombok.Data -public class CircleTransactionParty { - Type type; - String id; - String address; - String addressTag; - String name; - transient String email; - String chain; - - public enum Type { - @SerializedName("wallet") - WALLET("wallet"), - - @SerializedName("blockchain") - BLOCKCHAIN("blockchain"), - - @SerializedName("wire") - WIRE("wire"), - - @SerializedName("ach") - ACH("ach"), - - @SerializedName("card") - CARD("card"); - - private final String name; - - Type(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - public static CircleTransactionParty wallet(String id) { - CircleTransactionParty party = new CircleTransactionParty(); - party.type = Type.WALLET; - party.id = id; - return party; - } - - public static CircleTransactionParty stellar(String address, String addressTag) { - CircleTransactionParty party = new CircleTransactionParty(); - party.type = Type.BLOCKCHAIN; - party.address = address; - party.addressTag = addressTag; - party.chain = "XLM"; - return party; - } - - public static CircleTransactionParty wire(String bankId, String email) { - CircleTransactionParty party = new CircleTransactionParty(); - party.type = Type.WIRE; - party.id = bankId; - party.email = email; - return party; - } - - /** - * Transforms a Circle transaction party into an Account. - * - * @param distributionAccountId used to update the bank wire capability when this is a Circle - * wallet. - * @return a new account instance. - */ - public Account toAccount(@Nullable String distributionAccountId) { - switch (type) { - case BLOCKCHAIN: - if (!"XLM".equals(chain)) { - throw new RuntimeException("the only supported chain is `XLM`"); - } - return new Account( - PaymentNetwork.STELLAR, - address, - addressTag, - new Account.Capabilities(PaymentNetwork.STELLAR)); - - case WALLET: - boolean isMerchantAccount = - distributionAccountId != null && distributionAccountId.equals(id); - Account.Capabilities capabilities = - isMerchantAccount - ? CircleWallet.merchantAccountCapabilities() - : CircleWallet.defaultCapabilities(); - return new Account(PaymentNetwork.CIRCLE, id, capabilities); - - case WIRE: - return new Account( - PaymentNetwork.BANK_WIRE, id, new Account.Capabilities(PaymentNetwork.BANK_WIRE)); - - default: - throw new RuntimeException("unsupported type"); - // TODO: make sure to handle cards and ach as well - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java deleted file mode 100644 index 1a043ff2ff..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleTransfer.java +++ /dev/null @@ -1,66 +0,0 @@ -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 shadow.com.google.common.reflect.TypeToken; - -@Data -public class CircleTransfer { - String id; - CircleTransactionParty source; - CircleTransactionParty destination; - CircleBalance amount; - String transactionHash; - CirclePaymentStatus status; - String errorCode; - Instant createDate; - Map originalResponse; - - public Payment toPayment(String distributionAccountId) { - Payment p = new Payment(); - p.setId(id); - p.setSourceAccount(source.toAccount(distributionAccountId)); - Account destinationAccount = destination.toAccount(distributionAccountId); - p.setDestinationAccount(destinationAccount); - p.setBalance(amount.toBalance(destinationAccount.paymentNetwork)); - p.setIdTag(transactionHash); - p.setStatus(status.toPaymentStatus()); - p.setErrorCode(errorCode); - p.setCreatedAt(createDate); - p.setUpdatedAt(createDate); - p.setOriginalResponse(originalResponse); - - return p; - } - - public static class Serialization - implements JsonDeserializer, JsonSerializer { - private static final Gson gson = GsonUtils.getInstance(); - - @Override - public CircleTransfer deserialize( - JsonElement json, Type typeOfT, JsonDeserializationContext context) - throws JsonParseException { - JsonObject jsonObject = json.getAsJsonObject(); - CircleTransfer transfer = gson.fromJson(jsonObject, CircleTransfer.class); - - Type type = new TypeToken>() {}.getType(); - Map originalResponse = gson.fromJson(jsonObject, type); - transfer.setOriginalResponse(originalResponse); - - return transfer; - } - - @Override - public JsonElement serialize( - CircleTransfer src, Type typeOfSrc, JsonSerializationContext context) { - return gson.toJsonTree(src.originalResponse).getAsJsonObject(); - } - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java deleted file mode 100644 index 7c19dc26f8..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWallet.java +++ /dev/null @@ -1,61 +0,0 @@ -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.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 { - String walletId; - String entityId; - String type; // `merchant` or `end_user_wallet` - String description; - List balances; - - public CircleWallet(String walletId) { - this.walletId = walletId; - } - - public Account.Capabilities getCapabilities() { - return "merchant".equals(type) ? merchantAccountCapabilities() : defaultCapabilities(); - } - - public static Account.Capabilities defaultCapabilities() { - Account.Capabilities capabilities = - new Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR); - capabilities.getSend().put(PaymentNetwork.BANK_WIRE, true); - return capabilities; - } - - public static Account.Capabilities merchantAccountCapabilities() { - return new Account.Capabilities( - PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR, PaymentNetwork.BANK_WIRE); - } - - public Account toAccount() { - Account account = new Account(PaymentNetwork.CIRCLE, walletId, description, getCapabilities()); - account.setBalances( - balances.stream() - .map(circleBalance -> circleBalance.toBalance(PaymentNetwork.CIRCLE)) - .collect(Collectors.toList())); - account.setUnsettledBalances(new ArrayList<>()); - return account; - } - - public DepositInstructions toDepositInstructions() { - return new DepositInstructions( - walletId, - null, - PaymentNetwork.CIRCLE, - walletId, - null, - PaymentNetwork.CIRCLE, - CircleAsset.circleUSD(), - null); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java deleted file mode 100644 index 28590b79bc..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/CircleWireDepositInstructions.java +++ /dev/null @@ -1,53 +0,0 @@ -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.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 shadow.com.google.common.reflect.TypeToken; - -@Data -public class CircleWireDepositInstructions { - String trackingRef; - Beneficiary beneficiary; - BeneficiaryBank beneficiaryBank; - - private static final Gson gson = GsonUtils.getInstance(); - - @Data - public static class Beneficiary { - String name; - String address1; - String address2; - } - - @Data - public static class BeneficiaryBank { - String name; - String address; - String city; - String postalCode; - String country; - String swiftCode; - String routingNumber; - String accountNumber; - } - - public DepositInstructions toDepositInstructions(String beneficiaryAccountId) { - Type type = new TypeToken>() {}.getType(); - Map originalResponse = gson.fromJson(gson.toJson(this), type); - return new DepositInstructions( - beneficiaryAccountId, - null, - PaymentNetwork.CIRCLE, - trackingRef, - null, - PaymentNetwork.BANK_WIRE, - CircleAsset.fiatUSD(), - originalResponse); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java deleted file mode 100644 index 882838f28c..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/TransferNotificationBody.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model; - -import java.util.Map; -import lombok.Data; - -@Data -public class TransferNotificationBody { - String clientId; - String notificationType; - String version; - Map customAttributes; - CircleTransfer transfer; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java deleted file mode 100644 index 78f0599c13..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/request/CircleSendTransactionRequest.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.request; - -import java.util.HashMap; -import lombok.Data; -import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance; -import org.stellar.anchor.platform.payment.observer.circle.model.CircleTransactionParty; - -@Data -public class CircleSendTransactionRequest { - CircleTransactionParty source; - CircleTransactionParty destination; - CircleBalance amount; - String idempotencyKey; - HashMap metadata; - - public static CircleSendTransactionRequest forTransfer( - CircleTransactionParty source, - CircleTransactionParty destination, - CircleBalance amount, - String idempotencyKey) { - CircleSendTransactionRequest req = new CircleSendTransactionRequest(); - req.source = source; - req.destination = destination; - req.amount = amount; - req.idempotencyKey = idempotencyKey; - return req; - } - - public static CircleSendTransactionRequest forPayout( - CircleTransactionParty source, - CircleTransactionParty destination, - CircleBalance amount, - String idempotencyKey) { - CircleSendTransactionRequest req = new CircleSendTransactionRequest(); - req.source = source; - req.destination = destination; - req.amount = amount; - req.idempotencyKey = idempotencyKey; - req.metadata = new HashMap<>(); - req.metadata.put("beneficiaryEmail", destination.getEmail()); - return req; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java deleted file mode 100644 index fe6d67065e..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleAccountBalancesResponse.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import java.util.List; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.stellar.anchor.platform.payment.observer.circle.model.CircleBalance; - -@EqualsAndHashCode(callSuper = true) -@Data -public class CircleAccountBalancesResponse - extends CircleDetailResponse { - @lombok.Data - public static class Data { - public List available; - public List unsettled; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java deleted file mode 100644 index 8e05d47bb5..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleConfigurationResponse.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import lombok.Data; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode(callSuper = true) -@Data -public class CircleConfigurationResponse - extends CircleDetailResponse { - @lombok.Data - public static class Data { - public Payments payments; - } - - @lombok.Data - public static class Payments { - public String masterWalletId; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java deleted file mode 100644 index a880f28d0c..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleDetailResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import lombok.Data; - -@Data -public class CircleDetailResponse { - T data; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java deleted file mode 100644 index 458521dd12..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleError.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import lombok.Data; - -@Data -public class CircleError { - Integer code; - String message; -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java deleted file mode 100644 index 1a82b0e600..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleListResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import java.util.List; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode(callSuper = true) -@Data -public class CircleListResponse extends CircleDetailResponse> {} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java deleted file mode 100644 index 3326738342..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePaymentListResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import lombok.Data; -import lombok.EqualsAndHashCode; -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 -public class CirclePaymentListResponse extends CircleListResponse { - public PaymentHistory toPaymentHistory(int pageSize, Account account) { - PaymentHistory ph = new PaymentHistory(account); - if (data == null || data.size() == 0) { - return ph; - } - - for (int i = 0; i < data.size(); i++) { - CirclePayment payout = data.get(i); - ph.getPayments().add(payout.toPayment()); - - if (i == 0) { - ph.setBeforeCursor(payout.getId()); - } - - if (i + 1 == pageSize) { - ph.setAfterCursor(payout.getId()); - } - } - - return ph; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java deleted file mode 100644 index 938200bde4..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CirclePayoutListResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import lombok.Data; -import lombok.EqualsAndHashCode; -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 -public class CirclePayoutListResponse extends CircleListResponse { - public PaymentHistory toPaymentHistory(int pageSize, Account account) { - PaymentHistory ph = new PaymentHistory(account); - if (data == null || data.size() == 0) { - return ph; - } - - for (int i = 0; i < data.size(); i++) { - CirclePayout payout = data.get(i); - ph.getPayments().add(payout.toPayment()); - - if (i == 0) { - ph.setBeforeCursor(payout.getId()); - } - - if (i + 1 == pageSize) { - ph.setAfterCursor(payout.getId()); - } - } - - return ph; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java deleted file mode 100644 index 7847159ab0..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/model/response/CircleTransferListResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.model.response; - -import lombok.Data; -import lombok.EqualsAndHashCode; -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 -public class CircleTransferListResponse extends CircleListResponse { - public PaymentHistory toPaymentHistory( - int pageSize, Account account, String distributionAccountId) { - PaymentHistory ph = new PaymentHistory(account); - if (data == null || data.size() == 0) { - return ph; - } - - for (int i = 0; i < data.size(); i++) { - CircleTransfer transfer = data.get(i); - ph.getPayments().add(transfer.toPayment(distributionAccountId)); - - if (i == 0) { - ph.setBeforeCursor(transfer.getId()); - } - - if (i + 1 == pageSize) { - ph.setAfterCursor(transfer.getId()); - } - } - - return ph; - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java deleted file mode 100644 index e3736947b6..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/CircleAsset.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.util; - -import java.util.List; -import org.stellar.sdk.Network; -import reactor.util.annotation.NonNull; - -public class CircleAsset { - @NonNull - public static String stellarUSDC(Network stellarNetwork) { - if (stellarNetwork == org.stellar.sdk.Network.PUBLIC) - return "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"; - - return "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"; - } - - public static String circleUSD() { - return "circle:USD"; - } - - public static String fiatUSD() { - return "iso4217:USD"; - } - - public static boolean isSupported(String currencyName, Network stellarNetwork) { - return List.of( - CircleAsset.circleUSD(), CircleAsset.fiatUSD(), CircleAsset.stellarUSDC(stellarNetwork)) - .contains(currencyName); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java b/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java deleted file mode 100644 index ec02f61968..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/payment/observer/circle/util/NettyHttpClient.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.stellar.anchor.platform.payment.observer.circle.util; - -import io.netty.channel.ChannelOption; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaderValues; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutHandler; -import java.time.Duration; -import java.util.LinkedHashMap; -import java.util.Map; -import okhttp3.HttpUrl; -import reactor.netty.http.client.HttpClient; - -public class NettyHttpClient { - public static HttpClient withBaseUrl(String baseUrl) { - return HttpClient.create() - .baseUrl(baseUrl) - .compress(true) - .followRedirect(false) - .headers(h -> h.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)) - .option( - ChannelOption.CONNECT_TIMEOUT_MILLIS, - 5000) // period within which a connection between a client and a server must be - // established - .responseTimeout( - Duration.ofSeconds( - 30)) // the time we wait to receive a response after sending a request - .doOnConnected( - connection -> - connection - .addHandlerLast(new ReadTimeoutHandler(15)) - .addHandlerLast(new WriteTimeoutHandler(15))); - } - - public static String buildUri(String uri, LinkedHashMap queryParams) { - HttpUrl.Builder urlBuilder = new HttpUrl.Builder().scheme("https").host("example.com"); - - if (uri != null && !uri.isEmpty()) { - for (String pathSegment : uri.split("/")) { - urlBuilder = urlBuilder.addPathSegment(pathSegment); - } - } - - if (queryParams != null && !queryParams.isEmpty()) { - for (Map.Entry queryParam : queryParams.entrySet()) { - urlBuilder = urlBuilder.addQueryParameter(queryParam.getKey(), queryParam.getValue()); - } - } - - return urlBuilder.build().url().getFile(); - } -} 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 1235138857..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,15 @@ 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; -import java.util.List; - -import static org.stellar.anchor.util.Log.debugF; -import static org.stellar.anchor.util.Log.warnF; - @Service @DependsOn("configManager") public class HealthCheckService { 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 b8102fbf9f..3caa0da961 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 @@ -22,8 +22,8 @@ import org.stellar.anchor.api.shared.StellarTransaction; 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.platform.observer.ObservedPayment; +import org.stellar.anchor.platform.observer.PaymentListener; 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/Sep31DepositInfoGeneratorApi.java b/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorApi.java index 8327dd2427..48007545a8 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,6 +1,6 @@ package org.stellar.anchor.platform.service; -import static org.stellar.anchor.platform.payment.observer.stellar.PaymentObservingAccountsManager.AccountType.TRANSIENT; +import static org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager.AccountType.TRANSIENT; import java.util.Objects; import org.stellar.anchor.api.callback.GetUniqueAddressResponse; @@ -8,7 +8,7 @@ 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.platform.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.sep31.Sep31DepositInfoGenerator; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.util.MemoHelper; 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 deleted file mode 100644 index a96b46ceac..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/service/Sep31DepositInfoGeneratorCircle.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.stellar.anchor.platform.service; - -import org.stellar.anchor.api.sep.sep31.Sep31DepositInfo; -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; -import org.stellar.sdk.xdr.MemoType; - -public class Sep31DepositInfoGeneratorCircle implements Sep31DepositInfoGenerator { - - private final CirclePaymentService circlePaymentService; - - public Sep31DepositInfoGeneratorCircle(CirclePaymentService circlePaymentService) { - this.circlePaymentService = circlePaymentService; - } - - @Override - public Sep31DepositInfo generate(Sep31Transaction txn) { - return circlePaymentService - .getDistributionAccountAddress() - .flatMap(circlePaymentService::createNewStellarAddress) - .map( - circleBlockchainAddressCircleDetailResponse -> { - CircleBlockchainAddress blockchainAddress = - circleBlockchainAddressCircleDetailResponse.getData(); - return new Sep31DepositInfo( - blockchainAddress.getAddress(), - blockchainAddress.getAddressTag(), - MemoHelper.memoTypeAsString(MemoType.MEMO_TEXT)); - }) - .block(); - } -} 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 9b580d3e35..5e0b4bd7b8 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -106,25 +106,37 @@ platform_api: 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 + ## @param: type ## @type: string - ## The id of the wallet to track. 'all' indicates that events for all wallets are tracked. + ## @supported_values: + ## `stellar`: the observer will observer transactions from the Stellar network # - tracked_wallet: all - + type: stellar + stellar: + # + # The interval in seconds when the silence check will run. + silence_check_interval: 5 + + # The silence_timeout in seconds. + # + # If the observer had been silent for longer than the timeout value, the observer will try to reconnect to the stellar network. + silence_timeout: 90 + + # The maximum number of silence timeout retries before the observer will give up. + # Setting the silence_timeout_retires to -1 will result in indefinite retires. + silence_timeout_retries: 5 + + # The initial backoff (cool-down) time (in seconds) before reconnecting to the Stellar network + initial_stream_backoff_time: 1 + + # The maximum backoff (cool-down) time (in seconds) before reconnecting to the Stellar network + max_stream_backoff_time: 300 + + # The initial backoff (cool-down) time (in seconds) before reconnecting to the event publisher + initial_event_backoff_time: 1 + + # The initial backoff (cool-down) time (in seconds) before reconnecting to the event publisher + max_event_backoff_time: 1 ## @param: languages ## @supported_values: en 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 e132c6cc4e..ba08f54fd5 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -36,11 +36,23 @@ logging.stellar_level: metrics.enabled: metrics.extras_enabled: metrics.run_interval: -payment_observer.circle_url: payment_observer.enabled: -payment_observer.tracked_wallet: +payment_observer.stellar.initial_event_backoff_time: +payment_observer.stellar.initial_stream_backoff_time: +payment_observer.stellar.max_event_backoff_time: +payment_observer.stellar.max_stream_backoff_time: +payment_observer.stellar.silence_check_interval: +payment_observer.stellar.silence_timeout: +payment_observer.stellar.silence_timeout_retries: +payment_observer.type: platform_api.auth.expiration_milliseconds: platform_api.auth.type: +secret.callback_api.secret: +secret.data.password: +secret.data.username: +secret.jwt_secret: +secret.platform_api.secret: +secret.sep10_signing_seed: sep1.enabled: sep1.toml.type: sep1.toml.value: diff --git a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt deleted file mode 100644 index 68deec2bdb..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CirclePaymentServiceTest.kt +++ /dev/null @@ -1,2315 +0,0 @@ -package org.stellar.anchor.paymentservice.circle - -import com.google.gson.Gson -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import java.io.IOException -import java.lang.reflect.Method -import java.math.BigDecimal -import java.time.Instant -import java.time.format.DateTimeFormatter -import okhttp3.mockwebserver.Dispatcher -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import okhttp3.mockwebserver.RecordedRequest -import org.hamcrest.CoreMatchers -import org.hamcrest.MatcherAssert.assertThat -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.CsvSource -import org.junit.jupiter.params.provider.EnumSource -import org.junit.jupiter.params.provider.NullSource -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.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 reactor.core.publisher.Mono -import shadow.com.google.common.reflect.TypeToken - -class CirclePaymentServiceTest { - companion object { - val mockWalletToWalletTransferJson: String = - FileUtil.getResourceFileAsString("mock_wallet_to_wallet_transfer.json") - - val mockStellarToWalletTransferJson: String = - FileUtil.getResourceFileAsString("mock_stellar_to_wallet_transfer.json") - - val mockWalletToStellarTransferJson: String = - FileUtil.getResourceFileAsString("mock_wallet_to_stellar_transfer.json") - - val mockWalletToWirePayoutJson: String = - FileUtil.getResourceFileAsString("mock_wallet_to_wire_payout.json") - - val mockWireToWalletPaymentJson: String = - FileUtil.getResourceFileAsString("mock_wire_to_wallet_payment.json") - - val mockStellarPaymentResponsePageBody: String = - FileUtil.getResourceFileAsString("mock_stellar_payment_response_page_body.json") - - val mockStellarPathPaymentResponsePageBody: String = - FileUtil.getResourceFileAsString("mock_stellar_path_payment_response_page_body.json") - - val mockGetListOfAddressesBody: String = - FileUtil.getResourceFileAsString("mock_get_list_of_addresses_body.json") - - val mockGetWireDepositInstructionsBody: String = - FileUtil.getResourceFileAsString("mock_get_wire_deposit_instructions_body.json") - - val mockAddressJson: String = FileUtil.getResourceFileAsString("mock_address.json") - } - - private fun instantFromString(dateStr: String): Instant { - return DateTimeFormatter.ISO_INSTANT.parse(dateStr, Instant::from) - } - - private lateinit var server: MockWebServer - private lateinit var service: PaymentService - private val merchantAccount = - Account( - PaymentNetwork.CIRCLE, - "1000066041", - Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR, PaymentNetwork.BANK_WIRE) - ) - - private fun getDistAccountIdMockResponse(masterWalletId: String = "1000066041"): MockResponse { - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "payments":{ - "masterWalletId":"$masterWalletId" - } - } - }""" - ) - } - - @BeforeEach - @Throws(IOException::class) - fun setUp() { - server = MockWebServer() - server.start() - - val circlePaymentConfig = mockk() - every { circlePaymentConfig.isEnabled } returns true - every { circlePaymentConfig.name } returns "TestCircle" - - val circleConfig = mockk() - every { circleConfig.circleUrl } returns server.url("").toString() - every { circleConfig.apiKey } returns "" - - val horizon = mockk() - every { horizon.horizonUrl } returns server.url("").toString() - every { horizon.stellarNetworkPassphrase } returns "Test SDF Network ; September 2015" - every { horizon.server } returns Server(server.url("").toString()) - - service = CirclePaymentService(circlePaymentConfig, circleConfig, horizon) - } - - @AfterEach - @Throws(IOException::class) - fun tearDown() { - server.shutdown() - } - - @Test - fun test_ping() { - val response = - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("{\"message\": \"pong\"}") - server.enqueue(response) - - assertDoesNotThrow { service.ping().block() } - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertThat(request.path, CoreMatchers.endsWith("/ping")) - } - - @Test - fun test_getDistributionAccountAddress() { - server.enqueue(getDistAccountIdMockResponse()) - - var masterWalletId: String? = null - assertDoesNotThrow { masterWalletId = service.distributionAccountAddress.block() } - assertEquals("1000066041", masterWalletId) - - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - assertThat(request.path, CoreMatchers.endsWith("/v1/configuration")) - - // check if cached version doesn't freeze the thread - assertDoesNotThrow { masterWalletId = service.distributionAccountAddress.block() } - assertEquals("1000066041", masterWalletId) - } - - @Test - fun test_private_getMerchantAccountUnsettledBalances() { - val response = - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "available":[], - "unsettled": [ - { - "amount":"100.00", - "currency":"USD" - } - ] - } - }""" - ) - server.enqueue(response) - - // Let's use reflection to access the private method - val getMerchantAccountUnsettledBalancesMethod: Method = - CirclePaymentService::class.java.getDeclaredMethod("getMerchantAccountUnsettledBalances") - assert(getMerchantAccountUnsettledBalancesMethod.trySetAccessible()) - @Suppress("UNCHECKED_CAST") - val unsettledBalancesMono = - (getMerchantAccountUnsettledBalancesMethod.invoke(service) as Mono>) - - var unsettledBalances: List? = null - assertDoesNotThrow { unsettledBalances = unsettledBalancesMono.block() } - assertEquals(1, unsettledBalances?.size) - assertEquals("100.00", unsettledBalances!![0].amount) - assertEquals("circle:USD", unsettledBalances!![0].currencyName) - - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - assertThat(request.path, CoreMatchers.endsWith("/v1/businessAccount/balances")) - } - - @Test - fun test_private_getCircleWallet() { - val response = - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "walletId":"1000223064", - "entityId":"2f47c999-9022-4939-acea-dc3afa9ccbaf", - "type":"end_user_wallet", - "description":"Treasury Wallet", - "balances":[ - { - "amount":"123.45", - "currency":"USD" - } - ] - } - }""" - ) - server.enqueue(response) - - // Let's use reflection to access the private method - val getCircleWalletMethod: Method = - CirclePaymentService::class.java.getDeclaredMethod("getCircleWallet", String::class.java) - assert(getCircleWalletMethod.trySetAccessible()) - - var circleWallet: CircleWallet? = null - assertDoesNotThrow { - @Suppress("UNCHECKED_CAST") - circleWallet = - (getCircleWalletMethod.invoke(service, "1000223064") as Mono).block() - } - assertEquals("1000223064", circleWallet?.walletId) - assertEquals("2f47c999-9022-4939-acea-dc3afa9ccbaf", circleWallet?.entityId) - assertEquals("end_user_wallet", circleWallet?.type) - assertEquals("Treasury Wallet", circleWallet?.description) - assertEquals(1, circleWallet?.balances?.size) - assertEquals("123.45", circleWallet!!.balances[0].amount) - assertEquals("USD", circleWallet!!.balances[0].currency) - - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - assertTrue(request.path!!.endsWith("/v1/wallets/1000223064")) - } - - @Test - fun test_getAccount_isNotMainAccount() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "payments":{ - "masterWalletId":"1234" - } - } - }""" - ) - "/v1/wallets/1000223064" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "walletId":"1000223064", - "entityId":"2f47c999-9022-4939-acea-dc3afa9ccbaf", - "type":"end_user_wallet", - "description":"Treasury Wallet", - "balances":[ - { - "amount":"29472389929.00", - "currency":"USD" - } - ] - } - }""" - ) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var account: Account? = null - assertDoesNotThrow { account = service.getAccount("1000223064").block() } - assertEquals("1000223064", account?.id) - assertEquals(PaymentNetwork.CIRCLE, account?.paymentNetwork) - assertEquals(CircleWallet.defaultCapabilities(), account?.capabilities) - assertEquals("Treasury Wallet", account?.idTag) - assertEquals(1, account?.balances?.size) - assertEquals("29472389929.00", account!!.balances!![0].amount) - assertEquals("circle:USD", account!!.balances!![0].currencyName) - assertEquals(0, account?.unsettledBalances?.size) - - assertEquals(2, server.requestCount) - val allRequests = arrayOf(server.takeRequest(), server.takeRequest()) - - val validateSecretKeyRequest = - allRequests.find { request -> request.path!! == "/v1/configuration" }!! - assertEquals("GET", validateSecretKeyRequest.method) - assertEquals("application/json", validateSecretKeyRequest.headers["Content-Type"]) - assertEquals("Bearer ", validateSecretKeyRequest.headers["Authorization"]) - - val getAccountRequest = - allRequests.find { request -> request.path!! == "/v1/wallets/1000223064" }!! - assertEquals("GET", getAccountRequest.method) - assertEquals("application/json", getAccountRequest.headers["Content-Type"]) - assertEquals("Bearer ", getAccountRequest.headers["Authorization"]) - } - - @Test - fun test_getAccount_isMainAccount() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/businessAccount/balances" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "available":[], - "unsettled": [ - { - "amount":"100.00", - "currency":"USD" - } - ] - } - }""" - ) - "/v1/wallets/1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "walletId":"1000066041", - "entityId":"2f47c999-9022-4939-acea-dc3afa9ccbaf", - "type":"merchant", - "balances":[ - { - "amount":"29472389929.00", - "currency":"USD" - } - ] - } - }""" - ) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var account: Account? = null - assertDoesNotThrow { account = service.getAccount("1000066041").block() } - assertEquals("1000066041", account?.id) - assertEquals(PaymentNetwork.CIRCLE, account?.paymentNetwork) - assertEquals( - Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR, PaymentNetwork.BANK_WIRE), - account?.capabilities - ) - assertNull(account?.idTag) - assertEquals(1, account?.balances?.size) - assertEquals("29472389929.00", account!!.balances!![0].amount) - assertEquals("circle:USD", account!!.balances!![0].currencyName) - assertEquals(1, account?.unsettledBalances?.size) - assertEquals("100.00", account!!.unsettledBalances!![0].amount) - assertEquals("circle:USD", account!!.unsettledBalances!![0].currencyName) - - assertEquals(3, server.requestCount) - - val validateSecretKeyRequest = server.takeRequest() - assertEquals("GET", validateSecretKeyRequest.method) - assertEquals("application/json", validateSecretKeyRequest.headers["Content-Type"]) - assertEquals("Bearer ", validateSecretKeyRequest.headers["Authorization"]) - assertTrue(validateSecretKeyRequest.path!!.endsWith("/v1/configuration")) - - val parallelRequests = arrayOf(server.takeRequest(), server.takeRequest()) - - val mainAccountRequest = - parallelRequests.find { request -> request.path!! == "/v1/businessAccount/balances" }!! - assertEquals("GET", mainAccountRequest.method) - assertEquals("application/json", mainAccountRequest.headers["Content-Type"]) - assertEquals("Bearer ", mainAccountRequest.headers["Authorization"]) - - val getAccountRequest = - parallelRequests.find { request -> request.path!! == "/v1/wallets/1000066041" }!! - assertEquals("GET", getAccountRequest.method) - assertEquals("application/json", getAccountRequest.headers["Content-Type"]) - assertEquals("Bearer ", getAccountRequest.headers["Authorization"]) - } - - @Test - fun test_createAccount() { - val response = - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "walletId":"1000223064", - "entityId":"2f47c999-9022-4939-acea-dc3afa9ccbaf", - "type":"end_user_wallet", - "description":"Foo bar", - "balances":[ - { - "amount":"123.45", - "currency":"USD" - } - ] - } - }""" - ) - server.enqueue(response) - - var account: Account? = null - assertDoesNotThrow { account = service.createAccount("Foo bar").block() } - assertEquals("1000223064", account?.id) - assertEquals(PaymentNetwork.CIRCLE, account?.paymentNetwork) - assertEquals(CircleWallet.defaultCapabilities(), account?.capabilities) - assertEquals("Foo bar", account?.idTag) - assertEquals(1, account?.balances?.size) - assertEquals("123.45", account!!.balances!![0].amount) - assertEquals("circle:USD", account!!.balances!![0].currencyName) - assertEquals(0, account?.unsettledBalances?.size) - - val request = server.takeRequest() - val requestBody = request.body.readUtf8() - assertEquals("POST", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - assertThat(request.path, CoreMatchers.endsWith("/v1/wallets")) - assertThat(requestBody, CoreMatchers.containsString("\"description\":\"Foo bar\"")) - assertThat(requestBody, CoreMatchers.containsString("\"idempotencyKey\":")) - } - - @Test - fun test_sendPayment_parameterValidation() { - // invalid source account network - var ex = - assertThrows { - service - .sendPayment( - Account(PaymentNetwork.STELLAR, "123", Account.Capabilities()), - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - "", - BigDecimal(0) - ) - .block() - } - assertEquals( - HttpException(400, "the only supported network for the source account is circle"), - ex - ) - - // missing beneficiary email when destination is a wire bank account - ex = - assertThrows { - service - .sendPayment( - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - Account(PaymentNetwork.BANK_WIRE, "123", "invalidEmail", Account.Capabilities()), - "", - BigDecimal(0) - ) - .block() - } - assertEquals( - HttpException( - 400, - "for bank transfers, please provide a valid beneficiary email address in the destination idTag" - ), - ex - ) - - // invalid currency name schema - ex = - assertThrows { - service - .sendPayment( - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - Account(PaymentNetwork.CIRCLE, "456", Account.Capabilities()), - "invalidSchema:USD", - BigDecimal(0) - ) - .block() - } - assertEquals( - HttpException(400, "the currency to be sent must contain the destination network schema"), - ex - ) - - // invalid currency name - ex = - assertThrows { - service - .sendPayment( - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - Account(PaymentNetwork.BANK_WIRE, "456", "email@test.com", Account.Capabilities()), - "iso4217:ABC", - BigDecimal(0) - ) - .block() - } - val wantException = - HttpException( - 400, - String.format( - "the only supported currencies are %s, %s and %s.", - "circle:USD", - "iso4217:USD", - CircleAsset.stellarUSDC(Network.TESTNET) - ) - ) - assertEquals(wantException, ex) - } - - @Test - fun test_private_validateSendPaymentInput_supportedCurrencies() { - // Let's use reflection to access the private method - val validateSendPaymentInputMethod: Method = - CirclePaymentService::class.java.getDeclaredMethod( - "validateSendPaymentInput", - Account::class.java, - Account::class.java, - String::class.java - ) - assert(validateSendPaymentInputMethod.trySetAccessible()) - - // "circle:USD" succeeds - assertDoesNotThrow { - @Suppress("UNCHECKED_CAST") - validateSendPaymentInputMethod.invoke( - service, - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - Account(PaymentNetwork.CIRCLE, "456", Account.Capabilities()), - "circle:USD" - ) - } - - // "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" succeeds - assertDoesNotThrow { - @Suppress("UNCHECKED_CAST") - validateSendPaymentInputMethod.invoke( - service, - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - Account(PaymentNetwork.STELLAR, "G...", Account.Capabilities()), - "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" - ) - } - - // "iso4217:USD" succeeds - assertDoesNotThrow { - @Suppress("UNCHECKED_CAST") - validateSendPaymentInputMethod.invoke( - service, - Account(PaymentNetwork.CIRCLE, "123", Account.Capabilities()), - Account(PaymentNetwork.BANK_WIRE, "456", "email@test.com", Account.Capabilities()), - "iso4217:USD" - ) - } - } - - @Test - fun test_sendPayment_circleToCircle() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/transfers" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": { - "id":"c58e2613-a808-4075-956c-e576787afb3b", - "source":{ - "type":"wallet", - "id":"1000066041" - }, - "destination":{ - "type":"wallet", - "id":"1000067536" - }, - "amount":{ - "amount":"0.91", - "currency":"USD" - }, - "status":"pending", - "createDate":"2022-01-01T01:01:01.544Z" - } - }""".trimIndent() - ) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val destination = - Account( - PaymentNetwork.CIRCLE, - "1000067536", - Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR) - ) - var payment: Payment? = null - assertDoesNotThrow { - payment = - service - .sendPayment( - merchantAccount, - destination, - CircleAsset.circleUSD(), - BigDecimal.valueOf(0.91) - ) - .block() - } - - assertEquals("c58e2613-a808-4075-956c-e576787afb3b", payment?.id) - assertEquals(merchantAccount, payment?.sourceAccount) - assertEquals( - Account(PaymentNetwork.CIRCLE, "1000067536", CircleWallet.defaultCapabilities()), - payment?.destinationAccount - ) - assertEquals(Balance("0.91", "circle:USD"), payment?.balance) - assertEquals(Payment.Status.PENDING, payment?.status) - assertNull(payment?.errorCode) - - val wantDate = instantFromString("2022-01-01T01:01:01.544Z") - assertEquals(wantDate, payment?.createdAt) - assertEquals(wantDate, payment?.updatedAt) - - val wantOriginalResponse = - hashMapOf( - "id" to "c58e2613-a808-4075-956c-e576787afb3b", - "source" to hashMapOf("id" to "1000066041", "type" to "wallet"), - "destination" to - hashMapOf( - "id" to "1000067536", - "type" to "wallet", - ), - "amount" to - hashMapOf( - "amount" to "0.91", - "currency" to "USD", - ), - "status" to "pending", - "createDate" to "2022-01-01T01:01:01.544Z", - ) - assertEquals(wantOriginalResponse, payment?.originalResponse) - - assertEquals(2, server.requestCount) - val allRequests = arrayOf(server.takeRequest(), server.takeRequest()) - - val validateSecretKeyRequest = - allRequests.find { request -> request.path!! == "/v1/configuration" }!! - assertEquals("GET", validateSecretKeyRequest.method) - assertEquals("application/json", validateSecretKeyRequest.headers["Content-Type"]) - assertEquals("Bearer ", validateSecretKeyRequest.headers["Authorization"]) - - val postTransferRequest = allRequests.find { request -> request.path!! == "/v1/transfers" }!! - assertEquals("POST", postTransferRequest.method) - assertEquals("application/json", postTransferRequest.headers["Content-Type"]) - assertEquals("Bearer ", postTransferRequest.headers["Authorization"]) - val gotBody = postTransferRequest.body.readUtf8() - val wantBody = - """{ - "source": { - "type": "wallet", - "id": "1000066041" - }, - "destination": { - "type": "wallet", - "id": "1000067536" - }, - "amount": { - "amount": "0.91", - "currency": "USD" - } - }""".trimIndent() - JSONAssert.assertEquals(wantBody, gotBody, false) - } - - @Test - fun test_sendPayment_circleToStellar() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/transfers" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": { - "id":"c58e2613-a808-4075-956c-e576787afb3b", - "source":{ - "type":"wallet", - "id":"1000066041" - }, - "destination":{ - "type":"blockchain", - "address":"GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", - "addressTag":"test tag", - "chain":"XLM" - }, - "amount":{ - "amount":"0.91", - "currency":"USD" - }, - "status":"pending", - "createDate":"2022-01-01T01:01:01.544Z" - } - }""".trimIndent() - ) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val source = Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities()) - val destination = - Account( - PaymentNetwork.STELLAR, - "GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", - "test tag", - Account.Capabilities() - ) - var payment: Payment? = null - assertDoesNotThrow { - payment = - service - .sendPayment( - source, - destination, - "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", - BigDecimal.valueOf(0.91) - ) - .block() - } - - assertEquals("c58e2613-a808-4075-956c-e576787afb3b", payment?.id) - assertEquals(merchantAccount, payment?.sourceAccount) - assertEquals( - Account( - PaymentNetwork.STELLAR, - "GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", - "test tag", - Account.Capabilities(PaymentNetwork.STELLAR) - ), - payment?.destinationAccount - ) - assertEquals( - Balance("0.91", "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"), - payment?.balance - ) - assertEquals(Payment.Status.PENDING, payment?.status) - assertNull(payment?.errorCode) - - val wantDate = instantFromString("2022-01-01T01:01:01.544Z") - assertEquals(wantDate, payment?.createdAt) - assertEquals(wantDate, payment?.updatedAt) - - val wantOriginalResponse = - hashMapOf( - "id" to "c58e2613-a808-4075-956c-e576787afb3b", - "source" to - hashMapOf( - "id" to "1000066041", - "type" to "wallet", - ), - "destination" to - hashMapOf( - "address" to "GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", - "addressTag" to "test tag", - "type" to "blockchain", - "chain" to "XLM", - ), - "amount" to - hashMapOf( - "amount" to "0.91", - "currency" to "USD", - ), - "status" to "pending", - "createDate" to "2022-01-01T01:01:01.544Z", - ) - assertEquals(wantOriginalResponse, payment?.originalResponse) - - assertEquals(2, server.requestCount) - val allRequests = arrayOf(server.takeRequest(), server.takeRequest()) - - val validateSecretKeyRequest = - allRequests.find { request -> request.path!! == "/v1/configuration" }!! - assertEquals("GET", validateSecretKeyRequest.method) - assertEquals("application/json", validateSecretKeyRequest.headers["Content-Type"]) - assertEquals("Bearer ", validateSecretKeyRequest.headers["Authorization"]) - - val postTransferRequest = allRequests.find { request -> request.path!! == "/v1/transfers" }!! - assertEquals("POST", postTransferRequest.method) - assertEquals("application/json", postTransferRequest.headers["Content-Type"]) - assertEquals("Bearer ", postTransferRequest.headers["Authorization"]) - val gotBody = postTransferRequest.body.readUtf8() - val wantBody = - """{ - "source": { - "type": "wallet", - "id": "1000066041" - }, - "destination": { - "type": "blockchain", - "address": "GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", - "addressTag": "test tag", - "chain": "XLM" - }, - "amount": { - "amount": "0.91", - "currency": "USD" - } - }""".trimIndent() - JSONAssert.assertEquals(wantBody, gotBody, false) - } - - @Test - fun test_sendPayment_circleToWire() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/payouts" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": { - "id":"c58e2613-a808-4075-956c-e576787afb3b", - "amount":{ - "amount":"0.91", - "currency":"USD" - }, - "fees":{ - "amount":"0.09", - "currency":"USD" - }, - "status":"pending", - "sourceWalletId":"1000066041", - "destination":{ - "type":"wire", - "id":"6c87da10-feb8-484f-822c-2083ed762d25", - "name":"JPMORGAN CHASE BANK, NA ****6789" - }, - "createDate":"2021-11-25T15:43:03.477Z", - "updateDate":"2021-11-25T16:10:01.618Z" - } - }""".trimIndent() - ) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val source = Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities()) - val destination = - Account( - PaymentNetwork.BANK_WIRE, - "6c87da10-feb8-484f-822c-2083ed762d25", - "test@mail.com", - Account.Capabilities() - ) - var payment: Payment? = null - assertDoesNotThrow { - payment = - service - .sendPayment(source, destination, CircleAsset.fiatUSD(), BigDecimal.valueOf(0.91)) - .block() - } - - assertEquals("c58e2613-a808-4075-956c-e576787afb3b", payment?.id) - assertEquals(merchantAccount, payment?.sourceAccount) - assertEquals( - Account( - PaymentNetwork.BANK_WIRE, - "6c87da10-feb8-484f-822c-2083ed762d25", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ), - payment?.destinationAccount - ) - assertEquals(Balance("0.91", "iso4217:USD"), payment?.balance) - assertEquals(Payment.Status.PENDING, payment?.status) - assertNull(payment?.errorCode) - - val wantCreateDate = instantFromString("2021-11-25T15:43:03.477Z") - assertEquals(wantCreateDate, payment?.createdAt) - val wantUpdateDate = instantFromString("2021-11-25T16:10:01.618Z") - assertEquals(wantUpdateDate, payment?.updatedAt) - - val wantMap: Map = - object : HashMap() { - init { - put("id", "c58e2613-a808-4075-956c-e576787afb3b") - put( - "amount", - object : HashMap() { - init { - put("amount", "0.91") - put("currency", "USD") - } - } - ) - put( - "fees", - object : HashMap() { - init { - put("amount", "0.09") - put("currency", "USD") - } - } - ) - put("status", "pending") - put("sourceWalletId", "1000066041") - put( - "destination", - object : HashMap() { - init { - put("type", "wire") - put("id", "6c87da10-feb8-484f-822c-2083ed762d25") - put("name", "JPMORGAN CHASE BANK, NA ****6789") - } - } - ) - put("createDate", "2021-11-25T15:43:03.477Z") - put("updateDate", "2021-11-25T16:10:01.618Z") - } - } - assertEquals(wantMap, payment?.originalResponse) - - assertEquals(1, server.requestCount) - val request = server.takeRequest() - assertThat(request.path, CoreMatchers.endsWith("/v1/payouts")) - assertEquals("POST", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - val gotBody = request.body.readUtf8() - val wantBody = - """{ - "source": { - "type": "wallet", - "id": "1000066041" - }, - "destination": { - "type": "wire", - "id": "6c87da10-feb8-484f-822c-2083ed762d25" - }, - "amount": { - "amount": "0.91", - "currency": "USD" - }, - "metadata": { - "beneficiaryEmail": "test@mail.com" - } - }""".trimIndent() - JSONAssert.assertEquals(wantBody, gotBody, false) - } - - @Test - fun test_horizon() { - val type = (object : TypeToken>() {}).type - val mockStellarPaymentResponsePage: Page = - GsonUtils.getInstance().fromJson(mockStellarPaymentResponsePageBody, type) - val mockHorizonServer = mockk() - every { mockHorizonServer.payments().forTransaction(any()).execute() } returns - mockStellarPaymentResponsePage - - // when - val result = mockHorizonServer.payments().forTransaction("foo bar").execute() - - // then - verify { mockHorizonServer.payments().forTransaction(any()).execute() } - assertEquals(mockStellarPaymentResponsePage, result) - } - - @Test - fun test_getTransfers() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/transfers?pageSize=50&walletId=1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWalletToWalletTransferJson, - $mockStellarToWalletTransferJson, - $mockWalletToStellarTransferJson - ] - }""".trimIndent() - ) - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/transactions/fb8947c67856d8eb444211c1927d92bcf14abcfb34cdd27fc9e604b15d208fd1/payments" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody(mockStellarPaymentResponsePageBody) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var paymentHistory: PaymentHistory? = null - val service = this.service as CirclePaymentService - val getTransfersMono = service.getTransfers("1000066041", null, null, null) - assertDoesNotThrow { - paymentHistory = - getTransfersMono.block()!!.toPaymentHistory(50, merchantAccount, "1000066041") - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = "c58e2613-a808-4075-956c-e576787afb3b" - - val gson = Gson() - val type = object : TypeToken?>() {}.type - - val p1 = Payment() - p1.id = "c58e2613-a808-4075-956c-e576787afb3b" - p1.sourceAccount = merchantAccount - p1.destinationAccount = - Account(PaymentNetwork.CIRCLE, "1000067536", CircleWallet.defaultCapabilities()) - p1.balance = Balance("0.91", "circle:USD") - p1.status = Payment.Status.PENDING - p1.createdAt = instantFromString("2022-02-07T19:50:23.408Z") - p1.updatedAt = instantFromString("2022-02-07T19:50:23.408Z") - p1.originalResponse = gson.fromJson(mockWalletToWalletTransferJson, type) - wantPaymentHistory.payments.add(p1) - - val p2 = Payment() - p2.id = "7f131f58-a8a0-3dc2-be05-6a015c69de35" - p2.sourceAccount = - Account( - PaymentNetwork.STELLAR, - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - Account.Capabilities(PaymentNetwork.STELLAR) - ) - p2.destinationAccount = merchantAccount - p2.balance = Balance("1.50", "circle:USD") - p2.idTag = "fb8947c67856d8eb444211c1927d92bcf14abcfb34cdd27fc9e604b15d208fd1" - p2.status = Payment.Status.SUCCESSFUL - p2.createdAt = instantFromString("2022-02-07T18:02:17.999Z") - p2.updatedAt = instantFromString("2022-02-07T18:02:17.999Z") - p2.originalResponse = gson.fromJson(mockStellarToWalletTransferJson, type) - wantPaymentHistory.payments.add(p2) - - val p3 = Payment() - p3.id = "a8997020-3da7-4543-bc4a-5ae8c7ce346d" - p3.sourceAccount = merchantAccount - p3.destinationAccount = - Account( - PaymentNetwork.STELLAR, - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - Account.Capabilities(PaymentNetwork.STELLAR) - ) - p3.balance = - Balance("1.00", "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - p3.idTag = "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef" - p3.status = Payment.Status.SUCCESSFUL - p3.createdAt = instantFromString("2022-01-01T01:01:01.544Z") - p3.updatedAt = instantFromString("2022-01-01T01:01:01.544Z") - p3.originalResponse = gson.fromJson(mockWalletToStellarTransferJson, type) - wantPaymentHistory.payments.add(p3) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @Test - fun test_getTransfers_paginationResponse() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/transfers?pageSize=2&walletId=1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWalletToWalletTransferJson, - $mockWalletToStellarTransferJson - ] - }""".trimIndent() - ) - "/v1/configuration" -> return getDistAccountIdMockResponse() - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - // Let's use reflection to access the private method - val getTransfersMono = - (service as CirclePaymentService).getTransfers("1000066041", null, null, 2) - var paymentHistory: PaymentHistory? = null - assertDoesNotThrow { - paymentHistory = getTransfersMono.block()!!.toPaymentHistory(2, merchantAccount, "1000066041") - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = "c58e2613-a808-4075-956c-e576787afb3b" - wantPaymentHistory.afterCursor = "a8997020-3da7-4543-bc4a-5ae8c7ce346d" - - val gson = Gson() - val type = object : TypeToken?>() {}.type - - val p1 = Payment() - p1.id = "c58e2613-a808-4075-956c-e576787afb3b" - p1.sourceAccount = merchantAccount - p1.destinationAccount = - Account(PaymentNetwork.CIRCLE, "1000067536", CircleWallet.defaultCapabilities()) - p1.balance = Balance("0.91", "circle:USD") - p1.status = Payment.Status.PENDING - p1.createdAt = instantFromString("2022-02-07T19:50:23.408Z") - p1.updatedAt = instantFromString("2022-02-07T19:50:23.408Z") - p1.originalResponse = gson.fromJson(mockWalletToWalletTransferJson, type) - wantPaymentHistory.payments.add(p1) - - val p2 = Payment() - p2.id = "a8997020-3da7-4543-bc4a-5ae8c7ce346d" - p2.sourceAccount = merchantAccount - p2.destinationAccount = - Account( - PaymentNetwork.STELLAR, - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - Account.Capabilities(PaymentNetwork.STELLAR) - ) - p2.balance = - Balance("1.00", "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - p2.idTag = "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef" - p2.status = Payment.Status.SUCCESSFUL - p2.createdAt = instantFromString("2022-01-01T01:01:01.544Z") - p2.updatedAt = instantFromString("2022-01-01T01:01:01.544Z") - p2.originalResponse = gson.fromJson(mockWalletToStellarTransferJson, type) - wantPaymentHistory.payments.add(p2) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @Test - fun test_getPayouts() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/payouts?pageSize=50&source=1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWalletToWirePayoutJson - ] - }""".trimIndent() - ) - "/v1/configuration" -> return getDistAccountIdMockResponse() - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val getPayoutsMono = (service as CirclePaymentService).getPayouts("1000066041", null, null, 50) - var paymentHistory: PaymentHistory? = null - assertDoesNotThrow { - paymentHistory = getPayoutsMono.block()!!.toPaymentHistory(50, merchantAccount) - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = "6588a352-5131-4711-a264-e405f38d752d" - - val p = Payment() - p.id = "6588a352-5131-4711-a264-e405f38d752d" - p.sourceAccount = merchantAccount - p.destinationAccount = - Account( - PaymentNetwork.BANK_WIRE, - "6c87da10-feb8-484f-822c-2083ed762d25", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ) - p.balance = Balance("3.00", "iso4217:USD") - p.status = Payment.Status.SUCCESSFUL - p.createdAt = instantFromString("2022-02-03T15:41:25.286Z") - p.updatedAt = instantFromString("2022-02-03T16:00:31.697Z") - val gson = Gson() - val type = object : TypeToken?>() {}.type - p.originalResponse = gson.fromJson(mockWalletToWirePayoutJson, type) - wantPaymentHistory.payments.add(p) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @Test - fun test_getPayouts_paginationResponse() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/payouts?pageSize=1&source=1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWalletToWirePayoutJson - ] - }""".trimIndent() - ) - "/v1/configuration" -> return getDistAccountIdMockResponse() - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val getPayoutsMono = (service as CirclePaymentService).getPayouts("1000066041", null, null, 1) - var paymentHistory: PaymentHistory? = null - assertDoesNotThrow { - paymentHistory = getPayoutsMono.block()!!.toPaymentHistory(1, merchantAccount) - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = "6588a352-5131-4711-a264-e405f38d752d" - wantPaymentHistory.afterCursor = "6588a352-5131-4711-a264-e405f38d752d" - - val p = Payment() - p.id = "6588a352-5131-4711-a264-e405f38d752d" - p.sourceAccount = merchantAccount - p.destinationAccount = - Account( - PaymentNetwork.BANK_WIRE, - "6c87da10-feb8-484f-822c-2083ed762d25", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ) - p.balance = Balance("3.00", "iso4217:USD") - p.status = Payment.Status.SUCCESSFUL - p.createdAt = instantFromString("2022-02-03T15:41:25.286Z") - p.updatedAt = instantFromString("2022-02-03T16:00:31.697Z") - val gson = Gson() - val type = object : TypeToken?>() {}.type - p.originalResponse = gson.fromJson(mockWalletToWirePayoutJson, type) - wantPaymentHistory.payments.add(p) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @Test - fun test_getIncomingPayments() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/payments?pageSize=50" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWireToWalletPaymentJson - ] - }""".trimIndent() - ) - "/v1/configuration" -> return getDistAccountIdMockResponse() - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val getPaymentsMono = - (service as CirclePaymentService).getIncomingPayments("1000066041", null, null, 50) - var paymentHistory: PaymentHistory? = null - assertDoesNotThrow { - paymentHistory = getPaymentsMono.block()!!.toPaymentHistory(50, merchantAccount) - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = "acc622bf-89e1-447c-8588-1bdead8e41a3" - - val p = Payment() - p.id = "acc622bf-89e1-447c-8588-1bdead8e41a3" - p.sourceAccount = - Account( - PaymentNetwork.BANK_WIRE, - "a4e76642-81c5-47ca-9229-ebd64efd74a7", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ) - p.destinationAccount = merchantAccount - p.balance = Balance("1000.00", "circle:USD") - p.status = Payment.Status.SUCCESSFUL - p.createdAt = instantFromString("2022-02-21T19:20:01.438Z") - p.updatedAt = instantFromString("2022-02-21T19:28:01.901Z") - val gson = Gson() - val type = object : TypeToken?>() {}.type - p.originalResponse = gson.fromJson(mockWireToWalletPaymentJson, type) - wantPaymentHistory.payments.add(p) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @Test - fun test_getIncomingPayments_paginationResponse() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/payments?pageSize=1" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWireToWalletPaymentJson - ] - }""".trimIndent() - ) - "/v1/configuration" -> return getDistAccountIdMockResponse() - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val getPaymentsMono = - (service as CirclePaymentService).getIncomingPayments("1000066041", null, null, 1) - var paymentHistory: PaymentHistory? = null - assertDoesNotThrow { - paymentHistory = getPaymentsMono.block()!!.toPaymentHistory(1, merchantAccount) - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = "acc622bf-89e1-447c-8588-1bdead8e41a3" - wantPaymentHistory.afterCursor = "acc622bf-89e1-447c-8588-1bdead8e41a3" - - val p = Payment() - p.id = "acc622bf-89e1-447c-8588-1bdead8e41a3" - p.sourceAccount = - Account( - PaymentNetwork.BANK_WIRE, - "a4e76642-81c5-47ca-9229-ebd64efd74a7", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ) - p.destinationAccount = merchantAccount - p.balance = Balance("1000.00", "circle:USD") - p.status = Payment.Status.SUCCESSFUL - p.createdAt = instantFromString("2022-02-21T19:20:01.438Z") - p.updatedAt = instantFromString("2022-02-21T19:28:01.901Z") - val gson = Gson() - val type = object : TypeToken?>() {}.type - p.originalResponse = gson.fromJson(mockWireToWalletPaymentJson, type) - wantPaymentHistory.payments.add(p) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @ParameterizedTest - @CsvSource( - value = - [ - "transfers,,,/v1/transfers?pageSize=1&walletId=1000066041", - "transfers,before,,/v1/transfers?pageSize=1&walletId=1000066041&pageBefore=before", - "transfers,,after,/v1/transfers?pageSize=1&walletId=1000066041&pageAfter=after", - "transfers,before,after,/v1/transfers?pageSize=1&walletId=1000066041&pageAfter=after", - "payouts,,,/v1/payouts?pageSize=1&source=1000066041", - "payouts,before,,/v1/payouts?pageSize=1&source=1000066041&pageBefore=before", - "payouts,,after,/v1/payouts?pageSize=1&source=1000066041&pageAfter=after", - "payouts,before,after,/v1/payouts?pageSize=1&source=1000066041&pageAfter=after", - "payments,,,/v1/payments?pageSize=1&source=1000066041", - "payments,before,,/v1/payments?pageSize=1&source=1000066041&pageBefore=before", - "payments,,after,/v1/payments?pageSize=1&source=1000066041&pageAfter=after", - "payments,before,after,/v1/payments?pageSize=1&source=1000066041&pageAfter=after", - ] - ) - fun test_getTransfersOrPayoutsOrPayments_paginationRequestUri( - uri: String, - beforeCursor: String?, - afterCursor: String? - ) { - val dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - if (request.path!!.startsWith("/v1/$uri")) { - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("""{ - "data": [] - }""".trimIndent()) - } - - if (request.path.equals("/v1/configuration")) { - return getDistAccountIdMockResponse() - } - - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var paymentHistory: PaymentHistory? = null - when (uri) { - "transfers" -> { - assertDoesNotThrow { - paymentHistory = - (service as CirclePaymentService) - .getTransfers("1000066041", beforeCursor, afterCursor, 1) - .block()!! - .toPaymentHistory(1, merchantAccount, "1000066041") - } - } - "payouts" -> { - assertDoesNotThrow { - paymentHistory = - (service as CirclePaymentService) - .getPayouts("1000066041", beforeCursor, afterCursor, 1) - .block()!! - .toPaymentHistory(1, merchantAccount) - } - } - "payments" -> { - assertDoesNotThrow { - paymentHistory = - (service as CirclePaymentService) - .getIncomingPayments("1000066041", beforeCursor, afterCursor, 1) - .block()!! - .toPaymentHistory(1, merchantAccount) - } - } - else -> { - throw RuntimeException("INVALID URI FOR TEST") - } - } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - assertEquals(wantPaymentHistory, paymentHistory) - - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("Bearer ", request.headers["Authorization"]) - } - - @Test - fun test_getAccountHistory() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/transfers?pageSize=50&walletId=1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWalletToWalletTransferJson, - $mockStellarToWalletTransferJson, - $mockWalletToStellarTransferJson - ] - }""".trimIndent() - ) - "/v1/payouts?pageSize=50&source=1000066041" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWalletToWirePayoutJson - ] - }""".trimIndent() - ) - "/v1/payments?pageSize=50" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": [ - $mockWireToWalletPaymentJson - ] - }""".trimIndent() - ) - "/transactions/fb8947c67856d8eb444211c1927d92bcf14abcfb34cdd27fc9e604b15d208fd1/payments" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody(mockStellarPathPaymentResponsePageBody) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var paymentHistory: PaymentHistory? = null - val getAccountHistoryMono = service.getAccountPaymentHistory("1000066041", null, null) - assertDoesNotThrow { paymentHistory = getAccountHistoryMono.block() } - - val wantPaymentHistory = PaymentHistory(merchantAccount) - wantPaymentHistory.beforeCursor = - "c58e2613-a808-4075-956c-e576787afb3b:6588a352-5131-4711-a264-e405f38d752d:acc622bf-89e1-447c-8588-1bdead8e41a3" - - val gson = Gson() - val type = object : TypeToken?>() {}.type - - val p0 = Payment() - p0.id = "acc622bf-89e1-447c-8588-1bdead8e41a3" - p0.sourceAccount = - Account( - PaymentNetwork.BANK_WIRE, - "a4e76642-81c5-47ca-9229-ebd64efd74a7", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ) - p0.destinationAccount = merchantAccount - p0.balance = Balance("1000.00", "circle:USD") - p0.status = Payment.Status.SUCCESSFUL - p0.createdAt = instantFromString("2022-02-21T19:20:01.438Z") - p0.updatedAt = instantFromString("2022-02-21T19:28:01.901Z") - p0.originalResponse = gson.fromJson(mockWireToWalletPaymentJson, type) - wantPaymentHistory.payments.add(p0) - - val p1 = Payment() - p1.id = "c58e2613-a808-4075-956c-e576787afb3b" - p1.sourceAccount = merchantAccount - p1.destinationAccount = - Account(PaymentNetwork.CIRCLE, "1000067536", CircleWallet.defaultCapabilities()) - p1.balance = Balance("0.91", "circle:USD") - p1.status = Payment.Status.PENDING - p1.createdAt = instantFromString("2022-02-07T19:50:23.408Z") - p1.updatedAt = instantFromString("2022-02-07T19:50:23.408Z") - p1.originalResponse = gson.fromJson(mockWalletToWalletTransferJson, type) - wantPaymentHistory.payments.add(p1) - - val p2 = Payment() - p2.id = "7f131f58-a8a0-3dc2-be05-6a015c69de35" - p2.sourceAccount = - Account( - PaymentNetwork.STELLAR, - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - Account.Capabilities(PaymentNetwork.STELLAR) - ) - p2.destinationAccount = merchantAccount - p2.balance = Balance("1.50", "circle:USD") - p2.idTag = "fb8947c67856d8eb444211c1927d92bcf14abcfb34cdd27fc9e604b15d208fd1" - p2.status = Payment.Status.SUCCESSFUL - p2.createdAt = instantFromString("2022-02-07T18:02:17.999Z") - p2.updatedAt = instantFromString("2022-02-07T18:02:17.999Z") - p2.originalResponse = gson.fromJson(mockStellarToWalletTransferJson, type) - wantPaymentHistory.payments.add(p2) - - val p3 = Payment() - p3.id = "6588a352-5131-4711-a264-e405f38d752d" - p3.sourceAccount = merchantAccount - p3.destinationAccount = - Account( - PaymentNetwork.BANK_WIRE, - "6c87da10-feb8-484f-822c-2083ed762d25", - Account.Capabilities(PaymentNetwork.BANK_WIRE) - ) - p3.balance = Balance("3.00", "iso4217:USD") - p3.status = Payment.Status.SUCCESSFUL - p3.createdAt = instantFromString("2022-02-03T15:41:25.286Z") - p3.updatedAt = instantFromString("2022-02-03T16:00:31.697Z") - p3.originalResponse = gson.fromJson(mockWalletToWirePayoutJson, type) - wantPaymentHistory.payments.add(p3) - - val p4 = Payment() - p4.id = "a8997020-3da7-4543-bc4a-5ae8c7ce346d" - p4.sourceAccount = merchantAccount - p4.destinationAccount = - Account( - PaymentNetwork.STELLAR, - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - Account.Capabilities(PaymentNetwork.STELLAR) - ) - p4.balance = - Balance("1.00", "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - p4.idTag = "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef" - p4.status = Payment.Status.SUCCESSFUL - p4.createdAt = instantFromString("2022-01-01T01:01:01.544Z") - p4.updatedAt = instantFromString("2022-01-01T01:01:01.544Z") - p4.originalResponse = gson.fromJson(mockWalletToStellarTransferJson, type) - wantPaymentHistory.payments.add(p4) - - assertEquals(wantPaymentHistory, paymentHistory) - } - - @Test - fun test_getListOfAddresses() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/wallets/1000066041/addresses" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody(mockGetListOfAddressesBody) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val service = this.service as CirclePaymentService - var response: CircleListResponse? = null - assertDoesNotThrow { response = service.getListOfAddresses("1000066041").block() } - - val wantAddresses = ArrayList() - wantAddresses.add( - CircleBlockchainAddress( - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - "2454278437550473431", - "USD", - "XLM" - ) - ) - wantAddresses.add( - CircleBlockchainAddress( - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - "4560730744420812493", - "USD", - "XLM" - ) - ) - wantAddresses.add( - CircleBlockchainAddress("TLiSMwSrVp8YZaqt7RRcAZoptT1kmpA9sC", null, "USD", "TRX") - ) - val wantResponse = CircleListResponse() - wantResponse.data = wantAddresses - - assertEquals(wantResponse, response) - - val getRequest = server.takeRequest() - assertThat(getRequest.path, CoreMatchers.endsWith("/v1/wallets/1000066041/addresses")) - assertEquals("GET", getRequest.method) - assertEquals("application/json", getRequest.headers["Content-Type"]) - assertEquals("Bearer ", getRequest.headers["Authorization"]) - } - - @Test - fun test_createNewStellarAddress() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/wallets/1000066041/addresses" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("""{"data":$mockAddressJson}""") - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val service = this.service as CirclePaymentService - var response: CircleDetailResponse? = null - assertDoesNotThrow { response = service.createNewStellarAddress("1000066041").block() } - - val wantResponse = CircleDetailResponse() - wantResponse.data = - CircleBlockchainAddress( - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - "2454278437550473431", - "USD", - "XLM" - ) - - assertEquals(wantResponse, response) - - val request = server.takeRequest() - assertThat(request.path, CoreMatchers.endsWith("/v1/wallets/1000066041/addresses")) - assertEquals("POST", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - val gotBody = request.body.readUtf8() - val wantBody = """{"currency": "USD", "chain": "XLM"}""" - JSONAssert.assertEquals(wantBody, gotBody, false) - } - - @Test - fun test_getOrCreateStellarAddress() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - - if (request.path == "/v1/wallets/1000066041/addresses" && request.method == "GET") { - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("""{"data": []}""") - } - - if (request.path == "/v1/wallets/1000066041/addresses" && request.method == "POST") { - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("""{"data":$mockAddressJson}""") - } - - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val service = this.service as CirclePaymentService - var address: CircleBlockchainAddress? = null - assertDoesNotThrow { address = service.getOrCreateStellarAddress("1000066041").block() } - - val wantAddress = - CircleBlockchainAddress( - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - "2454278437550473431", - "USD", - "XLM" - ) - assertEquals(wantAddress, address) - - assertEquals(2, server.requestCount) - - val getRequest = server.takeRequest() - assertThat(getRequest.path, CoreMatchers.endsWith("/v1/wallets/1000066041/addresses")) - assertEquals("GET", getRequest.method) - assertEquals("application/json", getRequest.headers["Content-Type"]) - assertEquals("Bearer ", getRequest.headers["Authorization"]) - - val request = server.takeRequest() - assertThat(request.path, CoreMatchers.endsWith("/v1/wallets/1000066041/addresses")) - assertEquals("POST", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - val gotBody = request.body.readUtf8() - val wantBody = """{"currency": "USD", "chain": "XLM"}""" - JSONAssert.assertEquals(wantBody, gotBody, false) - } - - @Test - fun test_getWireDepositInstructions() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/banks/wires/bank-id-here/instructions" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody(mockGetWireDepositInstructionsBody) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val service = this.service as CirclePaymentService - var response: CircleDetailResponse? = null - assertDoesNotThrow { - response = service.getWireDepositInstructions("1000066041", "bank-id-here").block() - } - - val wantWireInstructions = CircleWireDepositInstructions() - wantWireInstructions.trackingRef = "CIR2KMMZEJ" - val wantBeneficiary = CircleWireDepositInstructions.Beneficiary() - wantBeneficiary.name = "CIRCLE INTERNET FINANCIAL INC" - wantBeneficiary.address1 = "1 MAIN STREET" - wantBeneficiary.address2 = "SUITE 1" - wantWireInstructions.beneficiary = wantBeneficiary - val wantBeneficiaryBank = CircleWireDepositInstructions.BeneficiaryBank() - wantBeneficiaryBank.name = "CRYPTO BANK" - wantBeneficiaryBank.address = "1 MONEY STREET" - wantBeneficiaryBank.city = "NEW YORK" - wantBeneficiaryBank.postalCode = "1001" - wantBeneficiaryBank.country = "US" - wantBeneficiaryBank.swiftCode = "CRYPTO99" - wantBeneficiaryBank.routingNumber = "999999999" - wantBeneficiaryBank.accountNumber = "1000000001" - wantWireInstructions.beneficiaryBank = wantBeneficiaryBank - - val wantResponse = CircleDetailResponse() - wantResponse.data = wantWireInstructions - assertEquals(wantResponse, response) - - assertEquals(2, server.requestCount) - - val validateSecretKeyRequest = server.takeRequest() - assertEquals("GET", validateSecretKeyRequest.method) - assertEquals("application/json", validateSecretKeyRequest.headers["Content-Type"]) - assertEquals("Bearer ", validateSecretKeyRequest.headers["Authorization"]) - assertTrue(validateSecretKeyRequest.path!!.endsWith("/v1/configuration")) - - val wireInstructionsRequest = server.takeRequest() - assertThat( - wireInstructionsRequest.path, - CoreMatchers.endsWith("/v1/banks/wires/bank-id-here/instructions") - ) - assertEquals("GET", wireInstructionsRequest.method) - assertEquals("application/json", wireInstructionsRequest.headers["Content-Type"]) - assertEquals("Bearer ", wireInstructionsRequest.headers["Authorization"]) - } - - @Test - fun test_getWireDepositInstructions_notTheDistributionAccount() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val service = this.service as CirclePaymentService - val ex: HttpException = assertThrows { - service.getWireDepositInstructions("1000646072", "bank-id-here").block() - } - val wantException = - HttpException(400, "in circle, only the distribution account id can receive wire payments") - assertEquals(wantException, ex) - } - - @Test - fun test_getDepositInstructions_parameterValidation() { - // empty beneficiary account id - var config = DepositRequirements(null, null, null, null) - var ex: HttpException = assertThrows { service.getDepositInstructions(config).block() } - assertEquals(HttpException(400, "beneficiary account id cannot be empty"), ex) - - // invalid currency name - config = DepositRequirements("1000066041", null, null, null) - ex = assertThrows { service.getDepositInstructions(config).block() } - assertEquals( - HttpException(400, "the only receiving currency in a circle account is \"circle:USD\""), - ex - ) - - // missing bank id - config = - DepositRequirements("1000066041", null, PaymentNetwork.BANK_WIRE, CircleAsset.circleUSD()) - ex = assertThrows { service.getDepositInstructions(config).block() } - assertEquals( - HttpException( - 400, - "please provide a valid Circle bank id for the intermediaryAccountId field when requesting instructions for bank wire deposits" - ), - ex - ) - } - - @ParameterizedTest - @NullSource - @EnumSource( - value = PaymentNetwork::class, - mode = EnumSource.Mode.EXCLUDE, - names = ["STELLAR", "CIRCLE", "BANK_WIRE"] - ) - fun test_getDepositInstructions_parameterValidation_supportedNetworks( - paymentNetwork: PaymentNetwork? - ) { - // unsupported intermediary payment network - val config = DepositRequirements("1000066041", null, paymentNetwork, CircleAsset.circleUSD()) - val ex: HttpException = assertThrows { service.getDepositInstructions(config).block() } - assertEquals( - HttpException( - 400, - """the only supported intermediary payment networks are "stellar", "circle" and "bank_wire"""" - ), - ex - ) - } - - @Test - fun test_getDepositInstructions_circle() { - var instructions: DepositInstructions? = null - val config = - DepositRequirements("1000066041", null, PaymentNetwork.CIRCLE, CircleAsset.circleUSD()) - assertDoesNotThrow { instructions = service.getDepositInstructions(config).block() } - - val wantInstructions = - DepositInstructions( - "1000066041", - null, - PaymentNetwork.CIRCLE, - "1000066041", - null, - PaymentNetwork.CIRCLE, - "circle:USD", - null - ) - assertEquals(wantInstructions, instructions) - assertEquals(0, server.requestCount) - } - - @Test - fun test_getDepositInstructions_stellar() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - - if (request.path == "/v1/wallets/1000066041/addresses" && request.method == "GET") { - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("""{"data": []}""") - } - - if (request.path == "/v1/wallets/1000066041/addresses" && request.method == "POST") { - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody("""{"data":$mockAddressJson}""") - } - - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var instructions: DepositInstructions? = null - val config = - DepositRequirements("1000066041", null, PaymentNetwork.STELLAR, CircleAsset.circleUSD()) - assertDoesNotThrow { instructions = service.getDepositInstructions(config).block() } - - val wantInstructions = - DepositInstructions( - "1000066041", - null, - PaymentNetwork.CIRCLE, - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - "2454278437550473431", - PaymentNetwork.STELLAR, - CircleAsset.stellarUSDC(Network.TESTNET), - null - ) - assertEquals(wantInstructions, instructions) - - assertEquals(2, server.requestCount) - - val getRequest = server.takeRequest() - assertThat(getRequest.path, CoreMatchers.endsWith("/v1/wallets/1000066041/addresses")) - assertEquals("GET", getRequest.method) - assertEquals("application/json", getRequest.headers["Content-Type"]) - assertEquals("Bearer ", getRequest.headers["Authorization"]) - - val request = server.takeRequest() - assertThat(request.path, CoreMatchers.endsWith("/v1/wallets/1000066041/addresses")) - assertEquals("POST", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer ", request.headers["Authorization"]) - val gotBody = request.body.readUtf8() - val wantBody = """{"currency": "USD", "chain": "XLM"}""" - JSONAssert.assertEquals(wantBody, gotBody, false) - } - - @Test - fun test_getDepositInstructions_wire() { - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> return getDistAccountIdMockResponse() - "/v1/banks/wires/bank-id-here/instructions" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody(mockGetWireDepositInstructionsBody) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - var instructions: DepositInstructions? = null - val config = - DepositRequirements( - "1000066041", - null, - PaymentNetwork.BANK_WIRE, - "bank-id-here", - CircleAsset.circleUSD() - ) - assertDoesNotThrow { instructions = service.getDepositInstructions(config).block() } - - val wantInstructions = - DepositInstructions( - "1000066041", - null, - PaymentNetwork.CIRCLE, - "CIR2KMMZEJ", - null, - PaymentNetwork.BANK_WIRE, - "iso4217:USD", - mapOf( - "trackingRef" to "CIR2KMMZEJ", - "beneficiary" to - mapOf( - "name" to "CIRCLE INTERNET FINANCIAL INC", - "address1" to "1 MAIN STREET", - "address2" to "SUITE 1" - ), - "beneficiaryBank" to - mapOf( - "name" to "CRYPTO BANK", - "address" to "1 MONEY STREET", - "city" to "NEW YORK", - "postalCode" to "1001", - "country" to "US", - "swiftCode" to "CRYPTO99", - "routingNumber" to "999999999", - "accountNumber" to "1000000001" - ) - ) - ) - assertEquals(wantInstructions, instructions) - - assertEquals(2, server.requestCount) - - val validateSecretKeyRequest = server.takeRequest() - assertEquals("GET", validateSecretKeyRequest.method) - assertEquals("application/json", validateSecretKeyRequest.headers["Content-Type"]) - assertEquals("Bearer ", validateSecretKeyRequest.headers["Authorization"]) - assertTrue(validateSecretKeyRequest.path!!.endsWith("/v1/configuration")) - - val wireInstructionsRequest = server.takeRequest() - assertThat( - wireInstructionsRequest.path, - CoreMatchers.endsWith("/v1/banks/wires/bank-id-here/instructions") - ) - assertEquals("GET", wireInstructionsRequest.method) - assertEquals("application/json", wireInstructionsRequest.headers["Content-Type"]) - assertEquals("Bearer ", wireInstructionsRequest.headers["Authorization"]) - } - - @Test - fun test_errorHandling() { - val badRequestResponse = - MockResponse() - .setResponseCode(400) - .addHeader("Content-Type", "application/json") - .setBody("{\"code\":2,\"message\":\"Request body contains unprocessable entity.\"}") - val validateSecretKeyResponse = getDistAccountIdMockResponse() - val mainAccountResponse = - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "available":[], - "unsettled": [ - { - "amount":"100.00", - "currency":"USD" - } - ] - } - }""" - ) - val emptyListResponse = - MockResponse().addHeader("Content-Type", "application/json").setBody("""{"data":[]}""") - - // Access private method getMainAccountBalances - val getMerchantAccountUnsettledBalancesMethod: Method = - CirclePaymentService::class.java.getDeclaredMethod("getMerchantAccountUnsettledBalances") - assert(getMerchantAccountUnsettledBalancesMethod.trySetAccessible()) - - // Access private method getCircleWallet - val getCircleWalletMethod: Method = - CirclePaymentService::class.java.getDeclaredMethod("getCircleWallet", String::class.java) - assert(getCircleWalletMethod.trySetAccessible()) - - // declare aux method - val validateErrHandling = { testCase: ErrorHandlingTestCase -> - // run project reactor synchronously - testCase.prepareMockWebServer(server) - var request = testCase.requestMono - val thrown = assertThrows { request.block() } - assertEquals(HttpException(400, "Request body contains unprocessable entity.", "2"), thrown) - - // run project reactor asynchronously - var didRunAsyncTask = false - testCase.prepareMockWebServer(server) - request = - testCase.requestMono.onErrorResume { ex -> - assertInstanceOf(HttpException::class.java, ex) - assertEquals(HttpException(400, "Request body contains unprocessable entity.", "2"), ex) - didRunAsyncTask = true - Mono.empty() - } - assertDoesNotThrow { request.block() } - assertTrue(didRunAsyncTask) - } - - // --- tests with sync/serial requests --- - validateErrHandling(ErrorHandlingTestCase(service.ping(), listOf(badRequestResponse))) - validateErrHandling( - ErrorHandlingTestCase(service.distributionAccountAddress, listOf(badRequestResponse)) - ) - validateErrHandling( - ErrorHandlingTestCase(service.getAccount("random_id"), listOf(badRequestResponse)) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.getAccount("random_id"), - listOf(validateSecretKeyResponse, badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - getMerchantAccountUnsettledBalancesMethod.invoke(service) as Mono<*>, - listOf(badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase(service.createAccount(null), listOf(badRequestResponse)) - ) - validateErrHandling( - ErrorHandlingTestCase( - getCircleWalletMethod.invoke(service, "random_id") as Mono<*>, - listOf(badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase(service.createAccount(null), listOf(badRequestResponse)) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).getListOfAddresses("any_id"), - listOf(badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).createNewStellarAddress("any_id"), - listOf(badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).getOrCreateStellarAddress("any_id"), - listOf(emptyListResponse, badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).getWireDepositInstructions("1000066041", "any_bank_id"), - listOf(badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.getDepositInstructions( - DepositRequirements("1000066041", PaymentNetwork.STELLAR, CircleAsset.circleUSD()) - ), - listOf(badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.getDepositInstructions( - DepositRequirements( - "1000066041", - null, - PaymentNetwork.BANK_WIRE, - "bank-id-here", - CircleAsset.circleUSD() - ) - ), - listOf(badRequestResponse) - ) - ) - // --- tests with async/parallel requests --- - // ATTENTION, make sure to run parallel tests at the end, if you try to run a serial - // test - // after a parallel - // test, the server dispatcher will throw an exception. - validateErrHandling( - ErrorHandlingTestCase( - service.getAccount("1000066041"), - hashMapOf( - "/v1/configuration" to validateSecretKeyResponse, - "/v1/businessAccount/balances" to mainAccountResponse, - "/v1/wallets/1000066041" to badRequestResponse - ) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.sendPayment( - Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities()), - Account(PaymentNetwork.CIRCLE, "1000067536", Account.Capabilities()), - CircleAsset.circleUSD(), - BigDecimal.valueOf(1) - ), - hashMapOf( - "/v1/configuration" to validateSecretKeyResponse, - "/v1/transfers" to badRequestResponse - ) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.sendPayment( - Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities()), - Account( - PaymentNetwork.STELLAR, - "GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", - "test tag", - Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR) - ), - "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", - BigDecimal(1) - ), - hashMapOf( - "/v1/configuration" to validateSecretKeyResponse, - "/v1/transfers" to badRequestResponse - ) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.sendPayment( - Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities()), - Account( - PaymentNetwork.BANK_WIRE, - "6c87da10-feb8-484f-822c-2083ed762d25", - "test@mail.com", - Account.Capabilities() - ), - CircleAsset.fiatUSD(), - BigDecimal(1) - ), - hashMapOf( - "/v1/configuration" to validateSecretKeyResponse, - "/v1/payouts" to badRequestResponse - ), - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).getTransfers("1000066041", null, null, null), - hashMapOf("/v1/transfers?pageSize=50&walletId=1000066041" to badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).getPayouts("1000066041", null, null, null), - hashMapOf("/v1/payouts?pageSize=50&source=1000066041" to badRequestResponse) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - (service as CirclePaymentService).getIncomingPayments("1000066041", null, null, null), - hashMapOf( - "/v1/configuration" to validateSecretKeyResponse, - "/v1/payments?pageSize=50" to badRequestResponse - ) - ) - ) - validateErrHandling( - ErrorHandlingTestCase( - service.getAccountPaymentHistory("1000066041", null, null), - hashMapOf( - "/v1/configuration" to validateSecretKeyResponse, - "/v1/transfers?pageSize=50&walletId=1000066041" to badRequestResponse, - "/v1/payouts?pageSize=50&source=1000066041" to badRequestResponse, - "/v1/payments?pageSize=50" to badRequestResponse - ) - ) - ) - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt deleted file mode 100644 index 2f04bdcea9..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/CircleResponseErrorHandlerTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.stellar.anchor.paymentservice.circle - -import com.google.gson.Gson -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import io.netty.handler.codec.http.HttpResponseStatus -import java.io.IOException -import org.junit.jupiter.api.Assertions.assertEquals -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 -import reactor.netty.http.client.HttpClientResponse - -class CircleResponseErrorHandlerTest { - internal class CircleResponseErrorHandlerImpl : CircleResponseErrorHandler - - private lateinit var gson: Gson - - @BeforeEach - @Throws(IOException::class) - fun setUp() { - gson = GsonUtils.getInstance() - } - - @Test - fun test_private_handleCircleError() { - // mock objects - val response = mockk() - every { response.status() } returns HttpResponseStatus.BAD_REQUEST - - val bodyBytesMono = mockk() - every { bodyBytesMono.asString() } returns - Mono.just("{\"code\":2,\"message\":\"Request body contains unprocessable entity.\"}") - - // run and test - val impl = CircleResponseErrorHandlerImpl() - val ex = - assertThrows { - impl.handleResponseSingle().apply(response, bodyBytesMono).block() - } - verify { response.status() } - verify { bodyBytesMono.asString() } - assertEquals(HttpException(400, "Request body contains unprocessable entity.", "2"), ex) - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt deleted file mode 100644 index f77a3d52fd..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/ErrorHandlingTestCase.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.stellar.anchor.paymentservice.circle - -import okhttp3.mockwebserver.Dispatcher -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import okhttp3.mockwebserver.RecordedRequest -import reactor.core.publisher.Mono - -class ErrorHandlingTestCase { - val requestMono: Mono<*> - private var mockResponses: List? = null - var mockResponsesMap: Map? = null - private set - - constructor(_requestMono: Mono<*>, _mockResponses: List) { - this.requestMono = _requestMono - this.mockResponses = _mockResponses - } - - constructor(_requestMono: Mono<*>, _mockResponsesMap: Map) { - this.requestMono = _requestMono - this.mockResponsesMap = _mockResponsesMap - } - - private fun getDispatcher(): Dispatcher? { - if (mockResponsesMap == null) { - return null - } - - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - - if (!mockResponsesMap!!.containsKey(request.path)) { - return MockResponse().setResponseCode(404) - } - - return mockResponsesMap!![request.path]!! - } - } - return dispatcher - } - - fun prepareMockWebServer(server: MockWebServer) { - val dispatcher = getDispatcher() - if (dispatcher != null) { - server.dispatcher = dispatcher - } else if (mockResponses != null) { - mockResponses!!.forEach { mockResponse -> server.enqueue(mockResponse) } - } - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt deleted file mode 100644 index a87bcbb6e5..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/StellarReconciliationTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package org.stellar.anchor.paymentservice.circle - -import com.google.gson.Gson -import java.io.IOException -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.hamcrest.CoreMatchers -import org.hamcrest.MatcherAssert -import org.junit.jupiter.api.Assertions.assertEquals -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.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 - -class StellarReconciliationTest { - internal class StellarReconciliationImpl(private val horizonUrl: String) : StellarReconciliation { - private val _horizonServer: Server = Server(this.horizonUrl) - - override fun getWebClient(authenticated: Boolean): HttpClient { - return NettyHttpClient.withBaseUrl(horizonUrl) - } - - override fun getHorizonServer(): Server { - return this._horizonServer - } - } - - private lateinit var gson: Gson - - @BeforeEach - @Throws(IOException::class) - fun setUp() { - gson = - GsonUtils.builder() - .registerTypeAdapter(CircleTransfer::class.java, CircleTransfer.Serialization()) - .create() - } - - @Test - fun test_updatedTransferStellarSender() { - // mock response in the server - val server = MockWebServer() - server.start() - val stellarResponse = - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody(CirclePaymentServiceTest.mockStellarPaymentResponsePageBody) - server.enqueue(stellarResponse) - - // verify the original transfer source address is null - val originalTransfer = - gson.fromJson( - CirclePaymentServiceTest.mockStellarToWalletTransferJson, - CircleTransfer::class.java - ) - assertNull(originalTransfer.source.address) - - // verify the method `.updatedStellarSenderAddress` populates the source address - val impl = StellarReconciliationImpl(server.url("").toString()) - var updatedTransfer: CircleTransfer? = null - assertDoesNotThrow { - updatedTransfer = impl.updatedStellarSenderAddress(originalTransfer).block() - } - assertEquals( - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - updatedTransfer?.source?.address - ) - - // verify the updated transfer is a new object and did not cause any change in the original - // transfer object - assertNull(originalTransfer.source.address) - - // validate the request format - val request = server.takeRequest() - assertEquals("GET", request.method) - println(request.requestUrl) - MatcherAssert.assertThat( - request.path, - CoreMatchers.endsWith( - "/transactions/fb8947c67856d8eb444211c1927d92bcf14abcfb34cdd27fc9e604b15d208fd1/payments" - ) - ) - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt deleted file mode 100644 index 4bb9880d04..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/circle/model/CircleTransferSerializationTest.kt +++ /dev/null @@ -1,132 +0,0 @@ -package org.stellar.anchor.paymentservice.circle.model - -import com.google.gson.Gson -import java.io.IOException -import java.time.Instant -import java.time.format.DateTimeFormatter -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 { - private lateinit var gson: Gson - - companion object { - const val mockCircleTransferJson = - """{ - "id":"a8997020-3da7-4543-bc4a-5ae8c7ce346d", - "source":{ - "type":"wallet", - "id":"1000066041" - }, - "destination":{ - "type":"blockchain", - "address":"GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - "chain":"XLM" - }, - "amount":{ - "amount":"1.00", - "currency":"USD" - }, - "transactionHash":"5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef", - "status":"complete", - "createDate":"2022-01-01T01:01:01.544Z" - }""" - } - - private fun instantFromString(dateStr: String): Instant { - return DateTimeFormatter.ISO_INSTANT.parse(dateStr, Instant::from) - } - - @BeforeEach - @Throws(IOException::class) - fun setUp() { - gson = - GsonUtils.builder() - .registerTypeAdapter(CircleTransfer::class.java, CircleTransfer.Serialization()) - .create() - } - - @Test - fun testDeserialize() { - val wantTransfer = CircleTransfer() - wantTransfer.id = "a8997020-3da7-4543-bc4a-5ae8c7ce346d" - wantTransfer.source = CircleTransactionParty.wallet("1000066041") - wantTransfer.destination = - CircleTransactionParty.stellar( - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - null - ) - wantTransfer.amount = CircleBalance("USD", "1.00") - wantTransfer.transactionHash = - "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef" - wantTransfer.status = CirclePaymentStatus.COMPLETE - wantTransfer.createDate = instantFromString("2022-01-01T01:01:01.544Z") - wantTransfer.originalResponse = - hashMapOf( - "id" to "a8997020-3da7-4543-bc4a-5ae8c7ce346d", - "source" to hashMapOf("id" to "1000066041", "type" to "wallet"), - "destination" to - hashMapOf( - "type" to "blockchain", - "address" to "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - "chain" to "XLM" - ), - "amount" to - hashMapOf( - "amount" to "1.00", - "currency" to "USD", - ), - "transactionHash" to "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef", - "status" to "complete", - "createDate" to "2022-01-01T01:01:01.544Z" - ) - - val transfer = gson.fromJson(mockCircleTransferJson, CircleTransfer::class.java) - assertEquals(wantTransfer, transfer) - } - - @Test - fun testSerialize() { - val transfer = CircleTransfer() - transfer.id = "a8997020-3da7-4543-bc4a-5ae8c7ce346d" - transfer.source = CircleTransactionParty.wallet("1000066041") - transfer.destination = - CircleTransactionParty.stellar( - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - null - ) - transfer.amount = CircleBalance("USD", "1.00") - transfer.transactionHash = "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef" - transfer.status = CirclePaymentStatus.COMPLETE - transfer.createDate = instantFromString("2022-01-01T01:01:01.544Z") - transfer.originalResponse = - hashMapOf( - "id" to "a8997020-3da7-4543-bc4a-5ae8c7ce346d", - "source" to hashMapOf("id" to "1000066041", "type" to "wallet"), - "destination" to - hashMapOf( - "type" to "blockchain", - "address" to "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S", - "chain" to "XLM" - ), - "amount" to - hashMapOf( - "amount" to "1.00", - "currency" to "USD", - ), - "transactionHash" to "5239ee055b1083231c6bdaaa921d3e4b3bc090577fbd909815bd5d7fe68091ef", - "status" to "complete", - "createDate" to "2022-01-01T01:01:01.544Z" - ) - - val transferJson = gson.toJson(transfer) - JSONAssert.assertEquals(mockCircleTransferJson, transferJson, true) - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt b/platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt deleted file mode 100644 index 2c66fc9a05..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/paymentservice/utils/NettyHttpClientTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.stellar.anchor.paymentservice.utils - -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.stellar.anchor.platform.payment.observer.circle.util.NettyHttpClient - -class NettyHttpClientTest { - @Test - fun testBuildUri() { - assertEquals("/", NettyHttpClient.buildUri(null, null)) - assertEquals("/foo", NettyHttpClient.buildUri("/foo", null)) - assertEquals("/foo", NettyHttpClient.buildUri("foo", null)) - assertEquals("/foo/bar", NettyHttpClient.buildUri("foo/bar", null)) - assertEquals("/foo/bar", NettyHttpClient.buildUri("/foo/bar", null)) - - assertEquals("/?key1=val1", NettyHttpClient.buildUri(null, linkedMapOf("key1" to "val1"))) - assertEquals("/foo?key1=val1", NettyHttpClient.buildUri("/foo", linkedMapOf("key1" to "val1"))) - var queryParams = linkedMapOf("key1" to "val1", "key2" to "val2") - assertEquals("/foo?key1=val1&key2=val2", NettyHttpClient.buildUri("/foo", queryParams)) - queryParams = linkedMapOf("key2" to "val2", "key1" to "val1") - assertEquals("/foo?key2=val2&key1=val1", NettyHttpClient.buildUri("/foo", queryParams)) - } -} 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 203334091d..824fe940e5 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/LogAppenderTest.kt @@ -6,6 +6,7 @@ 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.apache.logging.log4j.core.config.LoggerConfig import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach @@ -14,13 +15,20 @@ import org.junit.jupiter.params.provider.CsvSource import org.stellar.anchor.util.* class LogAppenderTest { - 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 + private lateinit var appender: Appender + private lateinit var capturedLogEvent: CapturingSlot + private lateinit var loggerContext: LoggerContext + private lateinit var rootLoggerConfig: LoggerConfig + private lateinit var lastLevel: Level + @BeforeEach fun setup() { + appender = mockk() + capturedLogEvent = slot() + loggerContext = LogManager.getContext(false) as LoggerContext + rootLoggerConfig = loggerContext.configuration.getLoggerConfig("org.stellar") + lastLevel = rootLoggerConfig.level + every { appender.name } returns "mock appender" every { appender.isStarted } returns true every { appender.append(capture(capturedLogEvent)) } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentBeansTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt similarity index 70% rename from platform/src/test/kotlin/org/stellar/anchor/platform/PaymentBeansTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt index 64f048c422..3fe6a79fa8 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentBeansTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt @@ -11,12 +11,14 @@ 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.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 +import org.stellar.anchor.platform.config.PaymentObserverConfig +import org.stellar.anchor.platform.config.PaymentObserverConfig.StellarPaymentObserverConfig +import org.stellar.anchor.platform.observer.PaymentListener +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountStore +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager +import org.stellar.anchor.platform.observer.stellar.StellarPaymentStreamerCursorStore -class PaymentBeansTest { +class PaymentObserverBeansTest { @MockK private lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore @@ -34,14 +36,14 @@ class PaymentBeansTest { @Test fun test_stellarPaymentObserverService_failure() { val assetService: AssetService = ResourceJsonAssetService("test_assets.json") - val paymentBeans = PaymentBeans() + val paymentObserverBeans = PaymentObserverBeans() val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) // assetService is null var ex = assertThrows { - paymentBeans.stellarPaymentObserverService(null, null, null, null, null) + paymentObserverBeans.stellarPaymentObserver(null, null, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) @@ -51,7 +53,14 @@ class PaymentBeansTest { every { mockEmptyAssetService.listAllAssets() } returns null ex = assertThrows { - paymentBeans.stellarPaymentObserverService(mockEmptyAssetService, null, null, null, null) + paymentObserverBeans.stellarPaymentObserver( + mockEmptyAssetService, + null, + null, + null, + null, + null + ) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) @@ -61,11 +70,12 @@ class PaymentBeansTest { every { mockStellarLessAssetService.listAllAssets() } returns listOf() ex = assertThrows { - paymentBeans.stellarPaymentObserverService( + paymentObserverBeans.stellarPaymentObserver( mockStellarLessAssetService, null, null, null, + null, null ) } @@ -75,7 +85,7 @@ class PaymentBeansTest { // paymentListeners is null ex = assertThrows { - paymentBeans.stellarPaymentObserverService(assetService, null, null, null, null) + paymentObserverBeans.stellarPaymentObserver(assetService, null, null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) @@ -83,7 +93,7 @@ class PaymentBeansTest { // paymentListeners is empty ex = assertThrows { - paymentBeans.stellarPaymentObserverService(assetService, listOf(), null, null, null) + paymentObserverBeans.stellarPaymentObserver(assetService, listOf(), null, null, null, null) } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) @@ -91,11 +101,12 @@ class PaymentBeansTest { // paymentStreamerCursorStore is null ex = assertThrows { - paymentBeans.stellarPaymentObserverService( + paymentObserverBeans.stellarPaymentObserver( assetService, mockPaymentListeners, null, null, + null, null ) } @@ -106,22 +117,23 @@ class PaymentBeansTest { every { paymentStreamerCursorStore.load() } returns null ex = assertThrows { - paymentBeans.stellarPaymentObserverService( + paymentObserverBeans.stellarPaymentObserver( assetService, mockPaymentListeners, paymentStreamerCursorStore, null, + null, null ) } assertInstanceOf(ServerErrorException::class.java, ex) - assertEquals("App config cannot be empty.", ex.message) + assertEquals("AppConfig cannot be empty.", ex.message) } @Test fun test_givenGoodManager_whenConstruct_thenOk() { // success! - val paymentBeans = PaymentBeans() + val paymentObserverBeans = PaymentObserverBeans() val assetService: AssetService = ResourceJsonAssetService("test_assets.json") val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) @@ -130,14 +142,20 @@ class PaymentBeansTest { PaymentObservingAccountsManager(paymentObservingAccountStore) val mockAppConfig = mockk() + val mockPaymentObserverConfig = mockk() + every { mockAppConfig.horizonUrl } returns "https://horizon-testnet.stellar.org" + every { mockPaymentObserverConfig.stellar } returns + StellarPaymentObserverConfig(1, 5, 1, 1, 2, 1, 2) + assertDoesNotThrow { - paymentBeans.stellarPaymentObserverService( + paymentObserverBeans.stellarPaymentObserver( assetService, mockPaymentListeners, paymentStreamerCursorStore, paymentObservingAccountsManager, - mockAppConfig + mockAppConfig, + mockPaymentObserverConfig ) } } 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 49f4d6b25d..4162c695c7 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 @@ -1,50 +1,22 @@ 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 -import org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.CIRCLE +import org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.API open class Sep31ConfigTest { @Test fun testSep31Valid() { - val circleConfig = PropertyCircleConfig() - circleConfig.circleUrl = "https://api-sandbox.circle.com" - circleConfig.apiKey = "apikey" val callbackApiConfig = CallbackApiConfig(PropertySecretConfig()) callbackApiConfig.baseUrl = "http://localhost:8080" - val sep31Config = PropertySep31Config(circleConfig, callbackApiConfig) - sep31Config.depositInfoGeneratorType = CIRCLE + val sep31Config = PropertySep31Config(callbackApiConfig) + sep31Config.depositInfoGeneratorType = API val errors = BindException(sep31Config, "sep31Config") ValidationUtils.invokeValidator(sep31Config, sep31Config, errors) assertEquals(0, errors.errorCount) - - val circleErrors = circleConfig.validate() - assertEquals(0, circleErrors.errorCount) - } - - @Test - fun testSep31BadCircleConfig() { - val circleConfig = PropertyCircleConfig() - circleConfig.circleUrl = "https://api-sandbox.circle.com" - val callbackApiConfig = CallbackApiConfig(PropertySecretConfig()) - callbackApiConfig.baseUrl = "http://localhost:8080" - - val sep31Config = PropertySep31Config(circleConfig, callbackApiConfig) - sep31Config.enabled = true - sep31Config.depositInfoGeneratorType = CIRCLE - - val errors = BindException(sep31Config, "sep31Config") - ValidationUtils.invokeValidator(sep31Config, sep31Config, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "badConfig-circle") } - - val circleErrors = circleConfig.validate() - assertEquals(1, circleErrors.errorCount) - circleErrors.message?.let { assertContains(it, "empty-apiKey") } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt index a0bafd354d..bd1e9bc364 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt @@ -14,7 +14,7 @@ import org.stellar.anchor.api.platform.HealthCheckResult import org.stellar.anchor.api.platform.HealthCheckStatus import org.stellar.anchor.api.platform.HealthCheckStatus.* import org.stellar.anchor.healthcheck.HealthCheckable -import org.stellar.anchor.platform.payment.observer.stellar.StellarPaymentObserver +import org.stellar.anchor.platform.observer.stellar.StellarPaymentObserver import org.stellar.anchor.platform.service.HealthCheckService class HealthControllerTest { 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/observer/stellar/PaymentObservingAccountsManagerTest.kt similarity index 95% rename from platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManagerTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManagerTest.kt index 770b526403..925415a21f 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/PaymentObservingAccountsManagerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManagerTest.kt @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar +package org.stellar.anchor.platform.observer.stellar import io.mockk.* import io.mockk.impl.annotations.MockK @@ -11,8 +11,8 @@ 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 +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager.AccountType.RESIDENTIAL +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager.AccountType.TRANSIENT class PaymentObservingAccountsManagerTest { @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore 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/observer/stellar/StellarPaymentObserverTest.kt similarity index 81% rename from platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserverTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt index 4fc8d26b5e..5dcd021dac 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/stellar/StellarPaymentObserverTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform.payment.observer.stellar +package org.stellar.anchor.platform.observer.stellar import com.google.gson.reflect.TypeToken import io.mockk.* @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.stellar.anchor.api.platform.HealthCheckStatus.RED +import org.stellar.anchor.platform.config.PaymentObserverConfig.StellarPaymentObserverConfig import org.stellar.sdk.Server import org.stellar.sdk.requests.RequestBuilder import org.stellar.sdk.requests.SSEStream @@ -24,7 +25,10 @@ class StellarPaymentObserverTest { const val TEST_HORIZON_URI = "https://horizon-testnet.stellar.org/" } - @MockK private lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore + @MockK lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore + @MockK lateinit var paymentObservingAccountsManager: PaymentObservingAccountsManager + + val stellarPaymentObserverConfig = StellarPaymentObserverConfig(1, 5, 1, 1, 2, 1, 2) @BeforeEach fun setUp() { @@ -42,7 +46,13 @@ class StellarPaymentObserverTest { // 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) + StellarPaymentObserver( + TEST_HORIZON_URI, + stellarPaymentObserverConfig, + null, + paymentObservingAccountsManager, + paymentStreamerCursorStore + ) var gotCursor = stellarObserver.fetchStreamingCursor() assertEquals("123", gotCursor) @@ -53,7 +63,13 @@ class StellarPaymentObserverTest { every { paymentStreamerCursorStore.load() } returns null mockkConstructor(Server::class) stellarObserver = - StellarPaymentObserver(TEST_HORIZON_URI, null, null, paymentStreamerCursorStore) + StellarPaymentObserver( + TEST_HORIZON_URI, + stellarPaymentObserverConfig, + null, + paymentObservingAccountsManager, + paymentStreamerCursorStore + ) // 2.1 If fetching from the network throws an error, we return `null` every { @@ -135,7 +151,15 @@ class StellarPaymentObserverTest { fun `test if SSEStream exception will leave the observer in STREAM_ERROR state`() { val stream: SSEStream = mockk(relaxed = true) val observer = - spyk(StellarPaymentObserver(TEST_HORIZON_URI, null, null, paymentStreamerCursorStore)) + spyk( + StellarPaymentObserver( + TEST_HORIZON_URI, + stellarPaymentObserverConfig, + null, + paymentObservingAccountsManager, + paymentStreamerCursorStore + ) + ) every { observer.startSSEStream() } returns stream observer.start() observer.handleFailure(Optional.of(SSLProtocolException(""))) 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 deleted file mode 100644 index b9170d5093..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/payment/observer/CirclePaymentObserverServiceTest.kt +++ /dev/null @@ -1,632 +0,0 @@ -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 -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.UnprocessableEntityException -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 -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.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 -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: PaymentObserverConfig - @MockK private lateinit var circlePaymentObserverService: CirclePaymentObserverService - @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) - - every { horizon.stellarNetworkPassphrase } returns "Test SDF Network ; September 2015" - circlePaymentObserverService = - CirclePaymentObserverService( - httpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - - server = MockWebServer() - server.start() - } - - @AfterEach - fun teardown() { - clearAllMocks() - unmockkAll() - - 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( - fromMapToCircleNotification(unsupportedNotification) - ) - } - assertEquals("Not handling notification of unsupported type \"\".", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - - // Unsupported type is ignored - unsupportedNotification = mapOf("Type" to "ABC") - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(unsupportedNotification) - ) - } - assertEquals("Not handling notification of unsupported type \"ABC\".", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - } - - @Test - fun test_handleCircleNotification_handleSubscriptionConfirmationNotification() { - val (server, newHttpClient) = mockSslServerAndClient() - - // missing subscribeUrl - var subConfirmationNotification = mapOf("Type" to "SubscriptionConfirmation") - - var ex: BadRequestException = assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals( - "Notification body of type SubscriptionConfirmation is missing subscription URL.", - ex.message - ) - assertInstanceOf(BadRequestException::class.java, ex) - - // Test IOException - every { httpClient.newCall(any()) } throws IOException("Some random IO error!") - val serverUrl = server.url("").toString() - subConfirmationNotification = - mapOf("Type" to "SubscriptionConfirmation", "SubscribeURL" to serverUrl) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Failed to call \"SubscribeURL\" endpoint.", ex.message) - assertInstanceOf(BadRequestException::class.java, ex) - - // Failing http request - circlePaymentObserverService = - CirclePaymentObserverService( - newHttpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - - val badRequestResponse = - MockResponse() - .addHeader("Content-Type", "application/json") - .setResponseCode(400) - .setBody("""{ "error": "Something went wrong with your request." }""") - server.enqueue(badRequestResponse) - - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Calling the \"SubscribeURL\" endpoint didn't succeed.", ex.message) - assertInstanceOf(BadRequestException::class.java, ex) - - var request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals(serverUrl, request.requestUrl.toString()) - assertNotNull(request.body.readUtf8()) - - // Success - val successResponse = - MockResponse() - .addHeader("Content-Type", "application/json") - .setResponseCode(200) - .setBody("""{ "success": "ok" }""") - server.enqueue(successResponse) - assertDoesNotThrow { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - - request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals(serverUrl, request.requestUrl.toString()) - assertNotNull(request.body.readUtf8()) - } - - @Test - fun test_handleCircleNotification_handleTransferNotification_failure() { - // missing Message - var subConfirmationNotification = mapOf("Type" to "Notification") - var ex: AnchorException = assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Notification body of type Notification is missing a message.", ex.message) - assertInstanceOf(BadRequestException::class.java, ex) - - // notification type is not "transfers" - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to "{}") - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Won't handle notification of type \"null\".", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - - // missing Message.transfer - var messageJson = """{ "notificationType": "transfers" }""" - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Missing \"transfer\" value in notification of type \"transfers\".", ex.message) - assertInstanceOf(BadRequestException::class.java, ex) - - // Not a complete transfer - messageJson = """{ "notificationType": "transfers", "transfer": {} }""" - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Not a complete transfer.", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - - // Incomplete transfer - messageJson = """{ "notificationType": "transfers", "transfer": { "status": "pending" } }""" - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Not a complete transfer.", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - - // Neither source nor destination are Stellar accounts - messageJson = - """{ - "notificationType": "transfers", - "transfer": { - "status": "complete", - "source": { - "type": "wallet", - "id": "1" - }, - "destination": { - "type": "wallet", - "id": "2" - } - } - }""" - .trimIndent() - .trimMargin() - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("Neither source nor destination are Stellar accounts.", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - - // Not tracking the wallets - messageJson = - """{ - "notificationType": "transfers", - "transfer": { - "status": "complete", - "source": { - "type": "blockchain", - "chain": "XLM" - }, - "destination": { - "type": "wallet", - "id": "1000223064" - }, - "amount": { - "amount": "1.50", - "currency": "ETH" - } - } - }""" - .trimIndent() - .trimMargin() - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("None of the transfer wallets is being tracked.", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - - // Not trading USDC - every { circlePaymentObserverConfig.trackedWallet } returns "all" - circlePaymentObserverService = - CirclePaymentObserverService( - httpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - messageJson = - """{ - "notificationType": "transfers", - "transfer": { - "status": "complete", - "source": { - "type": "blockchain", - "chain": "XLM" - }, - "destination": { - "type": "wallet", - "id": "1000223064" - }, - "amount": { - "amount": "1.50", - "currency": "ETH" - } - } - }""" - .trimIndent() - .trimMargin() - subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - ex = - assertThrows { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - assertEquals("The only supported Circle currency is USDC.", ex.message) - assertInstanceOf(UnprocessableEntityException::class.java, ex) - } - - @Test - fun test_handleCircleNotification_handleTransferNotification_success() { - every { circlePaymentObserverConfig.trackedWallet } returns "all" - - // Mock horizon call - val mockedServer = mockk() - val mockedOpResponsePage = mockk>() - every { horizon.server } returns mockedServer - every { - mockedServer - .payments() - .forTransaction(any()) - .limit(any()) - .includeTransactions(any()) - .execute() - } returns mockedOpResponsePage - - circlePaymentObserverService = - CirclePaymentObserverService( - httpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - - // Mock horizon call - val circleTransfer = CircleTransfer() - circleTransfer.transactionHash = - "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020" - circleTransfer.amount = CircleBalance("USD", "1.234") - - val mockedTransaction = mockk() - every { mockedTransaction.sourceAccount } returns - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S" - every { mockedTransaction.envelopeXdr } returns "my_envelope_xdr" - every { mockedTransaction.memo } returns Memo.text("my_text_memo") - - val usdcAsset = - AssetTypeCreditAlphaNum4("USDC", "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - val mockedPaymentOpResponse = mockk() - every { mockedPaymentOpResponse.transaction.get() } returns mockedTransaction - every { mockedPaymentOpResponse.isTransactionSuccessful } returns true - every { mockedPaymentOpResponse.type } returns "payment" - every { mockedPaymentOpResponse.asset } returns usdcAsset - every { mockedPaymentOpResponse.sourceAccount } returns - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S" - every { mockedPaymentOpResponse.id } returns 755914248193 - every { mockedPaymentOpResponse.to } returns - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU" - every { mockedPaymentOpResponse.from } returns - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S" - every { mockedPaymentOpResponse.amount } returns "1.2340000" - every { mockedPaymentOpResponse.createdAt } returns "2022-03-16T10:02:39Z" - every { mockedPaymentOpResponse.transactionHash } returns - "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020" - every { mockedOpResponsePage.records } returns arrayListOf(mockedPaymentOpResponse) - - val slotObservedPayment = slot() - every { paymentListener.onReceived(capture(slotObservedPayment)) } just Runs - - val messageJson = - """{ - "notificationType": "transfers", - "transfer": { - "id": "7f131f58-a8a0-3dc2-be05-6a015c69de35", - "status": "complete", - "source": { - "type": "blockchain", - "chain": "XLM" - }, - "destination": { - "type": "wallet", - "id": "1000223064" - }, - "amount": { - "amount": "1.234", - "currency": "USD" - }, - "transactionHash": "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020" - } - }""" - .trimIndent() - .trimMargin() - val subConfirmationNotification = mapOf("Type" to "Notification", "Message" to messageJson) - assertDoesNotThrow { - circlePaymentObserverService.handleCircleNotification( - fromMapToCircleNotification(subConfirmationNotification) - ) - } - - verify(exactly = 1) { paymentListener.onReceived(any()) } - - val wantObservedPayment = - ObservedPayment.builder() - .id("755914248193") - .externalTransactionId("7f131f58-a8a0-3dc2-be05-6a015c69de35") - .type(ObservedPayment.Type.CIRCLE_TRANSFER) - .from("GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S") - .to("GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU") - .amount("1.2340000") - .assetType("credit_alphanum4") - .assetCode("USDC") - .assetIssuer("GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - .assetName("USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - .sourceAccount("GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S") - .createdAt("2022-03-16T10:02:39Z") - .transactionHash("b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020") - .transactionMemo("my_text_memo") - .transactionMemoType("text") - .transactionEnvelope("my_envelope_xdr") - .build() - assertEquals(wantObservedPayment, slotObservedPayment.captured) - } - - @Test - fun test_fetchCircleTransferOnStellar() { - // Mock horizon call - val mockedServer = mockk() - val mockedOpResponsePage = mockk>() - every { horizon.server } returns mockedServer - every { - mockedServer - .payments() - .forTransaction(any()) - .limit(any()) - .includeTransactions(any()) - .execute() - } returns mockedOpResponsePage - circlePaymentObserverService = - CirclePaymentObserverService( - httpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - - // if the response is empty, returns null - val circleTransfer = CircleTransfer() - circleTransfer.id = "7f131f58-a8a0-3dc2-be05-6a015c69de35" - circleTransfer.transactionHash = - "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020" - every { mockedOpResponsePage.records } returns ArrayList() - var observedPayment = circlePaymentObserverService.fetchCircleTransferOnStellar(circleTransfer) - verify(exactly = 1) { mockedOpResponsePage.records } - assertNull(observedPayment) - - // if the response operation is not successful, returns null - val mockedOpResponse = mockk() - every { mockedOpResponse.isTransactionSuccessful } returns false - every { mockedOpResponsePage.records } returns arrayListOf(mockedOpResponse) - observedPayment = circlePaymentObserverService.fetchCircleTransferOnStellar(circleTransfer) - verify(exactly = 2) { mockedOpResponsePage.records } - verify(exactly = 1) { mockedOpResponse.isTransactionSuccessful } - assertNull(observedPayment) - - // if the response does not contain any payment, returns null - every { mockedOpResponse.isTransactionSuccessful } returns true - every { mockedOpResponse.type } returns "create_account" - every { mockedOpResponsePage.records } returns arrayListOf(mockedOpResponse) - observedPayment = circlePaymentObserverService.fetchCircleTransferOnStellar(circleTransfer) - verify(exactly = 3) { mockedOpResponsePage.records } - verify(exactly = 2) { mockedOpResponse.isTransactionSuccessful } - verify(exactly = 1) { mockedOpResponse.type } - assertNull(observedPayment) - - // if asset type is not AssetTypeCreditAlphaNum, returns null - circleTransfer.amount = CircleBalance("USD", "1.234") - - val mockedTransaction = mockk() - every { mockedTransaction.sourceAccount } returns - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S" - every { mockedTransaction.envelopeXdr } returns "my_envelope_xdr" - every { mockedTransaction.memo } returns Memo.text("my_text_memo") - - val usdcAsset = - AssetTypeCreditAlphaNum4("USDC", "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - val mockedPaymentOpResponse = mockk() - every { mockedPaymentOpResponse.transaction.get() } returns mockedTransaction - every { mockedPaymentOpResponse.isTransactionSuccessful } returns true - every { mockedPaymentOpResponse.type } returns "payment" - every { mockedPaymentOpResponse.asset } returns usdcAsset - every { mockedPaymentOpResponse.sourceAccount } returns - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S" - every { mockedPaymentOpResponse.id } returns 755914248193 - every { mockedPaymentOpResponse.to } returns - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU" - every { mockedPaymentOpResponse.from } returns - "GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S" - every { mockedPaymentOpResponse.amount } returns "1.2340000" - every { mockedPaymentOpResponse.createdAt } returns "2022-03-16T10:02:39Z" - every { mockedPaymentOpResponse.transactionHash } returns - "b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020" - every { mockedOpResponsePage.records } returns arrayListOf(mockedPaymentOpResponse) - observedPayment = circlePaymentObserverService.fetchCircleTransferOnStellar(circleTransfer) - - val wantObservedPayment = - ObservedPayment.builder() - .id("755914248193") - .externalTransactionId("7f131f58-a8a0-3dc2-be05-6a015c69de35") - .type(ObservedPayment.Type.CIRCLE_TRANSFER) - .from("GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S") - .to("GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU") - .amount("1.2340000") - .assetType("credit_alphanum4") - .assetCode("USDC") - .assetIssuer("GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - .assetName("USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5") - .sourceAccount("GAC2OWWDD75GCP4II35UCLYA7JB6LDDZUBZQLYANAVIHIRJAAQBSCL2S") - .createdAt("2022-03-16T10:02:39Z") - .transactionHash("b9d0b2292c4e09e8eb22d036171491e87b8d2086bf8b265874c8d182cb9c9020") - .transactionMemo("my_text_memo") - .transactionMemoType("text") - .transactionEnvelope("my_envelope_xdr") - .build() - assertEquals(wantObservedPayment, observedPayment) - } - - @Test - fun test_isWalletTracked() { - // Wire transfers are not tracked - var party = CircleTransactionParty.wire("bank_id", "mail@test.com") - var isWalletTracked = circlePaymentObserverService.isWalletTracked(party) - assertFalse(isWalletTracked) - - // Stellar transfers are not tracked - party = CircleTransactionParty.stellar("G...", "address_tag_here") - isWalletTracked = circlePaymentObserverService.isWalletTracked(party) - assertFalse(isWalletTracked) - - // when tracked == "all", it's always approved - every { circlePaymentObserverConfig.trackedWallet } returns "all" - circlePaymentObserverService = - CirclePaymentObserverService( - httpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - party = CircleTransactionParty.wallet("11111") - isWalletTracked = circlePaymentObserverService.isWalletTracked(party) - assertTrue(isWalletTracked) - - party = CircleTransactionParty.wallet("22222") - isWalletTracked = circlePaymentObserverService.isWalletTracked(party) - assertTrue(isWalletTracked) - - // when tracked == "11111", only the wallet with that ID is approved - every { circlePaymentObserverConfig.trackedWallet } returns "11111" - circlePaymentObserverService = - CirclePaymentObserverService( - httpClient, - circlePaymentObserverConfig, - horizon, - listOf(paymentListener) - ) - party = CircleTransactionParty.wallet("11111") - isWalletTracked = circlePaymentObserverService.isWalletTracked(party) - assertTrue(isWalletTracked) - - party = CircleTransactionParty.wallet("22222") - isWalletTracked = circlePaymentObserverService.isWalletTracked(party) - assertFalse(isWalletTracked) - } - - fun fromMapToCircleNotification(map: Map): CircleNotification? { - return gson.fromJson(gson.toJson(map), CircleNotification::class.java) - } -} 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 e6ea469c69..45278c14da 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,16 +10,12 @@ import org.junit.jupiter.api.BeforeEach 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.api.shared.StellarPayment -import org.stellar.anchor.api.shared.StellarTransaction +import org.stellar.anchor.api.shared.* import org.stellar.anchor.event.EventService -import org.stellar.anchor.event.models.* +import org.stellar.anchor.event.models.TransactionEvent 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.platform.observer.ObservedPayment import org.stellar.anchor.sep31.Sep31Transaction import org.stellar.anchor.util.GsonUtils 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 33c5203489..831f513804 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 @@ -15,8 +15,8 @@ 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.platform.observer.stellar.PaymentObservingAccountStore +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager import org.stellar.anchor.sep31.Sep31Transaction class Sep31DepositInfoGeneratorApiTest { 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 b8c7bdedba..c02a7597c0 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -1,13 +1,12 @@ package org.stellar.anchor.sep31 import com.google.gson.Gson -import io.mockk.* +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 okhttp3.mockwebserver.Dispatcher -import okhttp3.mockwebserver.MockResponse -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 @@ -18,18 +17,12 @@ 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 -import org.stellar.anchor.config.CircleConfig import org.stellar.anchor.config.Sep31Config 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 -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 import org.stellar.anchor.util.GsonUtils -import org.stellar.sdk.Server class Sep31DepositInfoGeneratorTest { companion object { @@ -129,83 +122,6 @@ class Sep31DepositInfoGeneratorTest { Assertions.assertEquals("YTIzOTJhZGQtODdjOS00MmYwLWE1YzEtNWYxNzI4MDM=", txn.stellarMemo) } - @Test - fun test_updateDepositInfo_circle() { - val server = MockWebServer() - server.start() - val dispatcher: Dispatcher = - object : Dispatcher() { - @Throws(InterruptedException::class) - override fun dispatch(request: RecordedRequest): MockResponse { - when (request.path) { - "/v1/configuration" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data":{ - "payments":{ - "masterWalletId":"1000066041" - } - } - }""".trimMargin() - ) - "/v1/wallets/1000066041/addresses" -> - return MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ - "data": { - "address":"GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - "addressTag":"2454278437550473431", - "currency":"USD", - "chain":"XLM" - } - }""".trimMargin() - ) - } - return MockResponse().setResponseCode(404) - } - } - server.dispatcher = dispatcher - - val circlePaymentConfig = mockk(relaxed = true) - val circleConfig = mockk(relaxed = true) - every { circleConfig.circleUrl } returns server.url("").toString() - every { circleConfig.apiKey } returns "" - val horizon = mockk(relaxed = true) - every { horizon.server } returns Server(server.url("").toString()) - every { horizon.stellarNetworkPassphrase } returns "Test SDF Network ; September 2015" - - val circlePaymentService = CirclePaymentService(circlePaymentConfig, circleConfig, horizon) - val depositInfoGenerator = Sep31DepositInfoGeneratorCircle(circlePaymentService) - sep31Service = - Sep31Service( - appConfig, - sep31Config, - txnStore, - depositInfoGenerator, // set deposit info generator - quoteStore, - assetService, - feeIntegration, - customerIntegration, - eventPublishService - ) - - Assertions.assertNull(txn.stellarMemoType) - Assertions.assertNull(txn.stellarMemo) - - Sep31Service.Context.get().transaction = txn - assertDoesNotThrow { sep31Service.updateDepositInfo() } - - Assertions.assertEquals( - "GAYF33NNNMI2Z6VNRFXQ64D4E4SF77PM46NW3ZUZEEU5X7FCHAZCMHKU", - txn.stellarAccountId - ) - Assertions.assertEquals("text", txn.stellarMemoType) - Assertions.assertEquals("2454278437550473431", txn.stellarMemo) - } - @ParameterizedTest @CsvSource( value = diff --git a/platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt b/platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt deleted file mode 100644 index f8a681d155..0000000000 --- a/platform/src/test/kotlin/paymentservice/AccountCapabilitiesTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -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) - } -} From 0bd41fca8c199e3fcaa5a3301286e17f3e4a4b85 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 3 Nov 2022 19:08:10 -0400 Subject: [PATCH 0045/1439] update dockerfile version to 2.4 (#647) * update dockerfile version to 2.4 * trigger build --- Makefile | 3 ++- docker-compose.yaml | 2 +- .../G - End to End Testing with Different Configs.md | 2 +- .../docker-compose-config.override.yaml | 2 +- .../docker-compose-config.override.yaml | 2 +- .../docker-compose-config.override.yaml | 2 +- .../docker-compose-configs/docker-compose.base.yaml | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index c79a406905..e03335f5b5 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,8 @@ run-e2e-test-all: make run-e2e-test-unique-address define run_tests - $(SUDO) docker-compose -f integration-tests/docker-compose-configs/docker-compose.base.yaml \ + $(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 rm -f $(SUDO) docker-compose --env-file integration-tests/docker-compose-configs/.env \ diff --git a/docker-compose.yaml b/docker-compose.yaml index 47b6665076..0a93677126 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,4 @@ -version: '2' +version: '2.4' services: anchor-platform: build: 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 4656333d5a..575f4a617c 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 @@ -42,7 +42,7 @@ directory. configuration (refer to: [End to End Tests](/end-to-end-tests/README.md)). ```text - version: '2' + version: '2.4' services: anchor-platform-server: volumes: 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 2fd8d7dbdb..0f61277cac 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 @@ -1,5 +1,5 @@ # Configuration Description - enable to omnibusAllowList feature to check if an account is whitelisted for SEP-10 -version: '2' +version: '2.4' services: anchor-platform-server: volumes: 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 index e48daed593..d3101b9b2f 100644 --- 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 @@ -1,5 +1,5 @@ # Configuration Description - default Anchor Platform configuration, also used as a template for new configurations -version: '2' +version: '2.4' services: anchor-platform-server: ports: 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 c3b172ea7a..92eebd5392 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 @@ -2,7 +2,7 @@ # 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' +version: '2.4' services: anchor-platform-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 666fbd322c..b89fa89838 100644 --- a/integration-tests/docker-compose-configs/docker-compose.base.yaml +++ b/integration-tests/docker-compose-configs/docker-compose.base.yaml @@ -1,4 +1,4 @@ -version: '2' +version: '2.4' services: anchor-platform-server: image: anchor-platform From 986fea07bb3db9e25a9f7dfa6a4b9160504da033 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 4 Nov 2022 09:06:00 -0700 Subject: [PATCH 0046/1439] Added type and value checkds of PropertyAssetsConfig (#646) --- .../platform/config/PropertyAssetsConfig.java | 49 ++++++++++++++++++- .../service/PropertyAssetsService.java | 9 +++- 2 files changed, 56 insertions(+), 2 deletions(-) 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 index cda58463d2..f781bca3ff 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java @@ -1,10 +1,16 @@ package org.stellar.anchor.platform.config; +import com.google.gson.JsonSyntaxException; import lombok.Data; import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; +import org.stellar.anchor.asset.Assets; import org.stellar.anchor.config.AssetsConfig; +import org.stellar.anchor.util.GsonUtils; +import org.stellar.anchor.util.StringHelper; + +import static org.stellar.anchor.util.Log.error; @Data public class PropertyAssetsConfig implements AssetsConfig, Validator { @@ -17,5 +23,46 @@ public boolean supports(@NotNull Class clazz) { } @Override - public void validate(@NotNull Object target, @NotNull Errors errors) {} + public void validate(@NotNull Object target, @NotNull Errors errors) { + PropertyAssetsConfig config = (PropertyAssetsConfig) target; + checkType(config, errors); + checkValue(config, errors); + } + + void checkValue(PropertyAssetsConfig config, Errors errors) { + if (StringHelper.isEmpty(config.getValue())) { + errors.reject("invalid-no-value-defined", "assets.value is empty. Please define."); + } else { + switch (config.getType()) { + case JSON: + try { + GsonUtils.getInstance().fromJson(config.getValue(), Assets.class); + } catch (JsonSyntaxException jsex) { + error("JSON parsing exception:", jsex); + errors.reject( + "invalid-asset-json-format", + "assets.value does not contain a valid JSON string for assets"); + } + break; + case YAML: + default: + break; + } + } + } + + void checkType(PropertyAssetsConfig config, Errors errors) { + if (config.getType() == null) { + errors.reject("invalid-no-type-defined", "assets.type is empty. Please define."); + } + switch (config.getType()) { + case JSON: + break; + case YAML: + default: + errors.reject( + "invalid-type-defined", + String.format("assets.type:%s is invalid. Only JSON is supported.", config.getType())); + } + } } 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 index f137144a34..de6fa88cbe 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java @@ -11,6 +11,9 @@ import org.stellar.anchor.util.GsonUtils; import org.stellar.anchor.util.Log; +import static org.stellar.anchor.util.Log.error; +import static org.stellar.anchor.util.Log.infoF; + public class PropertyAssetsService implements AssetService { static final Gson gson = GsonUtils.getInstance(); Assets assets; @@ -20,10 +23,14 @@ public PropertyAssetsService(AssetsConfig assetsConfig) throws InvalidConfigExce case JSON: String assetsJson = assetsConfig.getValue(); assets = gson.fromJson(assetsJson, Assets.class); + if (assets == null || assets.getAssets() == null || assets.getAssets().size() == 0) { + error("Invalid asset defined. assets JSON=", assetsJson); + throw new InvalidConfigException(String.format("Invalid assets defined in configuration. Please check the logs for details.")); + } break; case YAML: default: - Log.infoF("assets type {} is not supported", assetsConfig.getType()); + infoF("assets type {} is not supported", assetsConfig.getType()); throw new InvalidConfigException( String.format("assets type %s is not supported", assetsConfig.getType())); } From ebd133457f28067e19ffa1fc12bde0944950d65e Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 4 Nov 2022 16:20:45 -0400 Subject: [PATCH 0047/1439] Update docs 2.0 (#648) * update documentation --- docs/00 - Stellar Anchor Platform.md | 4 - ...- Running & Configuring the Application.md | 166 +++-- .../B - Circle Payment Observer.md | 49 -- .../A - Circle Payment Service.md | 227 ------ .../anchor-docker-compose-config.yaml | 661 +++++++++--------- .../config/anchor-config-default-values.yaml | 16 +- .../config/anchor-config-schema-v1.yaml | 1 + .../main/resources/example.anchor-config.yaml | 2 - platform/src/main/resources/example.env | 5 +- 9 files changed, 475 insertions(+), 656 deletions(-) delete mode 100644 docs/01 - Running & Configuring the Application/B - Circle Payment Observer.md delete mode 100644 docs/04 - Subprojects Usage/A - Circle Payment Service.md diff --git a/docs/00 - Stellar Anchor Platform.md b/docs/00 - Stellar Anchor Platform.md index a5d73641f1..272ab710a0 100644 --- a/docs/00 - Stellar Anchor Platform.md +++ b/docs/00 - Stellar Anchor Platform.md @@ -26,7 +26,6 @@ The full documentation can be found under the [`docs` directory](/docs), under t - [00 - Stellar Anchor Platform](/docs/00%20-%20Stellar%20Anchor%20Platform.md) - [01 - Running & Configuring the Application](/docs/01%20-%20Running%20%26%20Configuring%20the%20Application) - [A - Running & Configuring the Application](/docs/01%20-%20Running%20%26%20Configuring%20the%20Application/A%20-%20Running%20%26%20Configuring%20the%20Application.md) - - [B - Circle Payment Observer](/docs/01%20-%20Running%20%26%20Configuring%20the%20Application/B%20-%20Circle%20Payment%20Observer.md) - [02 - Contributing](/docs/02%20-%20Contributing) - [A - CONTRIBUTING.md](/docs/02%20-%20Contributing/A%20-%20CONTRIBUTING.md) - [B - Developer Tools](/docs/02%20-%20Contributing/B%20-%20Developer%20Tools.md) @@ -40,8 +39,6 @@ The full documentation can be found under the [`docs` directory](/docs), under t - [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) -- [04 - Subprojects Usage](/docs/04%20-%20Subprojects%20Usage) `// In progress...` - - [A - Circle Payment Service](/docs/04%20-%20Subprojects%20Usage/A%20-%20Circle%20Payment%20Service.md) ## Definitions @@ -225,7 +222,6 @@ The Stellar Anchor SDK is a collection of projects that make easy to build finan * [api-schema](/api-schema): the API interfaces, request, response classes. * [core](/core): the base package for implementing standardized anchor applications. -* [payment](/payment): implementation of payment service with [Circle API](https://developers.circle.com/reference). * [platform](/platform): the anchor platform [Spring Boot Application with WebMVC](https://spring.io/guides/gs/serving-web-content/). * [anchor-reference-server](/anchor-reference-server): the reference implementation of the anchor server. * [service-runner](/service-runner): the runner class for the platform, reference server and payment observer. 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 5c6d364fe6..85c346b4a8 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 @@ -6,8 +6,9 @@ - [Config Files](#config-files) - [Path to Yaml](#path-to-yaml) - [Yaml Search](#yaml-search) - - [Authorization Between Platform<>Anchor](#authorization-between-platformanchor) - [Environment variables](#environment-variables) + - [Secrets](#secrets) + - [Authorization Between Platform and Anchor](#authorization-between-platformanchor) - [Supported Assets](#supported-assets) - [Event Messaging](#event-messaging) - [JVM-Argument based run-configuration](#jvm-argument-based-run-configuration) @@ -18,16 +19,21 @@ ## Running the Application from Source Code -This section covers how to run the application from source code using the provided Kafka docker image configuration, the Anchor Reference server and the secrets provided in the demo configuration files. +This section covers how to run the application from source code using the provided Kafka docker image configuration, +the Anchor Reference server and the secrets provided in the demo configuration files. 1. Clone this repository: `git clone ssh://git@github.com:stellar/java-stellar-anchor-sdk.git`. 2. Start a Kafka Queue service: `cd docs/resources/docker-examples/kafka && docker compose up` 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. + - 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 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`, + - 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`] + - 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 needs the `STELLAR_ANCHOR_CONFIG` previously mentioned. @@ -35,14 +41,20 @@ This section covers how to run the application from source code using the provid ### Config Files -As mentioned previously, both the Anchor Platform and Anchor Reference server are configured using yaml and they have default configuration files: +As mentioned previously, both the Anchor Platform and Anchor Reference server are configured using yaml and they have +default configuration files: -- **Anchor Platform** default config file is located at [`anchor-config-defaults.yaml`]. +- **Anchor Platform** default config file is located at [`anchor-config-default-values.yaml`]. - **Anchor Reference Server** default config file is located at [`anchor-reference-server.yaml`]. -In order to use a custom configuration file, you need to set the `STELLAR_ANCHOR_CONFIG` (for Anchor Platform) or the `REFERENCE_SERVER_CONFIG_ENV` (for Anchor Reference Server) environment variable(s) to the path of the configuration file, following the [Path to Yaml](#path-to-yaml) format. +The default configuration files are very self-explanatory and contain tons of comments to explain what each +configuration option does. For the **Anchor Platform**, to modify the default configuration you can create a new +'override' configuration file that will be merged on top of the values in the [`anchor-config-defaults-values.yaml`] file. -The default config files are very self-explanatory and contain tons of comments to explain what each configuration option does. If you want to customize the configuration, just copy those files and start modifying them. +In order to use the 'override' configuration file, you need to set the `STELLAR_ANCHOR_CONFIG` +environment variable to the path of the 'override' file ([`example.anchor-config.yaml`] is an example 'override' file) + +The `REFERENCE_SERVER_CONFIG_ENV` (for Anchor Reference Server) #### Path to Yaml @@ -62,7 +74,8 @@ Examples: ##### Yaml Search -The Platform configuration loader tries to fetch the configuration from up to three different sources before failing, in the following order: +The Platform configuration loader tries to fetch the configuration from up to three different sources before failing, +in the following order: 1. The JVM Option `-Dstellar.anchor.config`, for instance: @@ -70,9 +83,12 @@ The Platform configuration loader tries to fetch the configuration from up to th ./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. +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. -3. The system Environment Variable `STELLAR_ANCHOR_CONFIG`. If neither the JVM option nor the file `.anchor/anchor-config.yaml` is found, the server searches for the `STELLAR_ANCHOR_CONFIG`, whose default value is `"classpath:/anchor-config-defaults.yaml"`: +3. The system Environment Variable `STELLAR_ANCHOR_CONFIG`. If neither the JVM option nor the file + `.anchor/anchor-config.yaml` is found, the server searches for the `STELLAR_ANCHOR_CONFIG`, whose default value is + `"classpath:/anchor-config-defaults.yaml"`: ```shell STELLAR_ANCHOR_CONFIG=classpath:/anchor-config-defaults.yaml @@ -81,27 +97,76 @@ The Platform configuration loader tries to fetch the configuration from up to th If all of the above fail, the server fails with an error. -### Authorization Between Platform<>Anchor +### Environment variables +All configuration values mentioned in [`anchor-config-default-values.yaml`] can be set via environment variables. +To set a value, convert the yaml path of the key to POSIX form and set the value. + +Example: + +Setting the Event Publisher Type +```text +events: + publisher: + type: kafka +``` +maps to the environment variable: +```text +EVENTS_PUBLISHER_TYPE=kafka +``` + +### Secrets +Secrets are passed to the Anchor Platform (and Anchor Reference Server) via environment variables, which can be set +either through command line or using a `.env` file. A list of secret environment variables and their descriptions +can be found in the [`example.env`] file. The following environment variables are required for the Anchor Platform: + +Note: secrets will always start with 'SECRET' + +```text +# REQUIRED - The secret key of JWT encryption +SECRET_SEP10_JWT_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= + +# REQUIRED - JWT secrets used to communicate between Anchor and Platform. +SECRET_CALLBACK_API_AUTH_SECRET= +SECRET_PLATFORM_API_AUTH_SECRET= +``` + + + +### Authorization Between Platform and Anchor -It's possible to enable/disable authorization headers for requests between Platform and Anchor by editing the `integration-auth` configuration in the Platform config map. You can use different secrets depending on the direction of the requests, i.e. one for `Platform->Anchor` and another for `Anchor->Platform`, and you can choose between the following auth options: -- `JWT_TOKEN`: where a secret is used to create a jwt token in the sender side, and this same secret is used to decode the token in the receiver side. This token is added to the `Authorization` header. +It's possible to enable/disable authorization headers for requests between Platform and Anchor by editing the +`auth` configuration in the Platform config map. You can use different secrets depending on the direction +of the requests, i.e. one for `Platform->Anchor` and another for `Anchor->Platform`, and you can choose between the +following `auth.type` options: +- `JWT_TOKEN`: where a secret is used to create a jwt token in the sender side, and this same secret is used to decode +the token in the receiver side. This token is added to the `Authorization` header. - `API_KEY`: where an API key is added directly to the `X-Api-Key` header. - `NONE`: where no authorization is used. -### Environment variables - -Secrets are passed to the Anchor Platform (and Anchor Reference Server) via environment variables, which can be set either through command line or using a `.env` file. A list of supported environment variables and their descriptions can be found at [`example.env`]. +The following `auth` secrets are required: +```text +# REQUIRED - JWT secrets used to communicate between Anchor and Platform. +SECRET_CALLBACK_API_AUTH_SECRET= +SECRET_PLATFORM_API_AUTH_SECRET= +``` ### Supported Assets -The Anchor Platform reads the list of supported assets from a json file whose address is configured in the config file under `app-config.app-assets` and defaults to [`assets-test.json`] ([ref](https://github.com/stellar/java-stellar-anchor-sdk/blob/1f84429f0c5d35cee75445686242643fbd8cffa5/platform/src/main/resources/anchor-config-defaults.yaml#L74)). +The Anchor Platform reads the list of supported assets (to be set in the configuration `override` file) in json form, +an example can be found in ['example.anchor-config.yaml'] under `assets`. ### Event Messaging A message queue is required for the Anchor Platform to publish messages to and for the Anchor to consume messages from. -The default queueType used (in the anchor-config.yaml file) is "kafka", and SQS is also supported. +The default event messaging service (defined in ['anchor-config-defualt-values.yaml']) is "kafka", SQS is also supported. -The default Kafka configuration should work out of the box if you have a running Kafka available at the correct port. The easiest way for that is by using the docker-compose.yaml file located at `docs/resources/docker-examples/kafka/docker-compose.yaml`: +The default Kafka configuration should work out of the box if you have a running Kafka available at the correct port. +The easiest way for that is by using the docker-compose.yaml file located at `docs/resources/docker-examples/kafka/docker-compose.yaml`: ```shell cd docs/resources/docker-examples/kafka @@ -110,7 +175,8 @@ docker compose up ### JVM-Argument based run-configuration -Since Java processes take `-D` arguments as JVM system properties, the path/locator of the `yaml` file can be passed to the process through JVM system properties: +Since Java processes take `-D` arguments as JVM system properties, the path/locator of the `yaml` file can be passed to +the process through JVM system properties: ```shell java -Dstellar.anchor.config=file:/path/to/file.yaml -jar anchor-platform.jar --anchor-reference-server @@ -130,64 +196,72 @@ Note: secrets (credentials, tokens, etc...) are passed to the application via en each required environment variables ([Environment Variables](/platform/src/main/resources/example.env)) ```shell -docker run -v {/local/path/to/config/file/}:/config -p 8081:8081 stellar-anchor-platform:latest --anchor-reference-server \ --e JWT_SECRET='secret' \ --e SEP10_SIGNING_SEED='SAX3...C3AW5X' \ --e CIRCLE_API_KEY='QVBJX0...NjMyZTQ5NWJhNDdlZg==' \ --e PAYMENT_GATEWAY_STELLAR_SECRET_KEY='secret' \ --e POSTGRES_USERNAME='postgres' \ --e POSTGRES_PASSWORD='password' +docker run -v :/config -p 8080:8080 \ +-e SECRET_SEP10_JWT_SECRET='secret' \ +-e SECRET_SEP10_SIGNING_SEED='SAX3...C3AW5X' \ +-e SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret +-e SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret +-e SECRET_DATA_USERNAME='postgres' \ +-e SECRET_DATA_PASSWORD='password' \ +stellar-anchor-platform:latest --sep-server ``` -or pass in an .env file +or pass in a .env file ```shell -docker run -v {/local/path/to/config/file/}:/config -p 8081:8081 stellar-anchor-platform:latest --anchor-reference-server \ ---env-file ./my-env-file +docker run -v {/local/path/to/config/file/}:/config -p 8080:8080 \ +--env-file ./my-env-file stellar-anchor-platform:latest --sep-server ``` -> Note 1: this image can run --sep-server (port: 8080), --anchor-reference-server (port: 8081) and --stellar-observer (no port needed). +> 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. +> Note 2: to check all the available environment variables, please refer to the [`anchor-config-default-values.yaml`] file. ## Running the Application with Docker Compose -You can use docker compose to run the whole infrastructure - Anchor Platform, Reference Server, Kafka, and a Postgres Database. All you need to do is making use of the [docker-compose.yaml](/docker-compose.yaml) available at the root of the project: +You can use docker compose to run the whole stack - Anchor Platform, Reference Server, Kafka, and a Postgres +Database. All you need to do is making use of the [docker-compose.yaml](/docker-compose.yaml) available at the root of +the project: ```shell docker compose up ``` -It uses the default config files [`anchor-config-defaults.yaml`], [`anchor-reference-server.yaml`] and the default environment variables from [`example.env`]. +It uses the default config files [`anchor-config-default-values.yaml`], [`anchor-reference-server.yaml`] and the default +environment variables from [`example.env`]. -You can test against this setup by running the end-to-end tests ([end_to_end_test.py](/end-to-end-tests/end_to_end_tests.py)) using localhost:8080 as the domain. -## Incoming Payments Observer +You can test against this setup by running the end-to-end tests +([end_to_end_test.py](/end-to-end-tests/end_to_end_tests.py)) using localhost:8080 as the domain. -The default configuration of the project uses a Stellar network observer to identify incoming Stellar payments. In case the Anchor relies on Circle, it should configure the project to use the Circle Payment Observer. For more information on how to do that, please refer to the [01.B - Circle Payment Observer](/docs/01%20-%20Running%20%26%20Configuring%20the%20Application/B%20-%20Circle%20Payment%20Observer.md) section. +## Incoming Payments Observer +The default configuration of the project uses a Stellar network observer to identify incoming Stellar payments. ## Metrics The Anchor Platform exposes a Prometheus metrics endpoint at `:8082/actuator/prometheus`. All standard Spring Boot Actuator metrics are enabled by default. There are certain metrics that periodically poll the database (eg: for -the count of transactions in each state); these metrics are disabled by default. -They can be enabled with the following configs: +the count of transactions in each state); these metrics are disabled by default but can be enabled with the following +configs: ```yaml - metrics-service: - optionalMetricsEnabled: true # optional metrics that periodically query the database - runInterval: 30 # interval to query the database to generate the optional metrics +metrics: + enabled: false + port: 8082 # port to expose metrics on + extras_enabled: true # optional metrics that periodically query the database + run_interval: 30 # interval (seconds) to query the database for the extra metrics ``` A Grafana dashboard for the Anchor Platform can be found at `docs/resources/grafana-dashboard/anchor-platform-grafana-dashboard.json` and imported into your Grafana instance to visualized the Prometheus metrics. -[`anchor-config-defaults.yaml`]: ../../platform/src/main/resources/anchor-config-defaults.yaml +[`anchor-config-default-values.yaml`]: ../../platform/src/main/resources/config/anchor-config-default-values.yaml [`anchor-reference-server.yaml`]: ../../anchor-reference-server/src/main/resources/anchor-reference-server.yaml [`example.env`]: ../../platform/src/main/resources/example.env +['example.anchor-config.yaml']: ../../platform/src/main/resources/example.anchor-config.yaml [`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/docs/01 - Running & Configuring the Application/B - Circle Payment Observer.md b/docs/01 - Running & Configuring the Application/B - Circle Payment Observer.md deleted file mode 100644 index 11812e4334..0000000000 --- a/docs/01 - Running & Configuring the Application/B - Circle Payment Observer.md +++ /dev/null @@ -1,49 +0,0 @@ -# Circle Payment Observer - -- [Circle Payment Observer](#circle-payment-observer) - - [Configuration](#configuration) - - [Circle API Key](#circle-api-key) - - [Subscribing to Receive Webhook Requests](#subscribing-to-receive-webhook-requests) - - [Generating SEP-31 Transactions Using Circle's Memo](#generating-sep-31-transactions-using-circles-memo) - -The Circle Payment Observer is meant to be used if Circle is used to receive payments. It uses -[Circle API notifications](https://developers.circle.com/docs/notifications-data-models), an async webhook notifier -implemented by Circle that can notify our server whenever a change is detected by them. It allows observing a variety of -Circle events, although we will only be observing [`transfer` events](https://developers.circle.com/docs/notifications-data-models#transfer-flow), -since they are the only ones that can contain information about incoming and outgoing stellar transfers. - -## Configuration - -In order to use Circle in the SEP-31 flow, a few basic steps are needed: - -1. Make sure you register to Circle and generate an API key. -2. Register the Platform `{BASE_URL}/circle-observer` endpoint to the Circle subscription service. -3. Generate the SEP-31 transactions using a memo provided by Circle. - -### Circle API Key - -Start by getting a sandbox account at and generating an API key. You'll need that -to authenticate your Circle requests. - -At some point, you'll want to get a production key. In order to do so, go to and create a new -Circle account. The account creation screening process takes around 3 weeks. - -### Subscribing to Receive Webhook Requests - -Here are the steps to subscribe to Circle events: - -1. Make sure your server is up and running and that it can be reached from an external IP. -2. Execute a `POST /v1/notifications/subscriptions` request using the `{HOST}/circle-observer` provided by the Platform. - - You can use the [Circle API reference](https://developers.circle.com/reference/listsubscriptions) website to do so. -3. If the request went through successful, Circle will send a `"SubscriptionConfirmation"` notification to your instance -of the Platform. -4. Upon receiving the `"SubscriptionConfirmation"` notification, the Platform will automatically reach back to Circle to -confirm the subscription. After receiving that confirmation, Circle will start notifying the Platform through the -`{HOST}/circle-observer` endpoint. - -You can check Circle's official documentation [here](https://developers.circle.com/docs/notifications-quickstart#2-subscribe-to-payments-status-notifications). - -### Generating SEP-31 Transactions Using Circle's Memo - -In order to connect incoming Circle payments with a SEP-31 transfer, SEP-31 actually needs to use Circle to generate the -SEP-31 memo. To do so, make sure you configure `sep31` in the yaml file with `depositInfoGeneratorType: circle`. diff --git a/docs/04 - Subprojects Usage/A - Circle Payment Service.md b/docs/04 - Subprojects Usage/A - Circle Payment Service.md deleted file mode 100644 index 79f3788d58..0000000000 --- a/docs/04 - Subprojects Usage/A - Circle Payment Service.md +++ /dev/null @@ -1,227 +0,0 @@ -# Circle Payment Service - -The Circle payment service implements the [PaymentService] interface using the [Circle network]. - -## Setup - -In order to properly utilize the Circle payment service you need to create a Circle account and configure it properly: - -1. Go to and create a new Circle account. The account creation screening process takes around -3 weeks. - - Alternatively, you can skip the long registration process and get your hands on the code quickly by creating a - simpler sandbox account at . -2. Make sure you get an API key. -3. If you need to receive bank wire transfers, please refer to the [Wire Payments Quickstart] for instructions on how to - register your bank account that will be used for incoming payments. - - Please be aware that all received funds go directly to Circle merchant account (what we call the distribution - account). Circle does not assign a bank account to a specific internal account so all bank payments go to the - distribution account balance. -4. If you need to send bank wire transfers, please refer to the [Wire Payouts Quickstart] for instructions on how to - register the third-party bank accounts that will be receiving this outgoing payments, a.k.a. payouts. Any internal - Circle account can make a payout given they have enough funds. - -### Creating a Bank Wire Account - -In order to send or receive Wire transfers, Circle requires the Bank Wire accounts to be registered in their platform. -This part is not covered by the `PaymentService` interface, so you'll need to send an API request directly to Circle. -For more details, please refer to the [Wire Payments Quickstart] or [Wire Payouts Quickstart] sections of the Circle -documentation. - -#### Bank Account Verification - -Due to regulatory obligations, Circle systems automatically compare the bank account (number and routing) and sender -(first and last name) information provided at the time of bank account creation (within Circle systems) with the -information received on the wire transfer details (what the sender bank transmits). When there are mismatches, Circle -is obliged to fail the payment and return the funds. - -To avoid returns, make sure you build a user experience that makes that clear for end users, so that they provide you -with the correct bank account and account holder / beneficiary information. - -#### Unsupported Bank Accounts - -Circle currently cannot support wire payments originating from bank accounts that require For Further Credit (FFC) -instructions. In a For Further Credit (FFC) payment, the money is sent to the final beneficiary via an intermediary bank -which obfuscates the bank account sender information. Examples of these bank accounts are brokerage and investment -accounts, accounts that leverage third party payment processors, accounts with money transfer services, and challenger -banks without their own banking license. - -#### Supported Countries - -You can find an updated list of the supported countries [here](https://developers.circle.com/docs/supported-countries#wire-transfer-payments--payouts). - -Note that even if wire transfers are technically supported for a country on the Circle Payments API, your Circle account -might end up being configured to only accept payments from a subset of those countries. - -Also note that countries might have capital controls in place which might in practice limit your ability to process wire -transfers from / to some countries. - -#### Wire Transfers Fees - -At the time this document was written, the fees for wire transfers were: -- $2 per incoming wire transfer -- $25 per outgoing wire transfer | $50 per wire reversal - - Customers are billed $50 per wire reversal. Amounts are directly debited from their Circle Account. Possible reasons - for rejection include missing or misspelled reference numbers or attempting to payout an amount in excess of any - agreed upon limits. - -For a more complete and up-to-date information on Circle's fees, please refer to their [pricing webpage](https://www.circle.com/en/pricing). - -## Usage - -The CirclePaymentService does not implement all Circle capabilities, just a subset of the ones that are relevant for -most of the Stellar-related use cases, being limited to `CircleWallet<>CircleWallet`, `CircleWallet<>Stellar` and -`CircleWallet<>BankWire` integrations. Credit cards, ACH, SEPA, BTC, ETH and other networks supported by Circle are not -covered by this integration. - -All methods are async and return a `reactor.core.publisher.Mono`. For more information, please refer to [Project Reactor]. -In most of the examples below we won't be handling the throwable errors, and we will be using the methods synchronously, -but you can also use the library asynchronous features at your choice, here is a quick example to help you get started: - -```java -CirclePaymentService service = CirclePaymentService(config); - -// Sync usage: -String distributionAccountId = service.getDistributionAccountAddress().block(); -System.out.println(distributionAccountId); - -// Async usage: -service.getDistributionAccountAddress().then(distributionAccountId -> { - System.out.println(distributionAccountId); -}); -``` - -You can find more details on how to use the CirclePaymentService in the subsections below: - -### `ping()` - -Allows users to check if the network is up and running. Usage: - -```java -try { - service.ping().block(); -} catch (Exception ex) { - ex.printStackTrace(); - // TODO: handle service being offline -} -``` - -### `getDistributionAccountAddress()` - -Returns the address string of the distribution account, also called **merchant account** within the Circle context. Usage: - -```java -String distributionAccountId = service.getDistributionAccountAddress().block(); -System.out.println(distributionAccountId); -``` - -### `getAccount(String accountId)` - -Returns the circle account info and its balance. Usage: - -```java -Account account = service.getAccount("").block(); -System.out.println(account); -``` - -> Note: only works with Circle accounts, not stellar nor bank wire accounts. - -### `createAccount(String accountId)` - -Allows the creation of a new Circle account. The `accountId` isn't needed for Circle, but if provided it will be added -to the account description, which is not unique. Usage: - -```java -Account newAccount = service.createAccount(null).block(); -System.out.println(newAccount); -``` - -### `getAccountPaymentHistory(String accountID, String afterCursor, String beforeCursor)` - -Returns the paginated history of the account-related transactions, which includes: -- CircleWallet<>Circle -- CircleWallet<>Stellar -- CircleWallet->BankWire - -Usage: - -```java -PaymentHistory history = service.getAccountPaymentHistory("", null).block(); -System.out.println(history); - -PaymentHistory nextPageHistory = service.getAccountPaymentHistory("", null, history.afterCursor).block(); -System.out.println(nextPageHistory); -``` - -> Note: CircleWallet<-BankWire is still missing. - -### `sendPayment(Account sourceAccount, Account destinationAccount, String currencyName, BigDecimal amount)` - -Allows sending payments to internal and external accounts, including: -- CircleWallet->CircleWallet -- CircleWallet->Stellar -- CircleWallet->BankWire - - ATTENTION: in order to send a payment to a wire account you first need to create this wire account using the Circle - API. Please refer to the [Creating a Bank Wire Account](#creating-a-bank-wire-account) section for more info. - -Usage: - -```java -// CircleWallet->CircleWallet -Account source = new Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR)); -Account destination = new Account(PaymentNetwork.CIRCLE, "1000067536", Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR)); -String currencyName = CircleAsset.circleUSD(); -Payment payment = service.sendPayment(source, destination, currencyName, BigDecimal.valueOf(0.91)).block(); -System.out.println(payment); - -// CircleWallet->Stellar -Account source = new Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR)); -Account destination = new Account(PaymentNetwork.STELLAR, "GBG7VGZFH4TU2GS7WL5LMPYFNP64ZFR23XEGAV7GPEEXKWOR2DKCYPCK", "", Account.Capabilities()); -String currencyName = "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5"; -Payment payment = service.sendPayment(source, destination, currencyName, BigDecimal.valueOf(0.91)).block(); -System.out.println(payment); - -// CircleWallet->BankWire -Account source = new Account(PaymentNetwork.CIRCLE, "1000066041", Account.Capabilities(PaymentNetwork.CIRCLE, PaymentNetwork.STELLAR)); -// The bank wire account should have been created in advance directly in Circle -Account destination = new Account(PaymentNetwork.BANK_WIRE, "6c87da10-feb8-484f-822c-2083ed762d25", "test@mail.com", Account.Capabilities()); -String currencyName = CircleAsset.fiatUSD(); -Payment payment = service.sendPayment(source, destination, currencyName, BigDecimal.valueOf(0.91)).block(); -System.out.println(payment); -``` - -### `getDepositInstructions(DepositRequirements config)` - -Used to get the instructions to make a deposit into the desired account using the native Circle network or an -intermediary network (medium) such as Stellar or BankWire. - -Usage: - -```java -// Deposit requirements to receive CircleWallet<-CircleWallet payments -DepositRequirements config = DepositRequirements("1000066041", PaymentNetwork.CIRCLE, CircleAsset.circleUSD()); -DepositInstructions instructions = service.getDepositInstructions(config).block(); -System.out.println(instructions); - -// Deposit requirements to receive CircleWallet<-Stellar payments -DepositRequirements config = DepositRequirements("1000066041", PaymentNetwork.STELLAR, CircleAsset.circleUSD()); -DepositInstructions instructions = service.getDepositInstructions(config).block(); -System.out.println(instructions); - -// Deposit requirements to receive CircleWallet<-BankWire payments -DepositRequirements config = DepositRequirements("1000066041", null, PaymentNetwork.BANK_WIRE, "a4e76642-81c5-47ca-9229-ebd64efd74a7", CircleAsset.circleUSD()); -DepositInstructions instructions = service.getDepositInstructions(config).block(); -System.out.println(instructions); -``` - -## Circle Documentation - -To get more info on the circle API and how to properly configure and use it directly, please refer to [Circle docs] and -[Circle API reference]. - -[PaymentService]: ../core/src/main/java/org/stellar/anchor/paymentservice/PaymentService.java -[Circle network]: https://developers.circle.com/reference -[Circle docs]: https://developers.circle.com/docs/ -[Circle API reference]: https://developers.circle.com/reference -[Wire Payments Quickstart]: https://developers.circle.com/docs/wire-payments-quickstart#3-create-the-bank-account-you-will-accept-a-payment-from -[Wire Payouts Quickstart]: https://developers.circle.com/docs/payouts-quickstart#4-create-the-bank-account-you-will-send-the-payout-to -[Project Reactor]: https://projectreactor.io/docs/core/release/reference/ \ No newline at end of file diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 79915baf30..5766de8e98 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -1,328 +1,347 @@ -# 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 `@ConfigruationProperties` 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-local-postgres # use local postgres instance - - # - # 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 - -# ************************************ -# Application settings -# ************************************ -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://host.docker.internal: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 - # TODO: Move the secret to a separate secret management module. - jwtSecretKey: secret - - # 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. +version: 1 + +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 # - # 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: JWT_TOKEN - # 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: myPlatformToAnchorSecret - # 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: myAnchorToPlatformSecret - # Expiration time, in milliseconds, that will be used to build and validate the JWT tokens - expirationMilliseconds: 30000 - - # 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: host.docker.internal: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: SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - - # The list of omnibus account. - # 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: - - # Require authenticating clients to be in the omnibusAccountList, defaults to true - requireKnownOmnibusAccount: false + type: postgres - # sep-12 - sep12: - enabled: true - # The callback API endpoint. - customerIntegrationEndpoint: http://host.docker.internal: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://host.docker.internal:8081/sep24/interactive - - # sep-31 - sep31: - enabled: true - - # 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 - # 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 - - # sep-38 - sep38: - enabled: true - - # The callback API endpoint. - quoteIntegrationEndPoint: http://host.docker.internal:8081 - - circle: - circleUrl: https://api-sandbox.circle.com - apiKey: QVBJX0tFWTpjOWRjOWU3YjgzNTFhZDIwNWNlODEzYjkyZDEzZTU1YzpkY2U1MzVhMTExYjNjODU1YzQzNjMyZTQ5NWJhNDdlZg== # 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: 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 - - 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: - secretKey: + ## @param: url + ## @type: string + ## Location of the database + # + url: jdbc:postgresql://host.docker.internal:5440/ -# -# 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: 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.username: admin - spring.datasource.password: admin - spring.mvc.converters.preferred-json-mapper: gson - spring.flyway.enabled: false # certain features that liquibase uses (ex: addForeignKeyConstraint) are not supported by sqlite + ## @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 -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.url: jdbc:postgresql://database-2-instance-1.chizvyczscs2.us-east-1.rds.amazonaws.com:5432/ - spring.datasource.username: postgres - spring.datasource.password: - spring.mvc.converters.preferred-json-mapper: gson - spring.liquibase.change-log: classpath:/db/changelog/db.changelog-master.yaml + ## @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 -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:5431/ - 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:5431/ - spring.flyway.locations: classpath:/db/migration + ## @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 -data-spring-jdbc-h2: - spring.datasource.url: jdbc:h2:mem:test - spring.jpa.properties.hibernate.dialect: org.hibernate.dialect.H2Dialect - spring.liquibase.enabled: false + ## @param: flyway_enabled + ## @type: string + ## Location on disk where migrations are stored if flyway is enabled. + # + flyway_location: /db/migration -# -# Spring framework configurations -# -spring: - logging: - level: - root: INFO - org.springframework: INFO - org.springframework.web.filter: INFO - org.stellar: INFO - mvc: - async.request-timeout: 6000 + ## @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-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index 5e0b4bd7b8..b2ae00f66e 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -204,7 +204,11 @@ sep10: ## @param: client_attribution_required ## @type: bool - ## Set if the client attribution is required + ## Set if the client attribution is required. Client Attribution requires clients to verify their identity by passing + ## a domain in the challenge transaction request and signing the challenge with the ``SIGNING_KEY`` on that domain's + ## SEP-1 stellar.toml. See the SEP-10 section `Verifying Client Application Identity` for more information + ## (https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#verifying-client-application-identity). + # client_attribution_required: false @@ -321,13 +325,19 @@ sep38: metrics: ## @param: enabled ## @type: bool - ## If true, enable metrics. + ## If true, enable metrics endpoints for third-party services (ex: prometheus) to scrape # enabled: false + ## @param: port + ## @type: int + ## The port to expose the metrics endpoints on + port: 8082 + ## @param: extras_enabled ## @type: bool - ## If true, enable extra metrics. + ## If true, enable extra metrics (these metrics periodically query the database to get the current state of the + ## system) # extras_enabled: false 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 ba08f54fd5..e1bdca7c30 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -34,6 +34,7 @@ languages: logging.level: logging.stellar_level: metrics.enabled: +metrics.port: metrics.extras_enabled: metrics.run_interval: payment_observer.enabled: diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml index f6a95b974d..e25355e13d 100644 --- a/platform/src/main/resources/example.anchor-config.yaml +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -32,8 +32,6 @@ logging: 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 diff --git a/platform/src/main/resources/example.env b/platform/src/main/resources/example.env index 78436f57ff..58d6f5c8be 100644 --- a/platform/src/main/resources/example.env +++ b/platform/src/main/resources/example.env @@ -17,10 +17,7 @@ SECRET_SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW 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 +# OPTIONAL - used for storage definition (database) SECRET_DATA_USERNAME=admin SECRET_DATA_PASSWORD=admin From 48c97f2d628c89ca5fec26c17f62cfff7baa87ee Mon Sep 17 00:00:00 2001 From: Stephen Date: Mon, 7 Nov 2022 12:41:16 -0500 Subject: [PATCH 0048/1439] Update end_to_end_tests.yml with sep10_signing_seed (#650) Update end_to_end_tests.yml with sep10_signing_seed --- .github/workflows/end_to_end_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index a190025201..b531c40bc1 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -17,6 +17,7 @@ jobs: env: E2E_SECRET: ${{ secrets.E2E_SECRET }} OMNIBUS_ALLOWLIST_KEYS: ${{ secrets.OMNIBUS_ALLOWLIST_KEYS }} + SECRET_SEP10_SIGNING_SEED: ${{ secrets.SEP10_SIGNING_SEED }} steps: - uses: actions/checkout@v3 From 8156e010a4e30c0c9b9b5a2285fd8708c74a26a8 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 8 Nov 2022 14:37:39 -0800 Subject: [PATCH 0049/1439] Clean up spring boot application startups (#651) * Cleanup the server starters: sep and observer * Add ConfigManager tests * Remove metrics.port --- .../platform/AnchorPlatformIntegrationTest.kt | 8 ++- .../platform/ApiKeyAuthIntegrationTest.kt | 10 ++-- .../stellar/anchor/platform/SepTestSuite.kt | 6 +-- .../anchor/platform/AnchorPlatformServer.java | 25 ++------- .../platform/ConfigManagementBeans.java | 14 ++++- ...rvice.java => StellarObservingServer.java} | 25 +++------ .../platform/config/PropertyAssetsConfig.java | 4 +- .../platform/configurator/ConfigManager.java | 39 +++++++------- .../configurator/ObserverConfigManager.java | 47 +++++++++++++++++ .../configurator/SepConfigManager.java | 52 +++++++++++++++++++ .../service/PropertyAssetsService.java | 11 ++-- .../config/anchor-config-default-values.yaml | 22 ++++++-- .../config/anchor-config-schema-v1.yaml | 7 ++- .../configurator/ConfigManagerTest.kt | 2 +- .../anchor/platform/ServiceRunner.java | 22 +++----- 15 files changed, 193 insertions(+), 101 deletions(-) rename platform/src/main/java/org/stellar/anchor/platform/{StellarObservingService.java => StellarObservingServer.java} (58%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/SepConfigManager.java 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 fcdb2541fd..74fc3fa178 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 @@ -85,6 +85,10 @@ class AnchorPlatformIntegrationTest { fun setup() { val envMap = mapOf( + "sep_server.port" to SEP_SERVER_PORT, + "sep_server.context_path" to "/", + "payment_observer.port" to OBSERVER_HEALTH_SERVER_PORT, + "payment_observer.context_path" to "/", "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", "secret.sep10.jwt_secret" to "secret", "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF", @@ -92,8 +96,8 @@ class AnchorPlatformIntegrationTest { "secret.data.password" to "password" ) - platformServerContext = ServiceRunner.startSepServer(SEP_SERVER_PORT, "/", envMap) - ServiceRunner.startStellarObserver(OBSERVER_HEALTH_SERVER_PORT, "/", envMap) + platformServerContext = ServiceRunner.startSepServer(envMap) + ServiceRunner.startStellarObserver(envMap) AnchorReferenceServer.start(REFERENCE_SERVER_PORT, "/") } 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 ccc0c15544..6a4f353735 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 @@ -12,7 +12,8 @@ import okhttp3.mockwebserver.MockWebServer import org.hamcrest.CoreMatchers import org.hamcrest.MatcherAssert import org.junit.jupiter.api.AfterAll -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -54,9 +55,9 @@ class ApiKeyAuthIntegrationTest { // Start platform platformServerContext = AnchorPlatformServer.start( - PLATFORM_SERVER_PORT, - "/", mapOf( + "sep_server.port" to PLATFORM_SERVER_PORT, + "sep_server.context_path" to "/", "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", "secret.sep10.jwt_secret" to "secret", "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF", @@ -65,8 +66,7 @@ class ApiKeyAuthIntegrationTest { "callback_api.base_url" to mockAnchorUrl, "callback_api.auth.type" to "API_KEY", "secret.callback_api.auth_secret" to PLATFORM_TO_ANCHOR_SECRET - ), - true + ) ) } 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 4325d57456..988bdeaa27 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,11 +42,7 @@ fun main(args: Array) { // Start sep server if enabled. if (cmd.hasOption("sep-server") || cmd.hasOption("all")) { - ServiceRunner.startSepServer( - ServiceRunner.DEFAULT_SEP_SERVER_PORT, - ServiceRunner.DEFAULT_CONTEXT_PATH, - null - ) + ServiceRunner.startSepServer(null) } // Start anchor reference server if enabled. 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 ca037081fc..9ff2964517 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -13,8 +13,8 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 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; +import org.stellar.anchor.platform.configurator.SepConfigManager; @Profile("default") @SpringBootApplication @@ -22,24 +22,11 @@ @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) { + public static ConfigurableApplicationContext start(Map environment) { SpringApplicationBuilder builder = new SpringApplicationBuilder(AnchorPlatformServer.class) - .bannerMode(OFF) - .properties( - // TODO: move these configs to the config file when this is fixed and get rid of - // disableMetrics param - - // https://github.com/stellar/java-stellar-anchor-sdk/issues/297 - "spring.mvc.converters.preferred-json-mapper=gson", - String.format("server.port=%d", port), - String.format("server.contextPath=%s", contextPath)); + .bannerMode(OFF); - if (!disableMetrics) { - builder.properties( - "management.endpoints.web.exposure.include=health,info,prometheus", - "management.server.port=8082"); - } if (environment != null) { for (String name : environment.keySet()) { System.setProperty(name, String.valueOf(environment.get(name))); @@ -50,12 +37,8 @@ public static ConfigurableApplicationContext start( SpringApplication springApplication = builder.build(); springApplication.addInitializers(SecretManager.getInstance()); - springApplication.addInitializers(ConfigManager.getInstance()); + springApplication.addInitializers(SepConfigManager.getInstance()); return springApplication.run(); } - - 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/ConfigManagementBeans.java b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java index 8400b97c2e..51fb501395 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java @@ -3,16 +3,26 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; 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.configurator.ObserverConfigManager; +import org.stellar.anchor.platform.configurator.SepConfigManager; @Configuration public class ConfigManagementBeans { - @Bean + @Bean(name = "configManager") + @Profile("stellar-observer") + ConfigManager observerConfigManager() { + return ObserverConfigManager.getInstance(); + } + + @Bean(name = "configManager") + @Profile("default") ConfigManager configManager() { - return ConfigManager.getInstance(); + return SepConfigManager.getInstance(); } @Bean diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java similarity index 58% rename from platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java rename to platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java index 8b5d740c15..b3f8717ec7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java @@ -12,7 +12,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.ConfigManager; +import org.stellar.anchor.platform.configurator.ObserverConfigManager; import org.stellar.anchor.platform.configurator.SecretManager; @Profile("stellar-observer") @@ -20,22 +20,14 @@ @EnableJpaRepositories(basePackages = {"org.stellar.anchor.platform.data"}) @EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) @EnableConfigurationProperties -public class StellarObservingService implements WebMvcConfigurer { - - public static ConfigurableApplicationContext start( - int port, String contextPath, Map environment) { +public class StellarObservingServer implements WebMvcConfigurer { + public static ConfigurableApplicationContext start(Map environment) { SpringApplicationBuilder builder = - new SpringApplicationBuilder(StellarObservingService.class) + new SpringApplicationBuilder(StellarObservingServer.class) .bannerMode(OFF) .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", - String.format("server.port=%d", port), - String.format("server.contextPath=%s", contextPath)); + "spring.profiles.active=stellar-observer"); if (environment != null) { builder.properties(environment); @@ -44,11 +36,8 @@ public static ConfigurableApplicationContext start( SpringApplication springApplication = builder.build(); springApplication.addInitializers(SecretManager.getInstance()); - springApplication.addInitializers(ConfigManager.getInstance()); - return springApplication.run(); - } + springApplication.addInitializers(ObserverConfigManager.getInstance()); - public static ConfigurableApplicationContext start(int port, String contextPath) { - return start(port, contextPath, null); + return springApplication.run(); } } 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 index f781bca3ff..eeba05948e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java @@ -1,5 +1,7 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.Log.error; + import com.google.gson.JsonSyntaxException; import lombok.Data; import org.jetbrains.annotations.NotNull; @@ -10,8 +12,6 @@ import org.stellar.anchor.util.GsonUtils; import org.stellar.anchor.util.StringHelper; -import static org.stellar.anchor.util.Log.error; - @Data public class PropertyAssetsConfig implements AssetsConfig, Validator { AssetConfigType type; 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 index 1b8edebfa9..e4c6b7ce90 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java @@ -23,35 +23,20 @@ import org.stellar.anchor.healthcheck.HealthCheckable; import org.stellar.anchor.util.Log; -public class ConfigManager +public abstract class ConfigManager implements ApplicationContextInitializer, HealthCheckable { static final String STELLAR_ANCHOR_CONFIG = "STELLAR_ANCHOR_CONFIG"; - static ConfigManager configManager = new ConfigManager(); + static final ConfigManager configManager = new DefaultConfigManager(); ConfigMap configMap; - private ConfigManager() {} + 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 @@ -194,3 +179,21 @@ public HealthCheckStatus getStatus() { return GREEN; } } + +class DefaultConfigManager extends ConfigManager { + + @SneakyThrows + @Override + public void initialize(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())); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java new file mode 100644 index 0000000000..68f5f854ba --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java @@ -0,0 +1,47 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.util.Log.info; + +import java.util.List; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.ConfigurableApplicationContext; +import org.stellar.anchor.api.exception.InvalidConfigException; + +public class ObserverConfigManager extends ConfigManager { + private static final ObserverConfigManager observerConfigManager = new ObserverConfigManager(); + + private ObserverConfigManager() {} + + public static ObserverConfigManager getInstance() { + return observerConfigManager; + } + + @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(), new ObserverConfigAdapter())); + } +} + +class ObserverConfigAdapter extends SpringConfigAdapter { + @Override + void updateSpringEnv(ConfigMap config) throws InvalidConfigException { + copy(config, "payment_observer.context_path", "server.servlet.context-path"); + copy(config, "payment_observer.port", "server.port"); + set("spring.mvc.converters.preferred-json-mapper", "gson"); + // set("spring.config.import", "optional:classpath:example.env[.properties]"); + set("spring.profiles.active", "stellar-observer"); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/SepConfigManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/SepConfigManager.java new file mode 100644 index 0000000000..34d7190162 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/SepConfigManager.java @@ -0,0 +1,52 @@ +package org.stellar.anchor.platform.configurator; + +import static org.stellar.anchor.util.Log.info; + +import java.util.List; +import lombok.SneakyThrows; +import org.jetbrains.annotations.NotNull; +import org.springframework.context.ConfigurableApplicationContext; +import org.stellar.anchor.api.exception.InvalidConfigException; + +public class SepConfigManager extends ConfigManager { + private static final SepConfigManager sepConfigManager = new SepConfigManager(); + + private SepConfigManager() {} + + public static SepConfigManager getInstance() { + return sepConfigManager; + } + + @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(), new SepServerConfigAdapter())); + } +} + +class SepServerConfigAdapter extends SpringConfigAdapter { + @Override + void updateSpringEnv(ConfigMap config) throws InvalidConfigException { + copy(config, "sep_server.context_path", "server.servlet.context-path"); + copy(config, "sep_server.port", "server.port"); + set("spring.mvc.converters.preferred-json-mapper", "gson"); + if (config.getBoolean("metrics.enabled")) { + set("management.endpoints.enabled-by-default", true); + copy(config, "sep_server.management_server_port", "management.server.port"); + set("management.endpoints.web.exposure.include", "health,info,prometheus"); + } else { + set("management.endpoints.enabled-by-default", false); + } + } +} 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 index de6fa88cbe..0522d9f767 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java @@ -1,5 +1,8 @@ package org.stellar.anchor.platform.service; +import static org.stellar.anchor.util.Log.error; +import static org.stellar.anchor.util.Log.infoF; + import com.google.gson.Gson; import java.util.ArrayList; import java.util.List; @@ -9,10 +12,6 @@ import org.stellar.anchor.asset.Assets; import org.stellar.anchor.config.AssetsConfig; import org.stellar.anchor.util.GsonUtils; -import org.stellar.anchor.util.Log; - -import static org.stellar.anchor.util.Log.error; -import static org.stellar.anchor.util.Log.infoF; public class PropertyAssetsService implements AssetService { static final Gson gson = GsonUtils.getInstance(); @@ -25,7 +24,9 @@ public PropertyAssetsService(AssetsConfig assetsConfig) throws InvalidConfigExce assets = gson.fromJson(assetsJson, Assets.class); if (assets == null || assets.getAssets() == null || assets.getAssets().size() == 0) { error("Invalid asset defined. assets JSON=", assetsJson); - throw new InvalidConfigException(String.format("Invalid assets defined in configuration. Please check the logs for details.")); + throw new InvalidConfigException( + String.format( + "Invalid assets defined in configuration. Please check the logs for details.")); } break; case YAML: 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 b2ae00f66e..ffd0612ad9 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -106,6 +106,12 @@ platform_api: expiration_milliseconds: 30000 payment_observer: + # The context path of the Stellar observer server + context_path: / + + # The listening port of the Stellar observer server + port: 8083 + ## @param: type ## @type: string ## @supported_values: @@ -158,6 +164,16 @@ logging: ###################### ## SEP configuration ###################### +sep_server: + # The context path of the SEP server + context_path: / + + # The listening port of the SEP server + port: 8080 + + # the management server port + management_server_port: 8082 + sep1: ## @param: enabled @@ -322,6 +338,7 @@ sep38: ########################## ## Metrics Configuration ########################## +## The metrics are exposed at the port specified by sep_server.management_server_port metrics: ## @param: enabled ## @type: bool @@ -329,11 +346,6 @@ metrics: # enabled: false - ## @param: port - ## @type: int - ## The port to expose the metrics endpoints on - port: 8082 - ## @param: extras_enabled ## @type: bool ## If true, enable extra metrics (these metrics periodically query the database to get the current state of the 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 e1bdca7c30..2d52520b79 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -34,10 +34,10 @@ languages: logging.level: logging.stellar_level: metrics.enabled: -metrics.port: metrics.extras_enabled: metrics.run_interval: -payment_observer.enabled: +payment_observer.context_path: +payment_observer.port: payment_observer.stellar.initial_event_backoff_time: payment_observer.stellar.initial_stream_backoff_time: payment_observer.stellar.max_event_backoff_time: @@ -74,6 +74,9 @@ sep31.deposit_info_generator_type: sep31.enabled: sep31.payment_type: sep38.enabled: +sep_server.context_path: +sep_server.management_server_port: +sep_server.port: stellar_network.horizon_url: stellar_network.network: stellar_network.network_passphrase: \ No newline at end of file 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 index c807fdaca2..cb6972895d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt @@ -11,7 +11,7 @@ import org.stellar.anchor.platform.configurator.ConfigMap.ConfigSource.FILE @TestMethodOrder(MethodOrderer.OrderAnnotation::class) class ConfigManagerTest { - lateinit var configManager: ConfigManager + private lateinit var configManager: ConfigManager @BeforeEach fun setUp() { 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 4c743a8405..86e64ef638 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 @@ -6,10 +6,7 @@ import org.stellar.anchor.reference.AnchorReferenceServer; 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 int DEFAULT_STELLAR_OBSERVER_SERVER_PORT = 8083; - public static final String DEFAULT_CONTEXT_PATH = "/"; public static void main(String[] args) { Options options = new Options(); @@ -26,12 +23,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_CONTEXT_PATH, null); + startSepServer(null); anyServerStarted = true; } if (cmd.hasOption("stellar-observer") || cmd.hasOption("all")) { - startStellarObserver(DEFAULT_STELLAR_OBSERVER_SERVER_PORT, DEFAULT_CONTEXT_PATH, null); + startStellarObserver(null); anyServerStarted = true; } @@ -48,14 +45,12 @@ public static void main(String[] args) { } } - static ConfigurableApplicationContext startSepServer( - int port, String contextPath, Map env) { - return AnchorPlatformServer.start(port, contextPath, env, true); + static ConfigurableApplicationContext startSepServer(Map env) { + return AnchorPlatformServer.start(env); } - static ConfigurableApplicationContext startStellarObserver( - int port, String contextPath, Map env) { - return StellarObservingService.start(port, contextPath, env); + static ConfigurableApplicationContext startStellarObserver(Map env) { + return StellarObservingServer.start(env); } static void startAnchorReferenceServer() { @@ -65,10 +60,7 @@ static void startAnchorReferenceServer() { port = Integer.parseInt(strPort); } String contextPath = System.getProperty("ANCHOR_REFERENCE_CONTEXT_PATH"); - if (contextPath == null) { - contextPath = DEFAULT_CONTEXT_PATH; - } - AnchorReferenceServer.start(port, contextPath); + AnchorReferenceServer.start(port, "/"); } static void printUsage(Options options) { From 74155994a53ce15365e2be88436707ef8b2a4b7f Mon Sep 17 00:00:00 2001 From: erika-sdf <92893480+erika-sdf@users.noreply.github.com> Date: Tue, 15 Nov 2022 11:20:10 -0800 Subject: [PATCH 0050/1439] Add updating helm chart to release checklist (#603) * Add updating helm chart to release checklist * Update .github/ISSUE_TEMPLATE/release_a_new_version.md --- .github/ISSUE_TEMPLATE/release_a_new_version.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/release_a_new_version.md b/.github/ISSUE_TEMPLATE/release_a_new_version.md index e726669370..4939404488 100644 --- a/.github/ISSUE_TEMPLATE/release_a_new_version.md +++ b/.github/ISSUE_TEMPLATE/release_a_new_version.md @@ -25,8 +25,10 @@ labels: release - [ ] `release/0.1.0 -> develop`: ideally, this should be merged after the `main` branch is merged. - [ ] 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 [Maven Central](https://search.maven.org/search?q=g:org.stellar.anchor-sdk). + - [ ] 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. +- [ ] If necessary, open a PR for stellar/helm-charts and [update with the latest helm chart](https://docs.google.com/document/d/10ujUQZvBCMUyciObQPouxjtlnOdI5OpAz2Pk1LFdDDE) to publish + - [ ] Bump helm chart version. [CHANGELOG.md]: ../../CHANGELOG.md [docs/00 - Stellar Anchor Platform.md]: ../../docs/00%20-%20Stellar%20Anchor%20Platform.md From 541ad8d5c70628bcf945dd1d9ed33f90fa6a8a2c Mon Sep 17 00:00:00 2001 From: Stephen Date: Wed, 16 Nov 2022 14:23:41 -0500 Subject: [PATCH 0051/1439] update too SECERT_SEP10_SIGNING_SEED --- .github/workflows/end_to_end_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index b531c40bc1..c446aa75c0 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -17,7 +17,7 @@ jobs: env: E2E_SECRET: ${{ secrets.E2E_SECRET }} OMNIBUS_ALLOWLIST_KEYS: ${{ secrets.OMNIBUS_ALLOWLIST_KEYS }} - SECRET_SEP10_SIGNING_SEED: ${{ secrets.SEP10_SIGNING_SEED }} + SECRET_SEP10_SIGNING_SEED: ${{ secrets.SECRET_SEP10_SIGNING_SEED }} steps: - uses: actions/checkout@v3 From 5ce22c342b3e4c4cd0dca21a46ac12322f0a7e31 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 16 Nov 2022 15:38:21 -0800 Subject: [PATCH 0052/1439] Add Sep1Service tests (#666) * add Sep1Service tests --- .../org/stellar/anchor/sep1/Sep1Service.java | 17 +++-- .../stellar/anchor/sep1/Sep1ServiceTest.kt | 67 ++++++++++++++++++- .../anchor/platform/SepServiceBeans.java | 2 +- 3 files changed, 76 insertions(+), 10 deletions(-) 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 6818351f41..c3d760b7aa 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -1,14 +1,16 @@ package org.stellar.anchor.sep1; -import static org.stellar.anchor.util.Log.debug; -import static org.stellar.anchor.util.Log.debugF; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.config.Sep1Config; +import org.stellar.anchor.util.Log; +import org.stellar.anchor.util.NetUtil; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import org.stellar.anchor.config.Sep1Config; -import org.stellar.anchor.util.Log; -import org.stellar.anchor.util.NetUtil; + +import static org.stellar.anchor.util.Log.debug; +import static org.stellar.anchor.util.Log.debugF; public class Sep1Service { private String tomlValue; @@ -18,7 +20,7 @@ public class Sep1Service { * * @param sep1Config The Sep1 configuration. */ - public Sep1Service(Sep1Config sep1Config) throws IOException { + public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { if (sep1Config.isEnabled()) { debug("sep1Config:", sep1Config); switch (sep1Config.getType().toLowerCase()) { @@ -34,6 +36,9 @@ public Sep1Service(Sep1Config sep1Config) throws IOException { debugF("reading stellar.toml from {}", sep1Config.getValue()); tomlValue = NetUtil.fetch(sep1Config.getValue()); break; + default: + throw new InvalidConfigException( + String.format("invalid sep1.type: %s", sep1Config.getType())); } Log.info("Sep1Service initialized."); 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 2fb8418cc4..8a18f7a60d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -1,14 +1,75 @@ package org.stellar.anchor.sep1 -import io.mockk.mockk +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockkStatic +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.assertThrows +import org.stellar.anchor.api.exception.InvalidConfigException import org.stellar.anchor.config.Sep1Config +import org.stellar.anchor.util.NetUtil +import java.nio.file.Files @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class Sep1ServiceTest { + + @MockK(relaxed = true) lateinit var sep1Config: Sep1Config + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxUnitFun = true) + } + + @Test + fun `disabled Sep1Service should return null string as toml value`() { + every { sep1Config.isEnabled } returns false + val sep1Service = Sep1Service(sep1Config) + assertEquals(null, sep1Service.stellarToml) + } + + @Test + fun `test string type`() { + every { sep1Config.isEnabled } returns true + every { sep1Config.type } returns "string" + every { sep1Config.value } returns "toml content" + val sep1Service = Sep1Service(sep1Config) + assertEquals("toml content", sep1Service.stellarToml) + } + @Test - fun `simple test if the toml file is read`() { - val sep1Config = mockk() + fun `test file type`() { + mockkStatic(Files::class) + every { Files.readString(any()) } returns "toml content" + every { sep1Config.isEnabled } returns true + every { sep1Config.type } returns "file" + every { sep1Config.value } returns "toml_file_path" + + val sep1Service = Sep1Service(sep1Config) + assertEquals("toml content", sep1Service.stellarToml) + } + + @Test + fun `test url type`() { + mockkStatic(NetUtil::class) + every { NetUtil.fetch(any()) } returns "toml content" + every { sep1Config.isEnabled } returns true + every { sep1Config.type } returns "url" + every { sep1Config.value } returns "toml_file_path" + + val sep1Service = Sep1Service(sep1Config) + assertEquals("toml content", sep1Service.stellarToml) + } + + @Test + fun `test bad type`() { + every { sep1Config.isEnabled } returns true + every { sep1Config.type } returns "bad" + every { sep1Config.value } returns "toml_file_path" + + assertThrows { Sep1Service(sep1Config) } } } 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 153cbd802f..a6a9fd95c7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java @@ -79,7 +79,7 @@ public Horizon horizon(AppConfig appConfig) { } @Bean - Sep1Service sep1Service(Sep1Config sep1Config) throws IOException { + Sep1Service sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { return new Sep1Service(sep1Config); } From 85b4c5b1516e662320c9c87ed66890b543f25b82 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 16 Nov 2022 16:22:50 -0800 Subject: [PATCH 0053/1439] Fix the invadlid JSON in the readme file (#668) --- .../java/org/stellar/anchor/sep1/Sep1Service.java | 13 ++++++------- .../org/stellar/anchor/sep1/Sep1ServiceTest.kt | 2 +- .../A - Running & Configuring the Application.md | 10 +++++----- .../anchor/platform/AnchorPlatformServer.java | 3 +-- 4 files changed, 13 insertions(+), 15 deletions(-) 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 c3d760b7aa..5c2da6c882 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -1,16 +1,15 @@ package org.stellar.anchor.sep1; -import org.stellar.anchor.api.exception.InvalidConfigException; -import org.stellar.anchor.config.Sep1Config; -import org.stellar.anchor.util.Log; -import org.stellar.anchor.util.NetUtil; +import static org.stellar.anchor.util.Log.debug; +import static org.stellar.anchor.util.Log.debugF; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; - -import static org.stellar.anchor.util.Log.debug; -import static org.stellar.anchor.util.Log.debugF; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.config.Sep1Config; +import org.stellar.anchor.util.Log; +import org.stellar.anchor.util.NetUtil; public class Sep1Service { private String tomlValue; 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 8a18f7a60d..76bcf79f16 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -4,6 +4,7 @@ import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockkStatic +import java.nio.file.Files import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -12,7 +13,6 @@ import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.InvalidConfigException import org.stellar.anchor.config.Sep1Config import org.stellar.anchor.util.NetUtil -import java.nio.file.Files @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class Sep1ServiceTest { 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 85c346b4a8..d3f2bda383 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 @@ -271,12 +271,12 @@ The format of anchor platform's logs can be set by the `LOG_APPENDER` environmen ```json { - "time": timestamp, - "source": logger, - "index": location, + "time": "2001-01-09 01:05:01", + "source": "o.s.a.p.ServiceRunner", + "index": "location", "event": { - "message": message, - "severity": level, + "message": "Starting ServiceRunner using Java 11.0.16...", + "severity": "DEBUG" } } ``` 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 9ff2964517..202e05a4ec 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -24,8 +24,7 @@ public class AnchorPlatformServer implements WebMvcConfigurer { public static ConfigurableApplicationContext start(Map environment) { SpringApplicationBuilder builder = - new SpringApplicationBuilder(AnchorPlatformServer.class) - .bannerMode(OFF); + new SpringApplicationBuilder(AnchorPlatformServer.class).bannerMode(OFF); if (environment != null) { for (String name : environment.keySet()) { From 76ff0916ad871a5ba75f0a96dd0575667be6a3a6 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 17 Nov 2022 12:06:07 -0800 Subject: [PATCH 0054/1439] Upgraded library versions to avoid security vulnerability (#661) * upgraded library versions * Add log4j-api to integration test sub-project and platform sub-project --- build.gradle.kts | 9 +- .../stellar/anchor/sep31/Sep31HelperTest.kt | 3 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 9 +- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 379 +++++++++--------- gradle/libs.versions.toml | 22 +- integration-tests/build.gradle.kts | 1 + .../org/stellar/anchor/platform/SepClient.kt | 5 +- .../platform/AnchorPlatformIntegrationTest.kt | 12 +- .../platform/ApiKeyAuthIntegrationTest.kt | 15 +- .../anchor/platform/RestFeeIntegrationTest.kt | 52 ++- .../platform/RestRateIntegrationTest.kt | 148 +++---- platform/build.gradle.kts | 1 + .../org/stellar/anchor/Sep1ServiceTest.kt | 3 +- .../platform/PaymentObserverBeansTest.kt | 98 +++-- .../callback/RestCustomerIntegrationTest.kt | 15 +- .../anchor/platform/config/Sep1ConfigTest.kt | 14 +- .../platform/data/JdbcSep31TransactionTest.kt | 6 +- .../service/TransactionServiceTest.kt | 6 +- 18 files changed, 395 insertions(+), 403 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 375be7ca3a..5607839a30 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,7 +53,7 @@ subprojects { googleJavaFormat() } - kotlin { ktfmt("0.30").googleStyle() } + kotlin { ktfmt("0.41").googleStyle() } } dependencies { @@ -65,10 +65,13 @@ subprojects { 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) - - // TODO: we should use log4j2 implementation(rootProject.libs.log4j.template.json) + // Although the following libraries are transitive dependencies, we are including them here to override the version + // for security vulnerabilities. + implementation(rootProject.libs.spring.aws.messaging) + implementation(rootProject.libs.aws.java.sdk.s3) + // The common dependencies are declared here because we would like to have a uniform unit // testing across all subprojects. // 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 41e9c63f91..db71012078 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31HelperTest.kt @@ -38,7 +38,8 @@ class Sep31HelperTest { "pending_sender", "completed", "expired", - "error"] + "error" + ] ) fun `test validate status`(status: String) { val txn = PojoSep31Transaction() 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 5829991fb4..9900640639 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -810,7 +810,8 @@ class Sep31ServiceTest { "creator": { "account": "GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2" } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantTx, gotTx, true) // validate event response @@ -979,12 +980,14 @@ class Sep31ServiceTest { val jpycJson = """ {"enabled":true,"quotes_supported":true,"quotes_required":true,"fee_fixed":0,"fee_percent":0,"min_amount":1,"max_amount":1000000,"sep12":{"sender":{"types":{"sep31-sender":{"description":"Japanese citizens"}}},"receiver":{"types":{"sep31-receiver":{"description":"Japanese citizens receiving USD"}}}},"fields":{"transaction":{"receiver_routing_number":{"description":"routing number of the destination bank account","optional":false},"receiver_account_number":{"description":"bank account number of the destination","optional":false},"type":{"description":"type of deposit to make","choices":["ACH","SWIFT","WIRE"],"optional":false}}}} - """.trimIndent() + """ + .trimIndent() val usdcJson = """ {"enabled":true,"quotes_supported":true,"quotes_required":true,"fee_fixed":0,"fee_percent":0,"min_amount":1,"max_amount":1000000,"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","optional":false},"receiver_account_number":{"description":"bank account number of the destination","optional":false},"type":{"description":"type of deposit to make","choices":["SEPA","SWIFT"],"optional":false}}}} - """.trimIndent() + """ + .trimIndent() @Test fun `test INFO response`() { 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 bc4164bd1e..76254a1b52 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -709,256 +709,236 @@ class Sep38ServiceTest { assertEquals("sell_asset cannot be empty", ex.message) // nonexistent sell_asset - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder().sellAssetName("foo:bar").build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder().sellAssetName("foo:bar").build() + ) + } assertInstanceOf(NotFoundException::class.java, ex) assertEquals("sell_asset not found", ex.message) // empty buy_asset - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder().sellAssetName(fiatUSD).build() - ) - } + ex = assertThrows { + sep38Service.postQuote(token, Sep38PostQuoteRequest.builder().sellAssetName(fiatUSD).build()) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("buy_asset cannot be empty", ex.message) // nonexistent buy_asset - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder().sellAssetName(fiatUSD).buyAssetName("foo:bar").build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder().sellAssetName(fiatUSD).buyAssetName("foo:bar").build() + ) + } assertInstanceOf(NotFoundException::class.java, ex) assertEquals("buy_asset not found", ex.message) // both sell_amount & buy_amount are empty - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder().sellAssetName(fiatUSD).buyAssetName(stellarUSDC).build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder().sellAssetName(fiatUSD).buyAssetName(stellarUSDC).build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Please provide either sell_amount or buy_amount", ex.message) // both sell_amount & buy_amount are filled - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("100") - .buyAssetName(stellarUSDC) - .buyAmount("100") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("100") + .buyAssetName(stellarUSDC) + .buyAmount("100") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Please provide either sell_amount or buy_amount", ex.message) // invalid (not a number) sell_amount - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("foo") - .buyAssetName(stellarUSDC) - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("foo") + .buyAssetName(stellarUSDC) + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sell_amount is invalid", ex.message) // sell_amount should be positive - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("-0.01") - .buyAssetName(stellarUSDC) - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("-0.01") + .buyAssetName(stellarUSDC) + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sell_amount should be positive", ex.message) // sell_amount should be positive - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("0") - .buyAssetName(stellarUSDC) - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("0") + .buyAssetName(stellarUSDC) + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sell_amount should be positive", ex.message) // invalid (not a number) buy_amount - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .buyAssetName(stellarUSDC) - .buyAmount("bar") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .buyAssetName(stellarUSDC) + .buyAmount("bar") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("buy_amount is invalid", ex.message) // buy_amount should be positive - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .buyAssetName(stellarUSDC) - .buyAmount("-0.02") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .buyAssetName(stellarUSDC) + .buyAmount("-0.02") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("buy_amount should be positive", ex.message) // buy_amount should be positive - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .buyAssetName(stellarUSDC) - .buyAmount("0") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .buyAssetName(stellarUSDC) + .buyAmount("0") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("buy_amount should be positive", ex.message) // unsupported sell_delivery_method - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("1.23") - .sellDeliveryMethod("FOO") - .buyAssetName(stellarUSDC) - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("1.23") + .sellDeliveryMethod("FOO") + .buyAssetName(stellarUSDC) + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Unsupported sell delivery method", ex.message) // unsupported buy_delivery_method - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("1.23") - .sellDeliveryMethod("WIRE") - .buyAssetName(stellarUSDC) - .buyDeliveryMethod("BAR") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("1.23") + .sellDeliveryMethod("WIRE") + .buyAssetName(stellarUSDC) + .buyDeliveryMethod("BAR") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Unsupported buy delivery method", ex.message) // unsupported country_code - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("1.23") - .sellDeliveryMethod("WIRE") - .buyAssetName(stellarUSDC) - .countryCode("BRA") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("1.23") + .sellDeliveryMethod("WIRE") + .buyAssetName(stellarUSDC) + .countryCode("BRA") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Unsupported country code", ex.message) // unsupported expire_after - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("1.23") - .sellDeliveryMethod("WIRE") - .buyAssetName(stellarUSDC) - .countryCode("USA") - .expireAfter("2022-04-18T23:33:24.629719Z") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("1.23") + .sellDeliveryMethod("WIRE") + .buyAssetName(stellarUSDC) + .countryCode("USA") + .expireAfter("2022-04-18T23:33:24.629719Z") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("Unsupported context. Should be one of [sep6, sep31].", ex.message) // sell_amount should be within limit - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("100000000") - .sellDeliveryMethod("WIRE") - .context(SEP31) - .buyAssetName(stellarUSDC) - .countryCode("USA") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("100000000") + .sellDeliveryMethod("WIRE") + .context(SEP31) + .buyAssetName(stellarUSDC) + .countryCode("USA") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sell_amount exceeds max limit", ex.message) // sell_amount should be positive - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellAmount("0.5") - .sellDeliveryMethod("WIRE") - .context(SEP31) - .buyAssetName(stellarUSDC) - .countryCode("USA") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellAmount("0.5") + .sellDeliveryMethod("WIRE") + .context(SEP31) + .buyAssetName(stellarUSDC) + .countryCode("USA") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sell_amount less than min limit", ex.message) @@ -971,20 +951,19 @@ class Sep38ServiceTest { "100000000", mockSellAssetFee(fiatUSD) ) - ex = - assertThrows { - sep38Service.postQuote( - token, - Sep38PostQuoteRequest.builder() - .sellAssetName(fiatUSD) - .sellDeliveryMethod("WIRE") - .context(SEP31) - .buyAssetName(stellarUSDC) - .buyAmount("100000000") - .countryCode("USA") - .build() - ) - } + ex = assertThrows { + sep38Service.postQuote( + token, + Sep38PostQuoteRequest.builder() + .sellAssetName(fiatUSD) + .sellDeliveryMethod("WIRE") + .context(SEP31) + .buyAssetName(stellarUSDC) + .buyAmount("100000000") + .countryCode("USA") + .build() + ) + } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("sell_amount exceeds max limit", ex.message) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a5ccc500df..73e13d30eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,9 +13,9 @@ findbugs-jsr305 = "3.0.2" flyway-core = "8.5.13" google-gson = "2.8.9" httpclient = "4.5.13" -h2database = "1.4.200" +h2database = "2.1.214" hibernate-types = "2.18.0" -jackson-dataformat-yaml = "2.13.2" +jackson-dataformat-yaml = "2.14.0" java-stellar-sdk = "0.34.1" javax-jaxb-api = "2.3.1" javax-transaction-api = "1.3" @@ -23,16 +23,16 @@ jjwt = "0.9.1" jsonassert = "1.5.0" junit = "5.8.2" junit-suite-engine = "1.8.2" -kafka = "3.1.2" +kafka = "3.3.1" kafka-json-schema = "7.0.1" -kotlin = "1.6.20" -log4j = "2.17.1" -log4j-template-json = "2.17.1" +kotlin = "1.7.20" +log4j = "2.19.0" +log4j-template-json = "2.19.0" lombok = "1.18.22" mockk = "1.12.2" micrometer-prometheus = "1.9.0" okhttp3 = "4.9.3" -postgresql = "42.4.1" +postgresql = "42.5.0" reactor-core = "3.4.14" reactor-netty = "1.0.15" scala-library = "2.13.9" @@ -40,15 +40,16 @@ servlet-api = "2.5" snakeyaml = "1.32" spring-kafka = "2.9.1" spring-aws-messaging = "2.2.6.RELEASE" +aws-java-sdk-s3 = "1.12.342" sqlite-jdbc = "3.34.0" slf4j = "1.7.35" toml4j = "0.7.2" # Plugin versions -spotless = "6.2.1" +spotless = "6.9.1" spring-boot = "2.6.8" -spring-dependency-management = "1.0.11.RELEASE" +spring-dependency-management = "1.1.0" [libraries] apache-commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "apache-commons-lang3" } @@ -84,7 +85,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"} +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" } @@ -104,6 +105,7 @@ 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" } +aws-java-sdk-s3 = { module = "com.amazonaws:aws-java-sdk-s3", version.ref = "aws-java-sdk-s3" } [bundles] slf4j = ["slf4j-api", "slf4j-log4j12"] diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index 62fc10b901..3500fbe096 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation(libs.java.stellar.sdk) implementation(libs.google.gson) implementation(libs.okhttp3) + implementation(libs.log4j2.api) implementation(libs.log4j2.core) implementation(libs.log4j2.slf4j) diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/SepClient.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/SepClient.kt index af1c1f794d..50449868c7 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/SepClient.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/SepClient.kt @@ -56,8 +56,9 @@ open class SepClient { println("statusCode: " + response.code) println("responseBody: $responseBody") when (response.code) { - HttpStatus.OK.value(), HttpStatus.CREATED.value(), HttpStatus.ACCEPTED.value() -> - return responseBody + HttpStatus.OK.value(), + HttpStatus.CREATED.value(), + HttpStatus.ACCEPTED.value() -> return responseBody HttpStatus.FORBIDDEN.value() -> throw SepNotAuthorizedException("Forbidden") HttpStatus.NOT_FOUND.value() -> { val sepException = gson.fromJson(responseBody, SepExceptionResponse::class.java) 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 74fc3fa178..564869f9f9 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 @@ -182,7 +182,8 @@ class AnchorPlatformIntegrationTest { "sell_amount": "100", "buy_amount": "98.0392" } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(result), true) } @@ -218,14 +219,16 @@ class AnchorPlatformIntegrationTest { ] } } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(result), true) } @Test fun testRate_firm() { val rate = - rriClient.getRate( + rriClient + .getRate( GetRateRequest.builder() .type(FIRM) .context(SEP31) @@ -281,7 +284,8 @@ class AnchorPlatformIntegrationTest { ] } } - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, gson.toJson(gotQuote), true) } 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 6a4f353735..7e2d5c273b 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 @@ -84,7 +84,8 @@ class ApiKeyAuthIntegrationTest { "PATCH,/transactions", "GET,/transactions/my_id", "GET,/exchange/quotes", - "GET,/exchange/quotes/id"] + "GET,/exchange/quotes/id" + ] ) fun test_incomingPlatformAuth_emptyApiKey_authFails(method: String, endpoint: String) { val httpRequest = @@ -105,7 +106,8 @@ class ApiKeyAuthIntegrationTest { "PATCH,/transactions", "GET,/transactions/my_id", "GET,/exchange/quotes", - "GET,/exchange/quotes/id"] + "GET,/exchange/quotes/id" + ] ) fun test_incomingPlatformAuth_apiKey_authPasses(method: String, endpoint: String) { val httpRequest = @@ -137,7 +139,8 @@ class ApiKeyAuthIntegrationTest { "asset": "iso4217:USD" } } - }""".trimMargin() + }""" + .trimMargin() ) ) val sep38Service = platformServerContext.getBean(Sep38Service::class.java) @@ -173,10 +176,8 @@ class ApiKeyAuthIntegrationTest { &sell_asset=iso4217%3AUSD &sell_amount=100 &buy_asset=stellar%3AUSDC%3AGDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP - """.replace( - "\n ", - "" - ) + """ + .replace("\n ", "") MatcherAssert.assertThat(request.path, CoreMatchers.endsWith(wantEndpoint)) assertEquals("", request.body.readUtf8()) } 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 f1e475c15b..f225dfb20e 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 @@ -129,10 +129,8 @@ class RestFeeIntegrationTest { &send_amount=10 &client_id=%3Cclient-id%3E &sender_id=%3Csender-id%3E - &receiver_id=%3Creceiver-id%3E""".replace( - "\n ", - "" - ) + &receiver_id=%3Creceiver-id%3E""" + .replace("\n ", "") ) ) } @@ -140,29 +138,29 @@ class RestFeeIntegrationTest { @Test fun test_getFee_errorHandling() { val validateRequest = - { statusCode: Int, responseBody: String?, wantException: AnchorException -> - // mock response - var mockResponse = - MockResponse().addHeader("Content-Type", "application/json").setResponseCode(statusCode) - if (responseBody != null) mockResponse = mockResponse.setBody(responseBody) - server.enqueue(mockResponse) - - // execute command - val dummyRequest = GetFeeRequest.builder().build() - val ex = assertThrows { feeIntegration.getFee(dummyRequest) } - - // validate exception - assertEquals(wantException.javaClass, ex.javaClass) - assertEquals(wantException.message, ex.message) - - // validateRequest - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer $mockJwtToken", request.headers["Authorization"]) - MatcherAssert.assertThat(request.path, CoreMatchers.endsWith("/fee")) - assertEquals("", request.body.readUtf8()) - } + { statusCode: Int, responseBody: String?, wantException: AnchorException -> + // mock response + var mockResponse = + MockResponse().addHeader("Content-Type", "application/json").setResponseCode(statusCode) + if (responseBody != null) mockResponse = mockResponse.setBody(responseBody) + server.enqueue(mockResponse) + + // execute command + val dummyRequest = GetFeeRequest.builder().build() + val ex = assertThrows { feeIntegration.getFee(dummyRequest) } + + // validate exception + assertEquals(wantException.javaClass, ex.javaClass) + assertEquals(wantException.message, ex.message) + + // validateRequest + val request = server.takeRequest() + assertEquals("GET", request.method) + assertEquals("application/json", request.headers["Content-Type"]) + assertEquals("Bearer $mockJwtToken", request.headers["Authorization"]) + MatcherAssert.assertThat(request.path, CoreMatchers.endsWith("/fee")) + assertEquals("", request.body.readUtf8()) + } // 400 without body validateRequest(400, null, BadRequestException("Bad Request")) 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 c15bb8f97e..5942f83842 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 @@ -175,10 +175,8 @@ class RestRateIntegrationTest { &sell_asset=iso4217%3AUSD &sell_amount=100 &sell_delivery_method=WIRE - &country_code=USA""".replace( - "\n ", - "" - ), + &country_code=USA""" + .replace("\n ", ""), getRateRequest ) @@ -203,10 +201,8 @@ class RestRateIntegrationTest { &sell_delivery_method=WIRE &buy_asset=stellar%3AUSDC%3AGA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN &buy_delivery_method=CASH - &country_code=USA""".replace( - "\n ", - "" - ), + &country_code=USA""" + .replace("\n ", ""), getRateRequest ) @@ -231,10 +227,8 @@ class RestRateIntegrationTest { &sell_delivery_method=WIRE &buy_asset=stellar%3AUSDC%3AGA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN &buy_delivery_method=CASH - &country_code=USA""".replace( - "\n ", - "" - ), + &country_code=USA""" + .replace("\n ", ""), getRateRequest ) @@ -265,10 +259,8 @@ class RestRateIntegrationTest { &buy_delivery_method=WIRE &country_code=USA &expire_after=2022-04-30T02%3A15%3A44.000Z - &client_id=GDGWTSQKQQAT2OXRSFLADMN4F6WJQMPJ5MIOKIZ2AMBYUI67MJA4WRLA""".replace( - "\n ", - "" - ), + &client_id=GDGWTSQKQQAT2OXRSFLADMN4F6WJQMPJ5MIOKIZ2AMBYUI67MJA4WRLA""" + .replace("\n ", ""), getRateRequest ) } @@ -276,33 +268,33 @@ class RestRateIntegrationTest { @Test fun test_getRate_errorHandling() { val validateRequest = - { - statusCode: Int, - responseBody: String?, - wantException: AnchorException, - type: GetRateRequest.Type -> - // mock response - var mockResponse = - MockResponse().addHeader("Content-Type", "application/json").setResponseCode(statusCode) - if (responseBody != null) mockResponse = mockResponse.setBody(responseBody) - server.enqueue(mockResponse) - - // execute command - val dummyRequest = GetRateRequest.builder().type(type).build() - val ex = assertThrows { rateIntegration.getRate(dummyRequest) } - - // validate exception - assertEquals(wantException.javaClass, ex.javaClass) - assertEquals(wantException.message, ex.message) - - // validateRequest - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer $mockJwtToken", request.headers["Authorization"]) - MatcherAssert.assertThat(request.path, CoreMatchers.endsWith("/rate?type=$type")) - assertEquals("", request.body.readUtf8()) - } + { + statusCode: Int, + responseBody: String?, + wantException: AnchorException, + type: GetRateRequest.Type -> + // mock response + var mockResponse = + MockResponse().addHeader("Content-Type", "application/json").setResponseCode(statusCode) + if (responseBody != null) mockResponse = mockResponse.setBody(responseBody) + server.enqueue(mockResponse) + + // execute command + val dummyRequest = GetRateRequest.builder().type(type).build() + val ex = assertThrows { rateIntegration.getRate(dummyRequest) } + + // validate exception + assertEquals(wantException.javaClass, ex.javaClass) + assertEquals(wantException.message, ex.message) + + // validateRequest + val request = server.takeRequest() + assertEquals("GET", request.method) + assertEquals("application/json", request.headers["Content-Type"]) + assertEquals("Bearer $mockJwtToken", request.headers["Authorization"]) + MatcherAssert.assertThat(request.path, CoreMatchers.endsWith("/rate?type=$type")) + assertEquals("", request.body.readUtf8()) + } // 400 without body validateRequest(400, null, BadRequestException("Bad Request"), INDICATIVE_PRICES) @@ -385,7 +377,8 @@ class RestRateIntegrationTest { "asset": "iso4217:USD" } } - }""".trimMargin() + }""" + .trimMargin() validateRequest(200, body, serverErrorException, FIRM) validateRequest(200, body, serverErrorException, INDICATIVE_PRICE) @@ -400,7 +393,8 @@ class RestRateIntegrationTest { "asset": "iso4217:USD" } } - }""".trimMargin() + }""" + .trimMargin() validateRequest(200, body, serverErrorException, FIRM) // 200 for type=firm where getRateResponse is missing "id" but contains "expires_at" @@ -415,7 +409,8 @@ class RestRateIntegrationTest { "asset": "iso4217:USD" } } - }""".trimMargin() + }""" + .trimMargin() validateRequest(200, body, serverErrorException, FIRM) // 200 for type=firm where getRateResponse is missing "expires_at" @@ -430,7 +425,8 @@ class RestRateIntegrationTest { "asset": "iso4217:USD" } } - }""".trimMargin() + }""" + .trimMargin() validateRequest(200, body, serverErrorException, FIRM) // 200 for type=firm where getRateResponse's "expires_at" is invalid @@ -446,7 +442,8 @@ class RestRateIntegrationTest { "asset": "iso4217:USD" } } - }""".trimMargin() + }""" + .trimMargin() validateRequest(200, body, serverErrorException, FIRM) } @@ -455,29 +452,29 @@ class RestRateIntegrationTest { val fee = mockSellAssetFee("iso4217:USD") val validateRequest = - { type: GetRateRequest.Type, responseBody: String, wantResponse: GetRateResponse -> - // mock response - val mockResponse = - MockResponse() - .addHeader("Content-Type", "application/json") - .setResponseCode(200) - .setBody(responseBody) - server.enqueue(mockResponse) - - // execute command - val dummyRequest = GetRateRequest.builder().type(type).build() - var gotResponse: GetRateResponse? = null - assertDoesNotThrow { gotResponse = rateIntegration.getRate(dummyRequest) } - assertEquals(wantResponse, gotResponse) - - // validateRequest - val request = server.takeRequest() - assertEquals("GET", request.method) - assertEquals("application/json", request.headers["Content-Type"]) - assertEquals("Bearer $mockJwtToken", request.headers["Authorization"]) - MatcherAssert.assertThat(request.path, CoreMatchers.endsWith("/rate?type=$type")) - assertEquals("", request.body.readUtf8()) - } + { type: GetRateRequest.Type, responseBody: String, wantResponse: GetRateResponse -> + // mock response + val mockResponse = + MockResponse() + .addHeader("Content-Type", "application/json") + .setResponseCode(200) + .setBody(responseBody) + server.enqueue(mockResponse) + + // execute command + val dummyRequest = GetRateRequest.builder().type(type).build() + var gotResponse: GetRateResponse? = null + assertDoesNotThrow { gotResponse = rateIntegration.getRate(dummyRequest) } + assertEquals(wantResponse, gotResponse) + + // validateRequest + val request = server.takeRequest() + assertEquals("GET", request.method) + assertEquals("application/json", request.headers["Content-Type"]) + assertEquals("Bearer $mockJwtToken", request.headers["Authorization"]) + MatcherAssert.assertThat(request.path, CoreMatchers.endsWith("/rate?type=$type")) + assertEquals("", request.body.readUtf8()) + } // indicative_prices quote successful response var wantGetRateResponse = GetRateResponse.indicativePrices("1.02", "102", "100") @@ -489,7 +486,8 @@ class RestRateIntegrationTest { "sell_amount": "102", "buy_amount": "100" } - }""".trimMargin(), + }""" + .trimMargin(), wantGetRateResponse ) @@ -514,7 +512,8 @@ class RestRateIntegrationTest { ] } } - }""".trimMargin(), + }""" + .trimMargin(), wantGetRateResponse ) @@ -554,7 +553,8 @@ class RestRateIntegrationTest { ] } } - }""".trimMargin(), + }""" + .trimMargin(), wantGetRateResponse ) verify(atLeast = 1) { Instant.now() } diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index 21164598f8..c6d00bee5b 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(libs.sqlite.jdbc) implementation(libs.okhttp3) implementation(libs.jackson.dataformat.yaml) + implementation(libs.log4j2.api) implementation(libs.log4j2.core) implementation(libs.log4j2.slf4j) diff --git a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt index 29d789cc87..403a8785cc 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt @@ -40,7 +40,8 @@ class Sep1ServiceTest { ORG_KEYBASE = "stellar.public" ORG_TWITTER = "StellarOrg" ORG_GITHUB = "stellar" - """.trimIndent() + """ + .trimIndent() @Test fun `test Sep1Service reading toml from inline string`() { diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt index 3fe6a79fa8..2b896f9482 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt @@ -51,81 +51,75 @@ class PaymentObserverBeansTest { // assetService.listAllAssets() is null val mockEmptyAssetService = mockk() every { mockEmptyAssetService.listAllAssets() } returns null - ex = - assertThrows { - paymentObserverBeans.stellarPaymentObserver( - mockEmptyAssetService, - null, - null, - null, - null, - null - ) - } + ex = assertThrows { + paymentObserverBeans.stellarPaymentObserver( + mockEmptyAssetService, + null, + null, + null, + null, + null + ) + } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service cannot be empty.", ex.message) // assetService.listAllAssets() doesn't contain stellar assets val mockStellarLessAssetService = mockk() every { mockStellarLessAssetService.listAllAssets() } returns listOf() - ex = - assertThrows { - paymentObserverBeans.stellarPaymentObserver( - mockStellarLessAssetService, - null, - null, - null, - null, - null - ) - } + ex = assertThrows { + paymentObserverBeans.stellarPaymentObserver( + mockStellarLessAssetService, + null, + null, + null, + null, + null + ) + } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Asset service should contain at least one Stellar asset.", ex.message) // paymentListeners is null - ex = - assertThrows { - paymentObserverBeans.stellarPaymentObserver(assetService, null, null, null, null, null) - } + ex = assertThrows { + paymentObserverBeans.stellarPaymentObserver(assetService, null, 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 { - paymentObserverBeans.stellarPaymentObserver(assetService, listOf(), null, null, null, null) - } + ex = assertThrows { + paymentObserverBeans.stellarPaymentObserver(assetService, listOf(), null, null, null, null) + } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("The stellar payment observer service needs at least one listener.", ex.message) // paymentStreamerCursorStore is null - ex = - assertThrows { - paymentObserverBeans.stellarPaymentObserver( - assetService, - mockPaymentListeners, - null, - null, - null, - null - ) - } + ex = assertThrows { + paymentObserverBeans.stellarPaymentObserver( + assetService, + mockPaymentListeners, + null, + null, + null, + null + ) + } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("Payment streamer cursor store cannot be empty.", ex.message) // appConfig is null every { paymentStreamerCursorStore.load() } returns null - ex = - assertThrows { - paymentObserverBeans.stellarPaymentObserver( - assetService, - mockPaymentListeners, - paymentStreamerCursorStore, - null, - null, - null - ) - } + ex = assertThrows { + paymentObserverBeans.stellarPaymentObserver( + assetService, + mockPaymentListeners, + paymentStreamerCursorStore, + null, + null, + null + ) + } assertInstanceOf(ServerErrorException::class.java, ex) assertEquals("AppConfig cannot be empty.", ex.message) } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/callback/RestCustomerIntegrationTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/callback/RestCustomerIntegrationTest.kt index b30ee0b754..5ff63db91b 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/callback/RestCustomerIntegrationTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/callback/RestCustomerIntegrationTest.kt @@ -94,7 +94,8 @@ class RestCustomerIntegrationTest { "status": "ACCEPTED" } } - }""".trimMargin() + }""" + .trimMargin() ) ) @@ -128,10 +129,8 @@ class RestCustomerIntegrationTest { &memo_type=memoType &type=sending_user &lang=en - """.replace( - "\n ", - "" - ) + """ + .replace("\n ", "") MatcherAssert.assertThat(request.path, CoreMatchers.endsWith(wantEndpoint)) assertEquals("", request.body.readUtf8()) } @@ -197,7 +196,8 @@ class RestCustomerIntegrationTest { "memo": "memo", "first_name": "John", "last_name": "Doe" - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, request.body.readUtf8(), true) } @@ -226,7 +226,8 @@ class RestCustomerIntegrationTest { """{ "id": "customer-id", "account": "$TEST_ACCOUNT" - }""".trimMargin() + }""" + .trimMargin() JSONAssert.assertEquals(wantBody, request.body.readUtf8(), true) } 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 1f5da4177e..55c77ada38 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 @@ -15,19 +15,17 @@ open class Sep1ConfigTest { companion object { fun getTestTomlAsFile(): String { val resource: URL = - Sep1ConfigTest::class.java.getResource( - "/org/stellar/anchor/platform/config/sep1-stellar-test.toml" - ) as - 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 + Sep1ConfigTest::class + .java + .getResource("/org/stellar/anchor/platform/config/sep1-stellar-test.toml") as URL return resource.toString() } 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 index 1e64bfada7..39142eb78d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt @@ -13,7 +13,8 @@ class JdbcSep31TransactionTest { "amount_fee": "5", "refundPayments": null } - """.trimIndent() + """ + .trimIndent() private val refundsJsonWithRefundPayment = """ @@ -33,7 +34,8 @@ class JdbcSep31TransactionTest { } ] } - """.trimIndent() + """ + .trimIndent() @Test fun `test JdbcSep31Transaction refunds Json conversion`() { 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 ce716dfed0..cdba8e2d6c 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 @@ -303,7 +303,8 @@ class TransactionServiceTest { "COMPLETED", "REFUNDED", "EXPIRED", - "ERROR"] + "ERROR" + ] ) fun test_validateIfStatusIsSupported_failure(sepTxnStatus: SepTransactionStatus) { val ex: Exception = assertThrows { @@ -325,7 +326,8 @@ class TransactionServiceTest { "PENDING_EXTERNAL", "COMPLETED", "EXPIRED", - "ERROR"] + "ERROR" + ] ) fun test_validateIfStatusIsSupported(sepTxnStatus: SepTransactionStatus) { assertDoesNotThrow { transactionService.validateIfStatusIsSupported(sepTxnStatus.getName()) } From ed5676ef0ad7b6a67ee7efb617b23459555cf93a Mon Sep 17 00:00:00 2001 From: Stephen Date: Fri, 18 Nov 2022 16:33:04 -0500 Subject: [PATCH 0055/1439] add delay in tests (#670) --- end-to-end-tests/end_to_end_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/end-to-end-tests/end_to_end_tests.py b/end-to-end-tests/end_to_end_tests.py index 5285b5e77c..a3796b9c70 100644 --- a/end-to-end-tests/end_to_end_tests.py +++ b/end-to-end-tests/end_to_end_tests.py @@ -221,6 +221,7 @@ def wait_for_anchor_platform_ready(domain, poll_interval=3, timeout=180): try: toml = fetch_stellar_toml(domain, use_http=True) print("anchor platform is ready") + time.sleep(30) return except Exception as e: print(e) From 8f96a334c960dda8e92c8fa7118b5dcda14293c9 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 21 Nov 2022 16:47:57 -0800 Subject: [PATCH 0056/1439] Reduce project errors and warnings - part 1 (#667) * Fixed gradle warnings * Add final to fields * Optimize flow control warning * Add final modifiers to fields * Fixed the dockerfile complaints * Suppressed the SameReturnValue complaints * replace sameAs with equals * delete EventRequest class * fix threading deadlock issue * fix some typos --- .../reference/client/BaseApiClient.java | 7 ++- .../reference/client/PlatformApiClient.java | 7 +++ .../controller/HealthController.java | 2 +- .../reference/controller/InfoController.java | 2 +- .../reference/event/AnchorEventProcessor.java | 7 +-- .../anchor/reference/event/KafkaListener.java | 2 +- .../anchor/reference/service/FeeService.java | 4 +- .../reference/service/HealthCheckService.java | 2 +- .../service/UniqueAddressService.java | 2 +- .../reference/sqlite/SQLiteDialect.java | 2 +- .../api/callback/CustomerIntegration.java | 1 + .../callback/UniqueAddressIntegration.java | 2 +- .../anchor/api/event/EventRequest.java | 10 ---- .../anchor/api/exception/HttpException.java | 8 --- .../InternalServerErrorException.java | 4 -- .../api/exception/InvalidConfigException.java | 2 +- .../ServiceUnavailableException.java | 4 -- .../exception/ValueValidationException.java | 11 ---- .../api/platform/HealthCheckStatus.java | 2 +- .../anchor/api/sep/SepTransactionStatus.java | 1 + .../stellar/anchor/api/sep/sep12/Field.java | 5 +- .../sep/sep12/Sep12GetCustomerRequest.java | 3 +- .../sep/sep12/Sep12GetCustomerResponse.java | 3 +- .../sep/sep12/Sep12PutCustomerRequest.java | 5 +- .../anchor/api/sep/sep38/InfoResponse.java | 2 + .../stellar/anchor/api/shared/Metadata.java | 1 + core/build.gradle.kts | 2 +- .../anchor/asset/JsonAssetService.java | 2 +- .../org/stellar/anchor/config/AppConfig.java | 1 + .../stellar/anchor/config/Sep24Config.java | 1 + .../stellar/anchor/config/Sep38Config.java | 1 + .../anchor/event/models/QuoteEvent.java | 1 + .../anchor/event/models/TransactionEvent.java | 5 ++ .../org/stellar/anchor/filter/NoneFilter.java | 2 +- .../anchor/healthcheck/HealthCheckable.java | 2 +- .../org/stellar/anchor/sep1/Sep1Service.java | 2 + .../anchor/sep24/Sep24RefundPayment.java | 1 + .../stellar/anchor/sep24/Sep24Refunds.java | 1 + .../stellar/anchor/sep24/Sep24Service.java | 2 - .../anchor/sep24/Sep24TransactionStore.java | 1 + .../sep31/Sep31DepositInfoGenerator.java | 1 + .../anchor/sep31/Sep31Transaction.java | 1 + .../anchor/sep31/Sep31TransactionBuilder.java | 2 - .../anchor/sep31/Sep31TransactionStore.java | 1 + .../org/stellar/anchor/sep38/Sep38Quote.java | 1 + .../java/org/stellar/anchor/util/Log.java | 1 + .../org/stellar/anchor/util/MathHelper.java | 1 + .../java/org/stellar/anchor/util/NetUtil.java | 1 + .../org/stellar/anchor/util/PropertyUtil.java | 3 - .../org/stellar/anchor/util/Sep1Helper.java | 2 +- .../org/stellar/anchor/util/SepHelper.java | 9 ++- .../anchor/util/StellarNetworkHelper.java | 18 ------ .../anchor/util/UrlValidationUtil.java | 27 +-------- .../kotlin/org/stellar/anchor/Constants.kt | 3 - .../kotlin/org/stellar/anchor/TestHelper.kt | 2 - .../stellar/anchor/sep1/Sep1ServiceTest.kt | 2 + .../stellar/anchor/sep10/Sep10ServiceTest.kt | 2 + .../stellar/anchor/sep12/Sep12ServiceTest.kt | 2 + .../stellar/anchor/sep24/Sep24ServiceTest.kt | 2 + .../stellar/anchor/sep31/RefundPaymentTest.kt | 2 + .../anchor/sep31/RefundsBuilderTest.kt | 2 + .../org/stellar/anchor/sep31/RefundsTest.kt | 2 + .../stellar/anchor/sep31/Sep31ServiceTest.kt | 2 + .../sep31/Sep31TransactionBuilderTest.kt | 2 + .../anchor/sep31/Sep31TransactionTest.kt | 2 + .../stellar/anchor/sep38/Sep38ServiceTest.kt | 4 +- .../kotlin/org/stellar/anchor/util/LogTest.kt | 2 + .../org/stellar/anchor/util/NetUtilTest.kt | 2 + .../stellar/anchor/util/PropertyUtilTest.kt | 2 + .../anchor/util/SepLanguageHelperTest.kt | 2 + end-to-end-tests/Dockerfile | 2 +- .../anchor/platform/RestFeeIntegrationTest.kt | 2 + .../platform/RestRateIntegrationTest.kt | 4 +- .../org/stellar/anchor/platform/Sep10Tests.kt | 2 - .../org/stellar/anchor/platform/SystemUtil.kt | 24 -------- .../callback/RestCustomerIntegration.java | 1 - .../condition/AbstractOnSepsEnabled.java | 5 +- .../platform/configurator/ConfigManager.java | 12 ++-- .../platform/configurator/ConfigMap.java | 17 ++++-- .../configurator/DataConfigAdapter.java | 4 +- .../platform/configurator/SecretManager.java | 6 +- .../configurator/SpringConfigAdapter.java | 14 +---- .../platform/controller/HealthController.java | 2 +- .../platform/controller/Sep1Controller.java | 3 +- .../platform/controller/Sep31Controller.java | 14 ++--- .../platform/data/JdbcSep31Refunds.java | 4 +- .../data/JdbcSep31TransactionStore.java | 8 +-- .../IAMAuthDataSource.java | 1 + .../platform/event/KafkaEventPublisher.java | 2 +- ...moryStellarPaymentStreamerCursorStore.java | 1 + .../stellar/PaymentObservingAccountStore.java | 2 +- .../PaymentObservingAccountsManager.java | 7 +-- .../stellar/StellarPaymentObserver.java | 10 ++-- .../platform/service/HealthCheckService.java | 2 +- .../service/MetricEmitterService.java | 16 ++--- .../PaymentOperationToEventListener.java | 58 +++++++++---------- .../service/PropertyAssetsService.java | 3 +- .../platform/service/TransactionService.java | 2 +- .../anchor/platform/sqlite/SQLiteDialect.java | 1 + .../anchor/platform/EmptyAssetService.kt | 18 ------ .../platform/PaymentObserverBeansTest.kt | 2 + .../configurator/ConfigManagerTest.kt | 4 +- .../controller/HealthControllerTest.kt | 2 + .../controller/Sep38ControllerTest.kt | 31 ---------- .../PaymentObservingAccountsManagerTest.kt | 2 + .../stellar/StellarPaymentObserverTest.kt | 2 + .../PaymentOperationToEventListenerTest.kt | 2 + .../Sep31DepositInfoGeneratorApiTest.kt | 2 + .../service/TransactionServiceTest.kt | 1 + .../sep31/Sep31DepositInfoGeneratorTest.kt | 2 + .../anchor/platform/ServiceRunner.java | 1 - 111 files changed, 214 insertions(+), 314 deletions(-) delete mode 100644 api-schema/src/main/java/org/stellar/anchor/api/event/EventRequest.java delete mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/ValueValidationException.java delete mode 100644 core/src/main/java/org/stellar/anchor/util/StellarNetworkHelper.java delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/SystemUtil.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/EmptyAssetService.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/controller/Sep38ControllerTest.kt diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/BaseApiClient.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/BaseApiClient.java index 5534f3e5fa..650e077667 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/BaseApiClient.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/BaseApiClient.java @@ -13,8 +13,8 @@ import org.stellar.anchor.util.GsonUtils; public abstract class BaseApiClient { - static Gson gson = GsonUtils.getInstance(); - static OkHttpClient client = + static final Gson gson = GsonUtils.getInstance(); + static final OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.MINUTES) .readTimeout(10, TimeUnit.MINUTES) @@ -23,8 +23,9 @@ public abstract class BaseApiClient { .build(); String handleResponse(Response response) throws AnchorException, IOException { - String responseBody = response.body().string(); + if (response.body() == null) throw new SepException("Empty response"); + String responseBody = response.body().string(); if (response.code() == HttpStatus.FORBIDDEN.value()) { throw new SepNotAuthorizedException("Forbidden"); } else if (!List.of( diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/PlatformApiClient.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/PlatformApiClient.java index 6a29359437..06a201ea5f 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/PlatformApiClient.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/client/PlatformApiClient.java @@ -8,6 +8,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import org.stellar.anchor.api.exception.AnchorException; +import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.platform.PatchTransactionsRequest; import org.stellar.anchor.api.platform.PatchTransactionsResponse; @@ -33,6 +34,9 @@ public GetTransactionResponse getTransaction(String id) throws IOException, Anch public PatchTransactionsResponse patchTransaction(PatchTransactionsRequest txnRequest) throws IOException, AnchorException { HttpUrl url = HttpUrl.parse(endpoint); + if (url == null) + throw new InvalidConfigException( + String.format("Invalid endpoint: %s of the client.", endpoint)); url = new HttpUrl.Builder() .scheme(url.scheme()) @@ -49,6 +53,9 @@ public PatchTransactionsResponse patchTransaction(PatchTransactionsRequest txnRe public HashMap health(List checks) throws IOException, AnchorException { HttpUrl url = HttpUrl.parse(endpoint); + if (url == null) + throw new InvalidConfigException( + String.format("Invalid endpoint: %s of the client.", endpoint)); HttpUrl.Builder builder = new HttpUrl.Builder() .scheme(url.scheme()) diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/HealthController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/HealthController.java index d0d25e6c15..3d16810d52 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/HealthController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/HealthController.java @@ -9,7 +9,7 @@ @CrossOrigin(origins = "*") @RequestMapping(value = "/health") public class HealthController { - HealthCheckService healthCheckService; + final HealthCheckService healthCheckService; HealthController(HealthCheckService healthCheckService) { this.healthCheckService = healthCheckService; diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/InfoController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/InfoController.java index 521d46a78a..5d1bb20bf9 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/InfoController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/InfoController.java @@ -8,7 +8,7 @@ @RestController public class InfoController { - private AppSettings appSettings; + private final AppSettings appSettings; public InfoController(AppSettings appSettings) { this.appSettings = appSettings; diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java index 28c9fb373c..c3cbcdcfbb 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java @@ -24,11 +24,8 @@ public class AnchorEventProcessor { public void handleQuoteEvent(QuoteEvent event) { Log.debugF("Received quote event: {}", event); - switch (event.getType()) { - case "quote_created": - break; - default: - Log.debugF("error: anchor_platform_event - invalid message type '{}'", event.getType()); + if (!"quote_created".equals(event.getType())) { + Log.debugF("error: anchor_platform_event - invalid message type '{}'", event.getType()); } } 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 564836a4be..235801f20f 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 @@ -164,7 +164,7 @@ boolean validateKafka() { class KafkaHealthCheckResult implements HealthCheckResult { transient String name; - List statuses = List.of(GREEN, RED); + List statuses; HealthCheckStatus status; diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java index 7bf06c0d31..6d88466947 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java @@ -15,8 +15,8 @@ @Service public class FeeService { private final CustomerRepo customerRepo; - BigDecimal feePercent = decimal("0.02"); // fixed 2% fee. - BigDecimal feeFixed = decimal("0.1"); + final BigDecimal feePercent = decimal("0.02"); // fixed 2% fee. + final BigDecimal feeFixed = decimal("0.1"); FeeService(CustomerRepo customerRepo) { this.customerRepo = customerRepo; 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 c628e956d2..af91767c6b 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 @@ -12,7 +12,7 @@ @Service @DependsOn("eventListener") public class HealthCheckService { - HealthCheckProcessor processor; + final HealthCheckProcessor processor; public HealthCheckService(List checkables) { checkables.forEach( 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 53fe5b2fff..c81cbbb903 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 @@ -19,7 +19,7 @@ @Service @ConditionalOnPropertyNotEmpty("anchor.settings.distributionWallet") public class UniqueAddressService { - AppSettings appSettings; + final AppSettings appSettings; UniqueAddressService(AppSettings appSettings) throws SepException { this.appSettings = appSettings; diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/sqlite/SQLiteDialect.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/sqlite/SQLiteDialect.java index 4b96f461c6..f2b86457a8 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/sqlite/SQLiteDialect.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/sqlite/SQLiteDialect.java @@ -7,7 +7,7 @@ import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.type.StringType; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "SameReturnValue"}) public class SQLiteDialect extends Dialect { public SQLiteDialect() { registerColumnType(Types.BIT, "integer"); diff --git a/api-schema/src/main/java/org/stellar/anchor/api/callback/CustomerIntegration.java b/api-schema/src/main/java/org/stellar/anchor/api/callback/CustomerIntegration.java index 3281361f7e..e5aee6dfe9 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/callback/CustomerIntegration.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/callback/CustomerIntegration.java @@ -40,6 +40,7 @@ public interface CustomerIntegration { * @return The response. * @throws AnchorException if error happens */ + @SuppressWarnings("RedundantThrows") PutCustomerVerificationResponse putVerification(PutCustomerVerificationRequest request) throws AnchorException; } 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 index 2d4019e1d7..a748575a1e 100644 --- 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 @@ -6,7 +6,7 @@ public interface UniqueAddressIntegration { /** * Gets the unique address of a transaction to which the Stellar fund can be sent. * - * @param transactionId + * @param transactionId the transaction ID * @return The GetUniqueAddressResponse */ GetUniqueAddressResponse getUniqueAddress(String transactionId) throws AnchorException; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/event/EventRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/event/EventRequest.java deleted file mode 100644 index bd8f134b04..0000000000 --- a/api-schema/src/main/java/org/stellar/anchor/api/event/EventRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.stellar.anchor.api.event; - -import lombok.Data; - -@Data -public class EventRequest { - String id; - String type; - Object data; -} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/HttpException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/HttpException.java index f38197a57c..a8726fceb6 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/exception/HttpException.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/HttpException.java @@ -15,14 +15,6 @@ public HttpException(int statusCode) { this(statusCode, null, null, null); } - public HttpException(int statusCode, @Nullable String reason) { - this(statusCode, reason, null, null); - } - - public HttpException(int statusCode, @Nullable String reason, @Nullable String internalCode) { - this(statusCode, reason, internalCode, null); - } - public HttpException( int statusCode, @Nullable String reason, diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/InternalServerErrorException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/InternalServerErrorException.java index 34b3694317..019f208eb0 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/exception/InternalServerErrorException.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/InternalServerErrorException.java @@ -4,10 +4,6 @@ @EqualsAndHashCode(callSuper = false) public class InternalServerErrorException extends AnchorException { - public InternalServerErrorException(String message, Exception cause) { - super(message, cause); - } - public InternalServerErrorException(String message) { super(message); } 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 index f9a315861d..415fb87732 100644 --- 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 @@ -7,7 +7,7 @@ @Getter public class InvalidConfigException extends AnchorException { - List messages; + final List messages; public InvalidConfigException(String... messages) { this(Arrays.asList(messages), null); diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/ServiceUnavailableException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/ServiceUnavailableException.java index 8a3f6b1897..5776f110ea 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/exception/ServiceUnavailableException.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/ServiceUnavailableException.java @@ -4,8 +4,4 @@ public class ServiceUnavailableException extends AnchorException { public ServiceUnavailableException(String message, Exception cause) { super(message, cause); } - - public ServiceUnavailableException(String message) { - super(message); - } } 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 deleted file mode 100644 index 07d903a52e..0000000000 --- a/api-schema/src/main/java/org/stellar/anchor/api/exception/ValueValidationException.java +++ /dev/null @@ -1,11 +0,0 @@ -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/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckStatus.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckStatus.java index 0c54f9bf9d..b87017c6b1 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckStatus.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/HealthCheckStatus.java @@ -5,7 +5,7 @@ public enum HealthCheckStatus { YELLOW("yellow"), GREEN("green"); - String name; + final String name; HealthCheckStatus(String name) { this.name = name; 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 33e26ae254..dceeae0fa8 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 @@ -43,6 +43,7 @@ public String getName() { return name; } + @SuppressWarnings("unused") public String getDescription() { return description; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Field.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Field.java index 471af9f493..9cde071953 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Field.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Field.java @@ -10,9 +10,8 @@ import lombok.NoArgsConstructor; /** - * Refer to SEP-12. - * - *

https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#fields + * Refer to SEP-12. ... */ @Data @Builder diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerRequest.java index 485d47dd0d..e64822da68 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerRequest.java @@ -7,7 +7,8 @@ /** * Refer to SEP-12. * - *

https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#request + *

... */ @Data @Builder diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerResponse.java index 20c2ab128c..5cc526b371 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12GetCustomerResponse.java @@ -7,7 +7,8 @@ /** * Refer to SEP-12. * - *

https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#response + *

... */ @Data public class Sep12GetCustomerResponse { diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12PutCustomerRequest.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12PutCustomerRequest.java index ae9bb2eae9..153394efc8 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12PutCustomerRequest.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep12/Sep12PutCustomerRequest.java @@ -5,9 +5,8 @@ import lombok.Data; /** - * Refer to SEP-12. - * - *

https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#request-1 + * Refer to SEP-12. ... */ @Data @Builder diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/InfoResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/InfoResponse.java index cc9265ca4b..7bba87bbe8 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/InfoResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep38/InfoResponse.java @@ -54,10 +54,12 @@ public static class Asset { private transient Integer decimals; + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean supportsSellDeliveryMethod(String deliveryMethod) { return supportsDeliveryMethod(sellDeliveryMethods, deliveryMethod); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public boolean supportsBuyDeliveryMethod(String deliveryMethod) { return supportsDeliveryMethod(buyDeliveryMethods, deliveryMethod); } 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 index 614c7af72c..2c85c8b7ba 100644 --- 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 @@ -20,6 +20,7 @@ private static Properties getProperties() { return projectProperties; } + @SuppressWarnings("unused") private static String get(String key) { return get(key, null); } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 74ae4f9140..6a0f545034 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { api(libs.lombok) - // Lombok should be used by all sub-projects to reduce Java verbosity + // Lombok should be used by all subprojects to reduce Java verbosity annotationProcessor(libs.lombok) implementation(libs.spring.kafka) 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 f32432e7fb..472357886d 100644 --- a/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java +++ b/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java @@ -8,7 +8,7 @@ public class JsonAssetService implements AssetService { static final Gson gson = GsonUtils.getInstance(); - Assets assets; + final Assets assets; public JsonAssetService(String assetJson) { this.assets = gson.fromJson(assetJson, Assets.class); 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 c4857fc7e3..5786824ee1 100644 --- a/core/src/main/java/org/stellar/anchor/config/AppConfig.java +++ b/core/src/main/java/org/stellar/anchor/config/AppConfig.java @@ -2,6 +2,7 @@ import java.util.List; +@SuppressWarnings("SameReturnValue") public interface AppConfig { String getStellarNetworkPassphrase(); diff --git a/core/src/main/java/org/stellar/anchor/config/Sep24Config.java b/core/src/main/java/org/stellar/anchor/config/Sep24Config.java index 769bd394c9..6262c29e18 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep24Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep24Config.java @@ -1,5 +1,6 @@ package org.stellar.anchor.config; +@SuppressWarnings("unused") public interface Sep24Config { boolean isEnabled(); 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 f69c1016b1..845c66dcb8 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep38Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep38Config.java @@ -1,5 +1,6 @@ package org.stellar.anchor.config; +@SuppressWarnings("SameReturnValue") public interface Sep38Config { boolean isEnabled(); } 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 b909be78d1..a2f728e666 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 @@ -65,6 +65,7 @@ public String getType() { RateFee fee; public enum Type { + UNDEFINED("undefined"), QUOTE_CREATED("quote_created"); @JsonValue public final String type; 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 7716f9ca4a..d7bd47459c 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 @@ -146,6 +146,8 @@ public static class StatusChange { } public enum Sep { + @SuppressWarnings("unused") + SEP_24(24), SEP_31(31); private final Integer sep; @@ -163,6 +165,7 @@ public Integer getSep() { public enum Type { TRANSACTION_CREATED("transaction_created"), TRANSACTION_STATUS_CHANGED("transaction_status_changed"), + @SuppressWarnings("unused") TRANSACTION_ERROR("transaction_error"); @JsonValue public final String type; @@ -173,6 +176,8 @@ public enum Type { } public enum Kind { + @SuppressWarnings("unused") + UNDEFINED("undefined"), RECEIVE("receive"); public final String kind; diff --git a/core/src/main/java/org/stellar/anchor/filter/NoneFilter.java b/core/src/main/java/org/stellar/anchor/filter/NoneFilter.java index feb05dbb95..c13692053c 100644 --- a/core/src/main/java/org/stellar/anchor/filter/NoneFilter.java +++ b/core/src/main/java/org/stellar/anchor/filter/NoneFilter.java @@ -7,7 +7,7 @@ @NoArgsConstructor public class NoneFilter implements Filter { @Override - public void init(FilterConfig filterConfig) throws ServletException {} + public void init(FilterConfig filterConfig) {} @Override public void doFilter( 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 4746ac82a8..efeccce28a 100644 --- a/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckable.java +++ b/core/src/main/java/org/stellar/anchor/healthcheck/HealthCheckable.java @@ -37,7 +37,7 @@ enum Tags { EVENT("evnet"), CONFIG("config"); - private String name; + private final String name; Tags(String name) { this.name = 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 5c2da6c882..348203a8d0 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -18,6 +18,8 @@ public class Sep1Service { * Construct the Sep1Service that reads the stellar.toml content from Java resource. * * @param sep1Config The Sep1 configuration. + * @throws IOException if the file cannot be read. + * @throws InvalidConfigException if invalid type is specified. */ public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { if (sep1Config.isEnabled()) { diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java index c4b40a6423..91bf94e870 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java @@ -1,5 +1,6 @@ package org.stellar.anchor.sep24; +@SuppressWarnings("unused") public interface Sep24RefundPayment { /** * The payment ID that can be used to identify the refund payment. This is either a Stellar diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java index cab4cb214c..f25c894517 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java @@ -2,6 +2,7 @@ import java.util.List; +@SuppressWarnings("unused") public interface Sep24Refunds { String getAmountRefunded(); diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 7232bc8bdd..800390263a 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -35,7 +35,6 @@ import org.stellar.sdk.Memo; public class Sep24Service { - final Gson gson; final AppConfig appConfig; final Sep24Config sep24Config; final AssetService assetService; @@ -51,7 +50,6 @@ public Sep24Service( Sep24TransactionStore txnStore) { debug("appConfig:", appConfig); debug("sep24Config:", sep24Config); - this.gson = gson; this.appConfig = appConfig; this.sep24Config = sep24Config; this.assetService = assetService; diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java index 14ec9ac29c..db1f867a49 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java @@ -5,6 +5,7 @@ import org.stellar.anchor.api.sep.sep24.GetTransactionsRequest; /** This interface is for the SEP adapter service to query/save the transaction document. */ +@SuppressWarnings("RedundantThrows") public interface Sep24TransactionStore { Sep24Transaction newInstance(); 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 42fa723db1..97b9ddbc02 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31DepositInfoGenerator.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31DepositInfoGenerator.java @@ -9,6 +9,7 @@ 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. + * @throws AnchorException if the deposit info cannot be generated */ Sep31DepositInfo generate(Sep31Transaction txn) throws AnchorException; } 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 af6a2e5096..46858c780a 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -8,6 +8,7 @@ import org.stellar.anchor.api.shared.*; import org.stellar.anchor.event.models.TransactionEvent; +@SuppressWarnings("unused") public interface Sep31Transaction { String getId(); 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 6daddb98f7..0dcc4f4dd6 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java @@ -9,10 +9,8 @@ public class Sep31TransactionBuilder { final Sep31Transaction txn; - final Sep31TransactionStore factory; public Sep31TransactionBuilder(Sep31TransactionStore factory) { - this.factory = factory; this.txn = factory.newTransaction(); } 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 8efd0b4be2..8543bf4d1c 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java @@ -6,6 +6,7 @@ import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.SepException; +@SuppressWarnings("RedundantThrows") public interface Sep31TransactionStore { Sep31Transaction newTransaction(); diff --git a/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java b/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java index 1ce5289543..4715b37380 100644 --- a/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java +++ b/core/src/main/java/org/stellar/anchor/sep38/Sep38Quote.java @@ -40,6 +40,7 @@ public interface Sep38Quote { void setBuyAmount(String buyAmount); + @SuppressWarnings("unused") String getBuyDeliveryMethod(); void setBuyDeliveryMethod(String buyDeliveryMethod); diff --git a/core/src/main/java/org/stellar/anchor/util/Log.java b/core/src/main/java/org/stellar/anchor/util/Log.java index 6d5e989932..095cc46c5b 100644 --- a/core/src/main/java/org/stellar/anchor/util/Log.java +++ b/core/src/main/java/org/stellar/anchor/util/Log.java @@ -15,6 +15,7 @@ import org.stellar.anchor.config.Secret; /** Logging utility functions. */ +@SuppressWarnings("unused") public class Log { static final Gson gson; 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 2fe371661d..fa5bce0b32 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,7 @@ public static BigDecimal decimal(Long value) { return BigDecimal.valueOf(value); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean equalsAsDecimals(String valueA, String valueB) { if (valueA == null && valueB == null) { return true; 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 462a60812f..4ae44efe72 100644 --- a/core/src/main/java/org/stellar/anchor/util/NetUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/NetUtil.java @@ -16,6 +16,7 @@ public static String fetch(String url) throws IOException { return Objects.requireNonNull(response.body()).string(); } + @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isUrlValid(String url) { /* Try creating a valid URL */ try { diff --git a/core/src/main/java/org/stellar/anchor/util/PropertyUtil.java b/core/src/main/java/org/stellar/anchor/util/PropertyUtil.java index 594ba8f5b6..c40f8b2212 100644 --- a/core/src/main/java/org/stellar/anchor/util/PropertyUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/PropertyUtil.java @@ -7,7 +7,6 @@ import org.apache.commons.beanutils.PropertyUtils; public class PropertyUtil { - @SuppressWarnings("unchecked") public static void set(Object instance, String path, Object value) throws ReflectiveOperationException { Target location = findTarget(instance, path, true); @@ -19,7 +18,6 @@ public static void set(Object instance, String path, Object value) } } - @SuppressWarnings("unchecked") public static Optional get(Object bean, String path) { try { Target target = findTarget(bean, path, false); @@ -36,7 +34,6 @@ public static Optional get(Object bean, String path) { } } - @SuppressWarnings("unchecked") static Target findTarget(Object target, String path, boolean create) throws ReflectiveOperationException { StringTokenizer st = new StringTokenizer(path, "."); diff --git a/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java b/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java index a4b9d67dcb..517a0864f3 100644 --- a/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java +++ b/core/src/main/java/org/stellar/anchor/util/Sep1Helper.java @@ -14,7 +14,7 @@ public static TomlContent parse(String tomlString) { } public static class TomlContent { - private Toml toml; + private final Toml toml; TomlContent(URL url) throws IOException { String tomlValue = NetUtil.fetch(url.toString()); 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 dd3755d9a8..f5cbd69ff2 100644 --- a/core/src/main/java/org/stellar/anchor/util/SepHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/SepHelper.java @@ -10,7 +10,6 @@ import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.sep.SepTransactionStatus; -import org.stellar.sdk.*; import org.stellar.sdk.xdr.MemoType; public class SepHelper { @@ -78,10 +77,10 @@ public static void validateAmountLimit(String messagePrefix, String amount, Long BigDecimal sAmount = validateAmount("", amount); BigDecimal bdMin = new BigDecimal(min); BigDecimal bdMax = new BigDecimal(max); - if (sAmount.compareTo(bdMin) == -1) { + if (sAmount.compareTo(bdMin) < 0) { throw new BadRequestException(String.format("%samount less than min limit", messagePrefix)); } - if (sAmount.compareTo(bdMax) == 1) { + if (sAmount.compareTo(bdMax) > 0) { throw new BadRequestException(String.format("%samount exceeds max limit", messagePrefix)); } } @@ -121,7 +120,7 @@ public static boolean validateTransactionStatus(SepTransactionStatus status, int } } - static List sep24Statuses = + static final List sep24Statuses = List.of( INCOMPLETE, PENDING_USR_TRANSFER_START, @@ -137,7 +136,7 @@ public static boolean validateTransactionStatus(SepTransactionStatus status, int TOO_LARGE, ERROR); - static List sep31Statuses = + static final List sep31Statuses = List.of( PENDING_SENDER, PENDING_STELLAR, diff --git a/core/src/main/java/org/stellar/anchor/util/StellarNetworkHelper.java b/core/src/main/java/org/stellar/anchor/util/StellarNetworkHelper.java deleted file mode 100644 index 968d84214b..0000000000 --- a/core/src/main/java/org/stellar/anchor/util/StellarNetworkHelper.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.stellar.anchor.util; - -import org.stellar.sdk.Network; - -public class StellarNetworkHelper { - public static Network toStellarNetwork(String stellarNetworkPassphrase) { - - if (stellarNetworkPassphrase.equals(Network.TESTNET.getNetworkPassphrase())) { - return Network.TESTNET; - } else if (stellarNetworkPassphrase.equals(Network.PUBLIC.getNetworkPassphrase())) { - return Network.PUBLIC; - } - - throw new IllegalArgumentException( - String.format( - "Invalid Stellar network passphrase [%s] is specified.", stellarNetworkPassphrase)); - } -} 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 b2b2605c49..18c8cf2255 100644 --- a/core/src/main/java/org/stellar/anchor/util/UrlValidationUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/UrlValidationUtil.java @@ -1,40 +1,15 @@ package org.stellar.anchor.util; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; import org.springframework.validation.Errors; 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); - if (testConnection) { - URLConnection conn = url.openConnection(); - conn.connect(); - } - } catch (MalformedURLException e) { - return UrlConnectionStatus.MALFORMED; - } catch (IOException e) { - return UrlConnectionStatus.UNREACHABLE; - } - return UrlConnectionStatus.VALID; - } public static void rejectIfMalformed(String url, String fieldName, Errors errors) { - UrlConnectionStatus urlStatus = validateUrl(url); - if (urlStatus == UrlConnectionStatus.MALFORMED) { + if (!NetUtil.isUrlValid(url)) { errors.rejectValue( fieldName, 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, url)); } } } diff --git a/core/src/test/kotlin/org/stellar/anchor/Constants.kt b/core/src/test/kotlin/org/stellar/anchor/Constants.kt index cb94e315a3..541e5ac832 100644 --- a/core/src/test/kotlin/org/stellar/anchor/Constants.kt +++ b/core/src/test/kotlin/org/stellar/anchor/Constants.kt @@ -4,9 +4,6 @@ class Constants { companion object { const val TEST_SIGNING_SEED = "SBVEOFAHGJCKGR4AAM7RTDRCP6RMYYV5YUV32ZK7ZD3VPDGGHYLXTZRZ" const val TEST_ACCOUNT = "GBLGJA4TUN5XOGTV6WO2BWYUI2OZR5GYQ5PDPCRMQ5XEPJOYWB2X4CJO" - const val TEST_SEP10_CUSTODIAL_SEED = "SD4UZRPPWOGFXDYLNRHJDOTHVRJIGUWKVGVBA4BX4WSDGVJQDR3FMRTW" - const val TEST_SEP10_CUSTODIAL_ACCOUNT = - "GC2PO4QZQX2R4T4EVJAPC3GXTLWXUEHJVGKAKERFESEK5PBJSAHMMG3Q" const val TEST_MEMO = "123" const val TEST_HOME_DOMAIN = "test.stellar.org" const val TEST_CLIENT_DOMAIN = "test.client.stellar.org" diff --git a/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt b/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt index b57b43b4d3..61a29ba567 100644 --- a/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt +++ b/core/src/test/kotlin/org/stellar/anchor/TestHelper.kt @@ -5,8 +5,6 @@ import org.stellar.anchor.auth.JwtToken class TestHelper { companion object { const val TEST_ACCOUNT = "GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2" - const val TEST_MUXED_ACCOUNT = - "MBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLEAAAAAAAAAPCIBR34" const val TEST_MEMO = "123456" fun createJwtToken( 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 76bcf79f16..36ba003758 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep1 import io.mockk.MockKAnnotations 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 81e1449838..cfd71e5aaa 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep10 import com.google.common.io.BaseEncoding 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 9b1b400fd0..3abe8f660a 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep12 import io.mockk.* 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 79828fe47d..185d667105 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep24 import io.mockk.* 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 3548a10d3c..4f2dda27d7 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import io.mockk.MockKAnnotations diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt index ff56cb2201..c6504550de 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import io.mockk.MockKAnnotations 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 deee947e50..92aefaa817 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import io.mockk.MockKAnnotations 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 9900640639..ce8e5f80f1 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import com.google.gson.Gson diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionBuilderTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionBuilderTest.kt index 5cabfefeba..98a7d67cea 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionBuilderTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionBuilderTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import io.mockk.MockKAnnotations 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 b88fd2639a..34308bf026 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import io.mockk.MockKAnnotations 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 76254a1b52..487c36c7a4 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused", "SameParameterValue") + package org.stellar.anchor.sep38 import io.mockk.* @@ -23,7 +25,6 @@ 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.EventService @@ -54,7 +55,6 @@ class Sep38ServiceTest { @MockK(relaxed = true) private lateinit var eventService: EventService // sep10 related: - @MockK(relaxed = true) private lateinit var appConfig: AppConfig @MockK(relaxed = true) private lateinit var secretConfig: SecretConfig @BeforeEach 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 9ee94ef0a7..3da9e13e85 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.util import io.mockk.* 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 780049e4ed..ced3aaed85 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.util import io.mockk.* diff --git a/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt b/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt index 7844b201ce..9a2f960b12 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/PropertyUtilTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.util import java.util.* 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 257494e744..83a6362ddf 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/SepLanguageHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/SepLanguageHelperTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.util import io.mockk.MockKAnnotations diff --git a/end-to-end-tests/Dockerfile b/end-to-end-tests/Dockerfile index 17c19e7a50..b53cf6b7f0 100644 --- a/end-to-end-tests/Dockerfile +++ b/end-to-end-tests/Dockerfile @@ -7,4 +7,4 @@ RUN pip3 install -r requirements.txt COPY end-to-end-tests/. . -Entrypoint [ "python3", "end_to_end_tests.py"] \ No newline at end of file +ENTRYPOINT [ "python3", "end_to_end_tests.py"] \ No newline at end of file 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 f225dfb20e..06c95ad8c1 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 @@ -1,3 +1,5 @@ +@file:Suppress("SameParameterValue") + package org.stellar.anchor.platform import io.mockk.* 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 5942f83842..0e19ba1d0c 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 @@ -3,7 +3,7 @@ package org.stellar.anchor.platform import io.mockk.* import java.time.Instant import java.time.format.DateTimeFormatter -import java.util.Calendar +import java.util.* import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.hamcrest.CoreMatchers @@ -22,7 +22,7 @@ import org.stellar.anchor.api.exception.NotFoundException import org.stellar.anchor.api.exception.ServerErrorException import org.stellar.anchor.api.sep.sep38.RateFee import org.stellar.anchor.api.sep.sep38.RateFeeDetail -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.auth.JwtToken diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt index c8f09d6d7f..7cc31b277b 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt @@ -5,9 +5,7 @@ import org.stellar.anchor.api.exception.SepNotAuthorizedException import org.stellar.anchor.api.sep.sep10.ValidationRequest import org.stellar.anchor.util.Sep1Helper -var CLIENT_WALLET_EXTRA_SIGNER_1 = "GC6X2ANA2OS3O2ESHUV6X44NH6J46EP2EO2JB7563Y7DYOIXFKHMHJ5O" var CLIENT_WALLET_EXTRA_SIGNER_1_SECRET = "SDUDRKCL5AX7RDZ7S6JAPBNKLV6LRZXJSHN5OJDP32TIJB42ODPQODHY" -var CLIENT_WALLET_EXTRA_SIGNER_2 = "GATEYCIMJZ2F6Y437QSYH4XFQ6HLD5YP4MBJZFFPZVEQDJOY4QTCB7BB" var CLIENT_WALLET_EXTRA_SIGNER_2_SECRET = "SC52GRNSIODLPNGTXUCZ5NHBII4PYUKUVQCCWWIK6OB6P4AW4M37DXZK" lateinit var sep10Client: Sep10Client 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 deleted file mode 100644 index 4b42896a41..0000000000 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/SystemUtil.kt +++ /dev/null @@ -1,24 +0,0 @@ -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/platform/src/main/java/org/stellar/anchor/platform/callback/RestCustomerIntegration.java b/platform/src/main/java/org/stellar/anchor/platform/callback/RestCustomerIntegration.java index 0e1a0eb28f..477ccd8364 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/callback/RestCustomerIntegration.java +++ b/platform/src/main/java/org/stellar/anchor/platform/callback/RestCustomerIntegration.java @@ -15,7 +15,6 @@ import okhttp3.HttpUrl.Builder; import org.springframework.http.HttpStatus; import org.stellar.anchor.api.callback.*; -import org.stellar.anchor.api.callback.CustomerIntegration; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.ServerErrorException; import org.stellar.anchor.api.sep.sep12.Sep12GetCustomerRequest; diff --git a/platform/src/main/java/org/stellar/anchor/platform/condition/AbstractOnSepsEnabled.java b/platform/src/main/java/org/stellar/anchor/platform/condition/AbstractOnSepsEnabled.java index 7935e8d43c..157d763e14 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/condition/AbstractOnSepsEnabled.java +++ b/platform/src/main/java/org/stellar/anchor/platform/condition/AbstractOnSepsEnabled.java @@ -1,5 +1,6 @@ package org.stellar.anchor.platform.condition; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Objects; @@ -34,9 +35,7 @@ public ConditionOutcome getMatchOutcome( LinkedList seps = new LinkedList<>(); for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { - for (String sep : (String[]) annotationAttributes.get("seps")) { - seps.add(sep); - } + seps.addAll(Arrays.asList((String[]) annotationAttributes.get("seps"))); } return getMatchOutcome(seps, context, className); 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 index e4c6b7ce90..bd25ebce9c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java @@ -27,13 +27,14 @@ public abstract class ConfigManager implements ApplicationContextInitializer, HealthCheckable { static final String STELLAR_ANCHOR_CONFIG = "STELLAR_ANCHOR_CONFIG"; - static final ConfigManager configManager = new DefaultConfigManager(); + static ConfigManager configManager; ConfigMap configMap; ConfigManager() {} public static ConfigManager getInstance() { + if (configManager == null) configManager = new DefaultConfigManager(); return configManager; } @@ -76,7 +77,6 @@ ConfigMap processConfigurations(ConfigurableApplicationContext applicationContex 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 @@ -90,17 +90,17 @@ ConfigMap processConfigurations(ConfigurableApplicationContext applicationContex if (configFileResource != null) { infoF("reading configuration file from {}", configFileResource.getURL()); ConfigMap yamlConfig = loadConfig(configFileResource, FILE); - config.merge(updateToLatestConfig(latestConfig, yamlConfig)); + latestConfig.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)); + latestConfig.merge(updateToLatestConfig(latestConfig, envConfig)); } - return config; + return latestConfig; } ConfigMap updateToLatestConfig(ConfigMap latestConfig, ConfigMap config) @@ -184,7 +184,7 @@ class DefaultConfigManager extends ConfigManager { @SneakyThrows @Override - public void initialize(ConfigurableApplicationContext applicationContext) { + 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); 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 83ba9d3fc8..1e7a511294 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 @@ -7,7 +7,7 @@ public class ConfigMap { int version; - Map data; + final Map data; // ConfigMap keys will be in normalized form (dot separated hierarchy) public ConfigMap() { @@ -72,14 +72,23 @@ public void merge(ConfigMap config) { } } - public boolean sameAs(ConfigMap anotherMap) { + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ConfigMap)) { + return false; + } + + ConfigMap anotherMap = (ConfigMap) obj; + if (data.size() != anotherMap.data.size()) { + return false; + } + for (String key : names()) { if (!anotherMap.getString(key).equals(getString(key))) { return false; } } - - return data.size() == anotherMap.data.size(); + return true; } public enum ConfigSource { 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 05a5540498..645c85a360 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 @@ -50,7 +50,7 @@ * */ public class DataConfigAdapter extends SpringConfigAdapter { - List allFields = + final List allFields = Arrays.asList( "spring.datasource.driver-class-name", "spring.datasource.name", @@ -152,7 +152,7 @@ private void checkIfAllFieldsAreSet() { }); } - void setSpringDataDefaults() throws InvalidConfigException { + void setSpringDataDefaults() { set("spring.datasource.generate-unique-name", "false"); set("spring.datasource.hikari.connection-timeout ", 20000); // in ms 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 380c3df868..096a116bc2 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 @@ -13,7 +13,7 @@ public class SecretManager implements ApplicationContextInitializer { - List secretVars = + final List secretVars = Arrays.asList( "secret.sep10.jwt_secret", "secret.sep10.signing_seed", @@ -22,9 +22,9 @@ public class SecretManager "secret.data.username", "secret.data.password"); - Properties props = new Properties(); + final Properties props = new Properties(); - static SecretManager secretManager = new SecretManager(); + static final SecretManager secretManager = new SecretManager(); private SecretManager() {} 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 index 715fa92f6e..37bd31ad38 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringConfigAdapter.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/SpringConfigAdapter.java @@ -15,7 +15,7 @@ *

The SpringConfigAdapter is NOT thread-safe. */ public abstract class SpringConfigAdapter { - Properties props = new Properties(); + final Properties props = new Properties(); protected void set(String name, boolean value) { props.put(name, value); @@ -25,22 +25,14 @@ protected void set(String name, int value) { props.put(name, value); } - protected void set(String name, String value) throws InvalidConfigException { + protected void set(String name, String value) { 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; + throw new InvalidConfigException(String.format("config[%s] is not defined", from)); } set(to, value); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java b/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java index bf28bc962f..d36fda6e5f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/HealthController.java @@ -12,7 +12,7 @@ @CrossOrigin(origins = "*") @RequestMapping(value = "/health") public class HealthController { - HealthCheckService healthCheckService; + final HealthCheckService healthCheckService; HealthController(HealthCheckService healthCheckService) { this.healthCheckService = healthCheckService; 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 467e8899b1..e1d21b4294 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,5 @@ 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; @@ -29,7 +28,7 @@ public Sep1Controller(Sep1Config sep1Config, Sep1Service sep1Service) { @RequestMapping( value = "/.well-known/stellar.toml", method = {RequestMethod.GET, RequestMethod.OPTIONS}) - public ResponseEntity getToml() throws IOException, SepNotFoundException { + public ResponseEntity getToml() throws SepNotFoundException { if (!sep1Config.isEnabled()) { throw new SepNotFoundException("Not Found"); } 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 8b9912e5a3..c627365ad8 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 lombok.Data; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -59,9 +60,8 @@ public Sep31PostTransactionResponse postTransaction( value = "/transactions/{id}", method = {RequestMethod.GET}) public Sep31GetTransactionResponse getTransaction( - HttpServletRequest servletRequest, @PathVariable(name = "id") String txnId) + HttpServletRequest ignoredServletRequest, @PathVariable(name = "id") String txnId) throws AnchorException { - JwtToken jwtToken = getSep10Token(servletRequest); debugF("GET /transactions id={}", txnId); return sep31Service.getTransaction(txnId); } @@ -73,11 +73,10 @@ public Sep31GetTransactionResponse getTransaction( consumes = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.PATCH}) public Sep31GetTransactionResponse patchTransaction( - HttpServletRequest servletRequest, + HttpServletRequest ignoredServletRequest, @PathVariable(name = "id") String txnId, @RequestBody Sep31PatchTransactionRequest request) throws AnchorException { - JwtToken jwtToken = getSep10Token(servletRequest); debugF("PATCH /transactions id={} request={}", txnId, request); return sep31Service.patchTransaction(request); } @@ -110,9 +109,10 @@ public static Sep31MissingFieldResponse from(Sep31MissingFieldException exceptio } } - private class Sep31CustomerInfoNeededResponse { - String error; - String type; + @Data + static class Sep31CustomerInfoNeededResponse { + final String error; + final String type; public Sep31CustomerInfoNeededResponse(String type) { this.error = "customer_info_needed"; 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 5a8f372882..e817b73065 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 @@ -23,9 +23,7 @@ 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); - } + payments.addAll(refundPayments); return payments; } 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 a483266ebe..6a7c45868b 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 @@ -5,7 +5,6 @@ import java.util.List; import java.util.Optional; 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; @@ -35,19 +34,18 @@ public RefundPayment newRefundPayment() { } @Override - public Sep31Transaction findByTransactionId(@NonNull String transactionId) - throws AnchorException { + public Sep31Transaction findByTransactionId(@NonNull String transactionId) { return transactionRepo.findById(transactionId).orElse(null); } @Override public List findByTransactionIds( - @NonNull Collection transactionId) throws AnchorException { + @NonNull Collection transactionId) { return transactionRepo.findByIds(transactionId); } @Override - public Sep31Transaction findByStellarMemo(@NonNull String memo) throws AnchorException { + public Sep31Transaction findByStellarMemo(@NonNull String memo) { return transactionRepo.findByStellarMemo(memo).orElse(null); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/databaseintegration/IAMAuthDataSource.java b/platform/src/main/java/org/stellar/anchor/platform/databaseintegration/IAMAuthDataSource.java index 10aea94195..2569888cd9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/databaseintegration/IAMAuthDataSource.java +++ b/platform/src/main/java/org/stellar/anchor/platform/databaseintegration/IAMAuthDataSource.java @@ -9,6 +9,7 @@ // Used by the data-spring-jdbc-aws-aurora-postgres settings to allow authentication to the db via // AWS IAM +@SuppressWarnings("unused") public class IAMAuthDataSource extends HikariDataSource { @Override public String getPassword() { 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 6e65a0e229..4969e5884b 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 @@ -39,7 +39,7 @@ protected void createPublisher(Properties props) { } @Override - public void publish(String queue, AnchorEvent event) throws EventPublishException { + public void publish(String queue, AnchorEvent event) { try { ProducerRecord record = new ProducerRecord<>(queue, event); record.headers().add(new RecordHeader("type", event.getType().getBytes())); diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java index b705a5f0f4..8d77c74721 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/MemoryStellarPaymentStreamerCursorStore.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; +@SuppressWarnings("unused") public class MemoryStellarPaymentStreamerCursorStore implements StellarPaymentStreamerCursorStore { Map mapTokens = new HashMap<>(); String cursor = null; diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountStore.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountStore.java index afedcd2614..56a31fc501 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountStore.java @@ -8,7 +8,7 @@ import org.stellar.anchor.util.Log; public class PaymentObservingAccountStore { - PaymentObservingAccountRepo repo; + final PaymentObservingAccountRepo repo; public PaymentObservingAccountStore(PaymentObservingAccountRepo repo) { this.repo = repo; diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManager.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManager.java index 88f1f2ddc1..f6599c2887 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManager.java @@ -15,12 +15,11 @@ 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; + final Map allAccounts; private final PaymentObservingAccountStore store; public PaymentObservingAccountsManager(PaymentObservingAccountStore store) { @@ -29,7 +28,7 @@ public PaymentObservingAccountsManager(PaymentObservingAccountStore store) { } @PostConstruct - public void initialize() throws ValueValidationException { + public void initialize() { List accounts = store.list(); for (PaymentObservingAccount account : accounts) { ObservingAccount oa = @@ -68,7 +67,7 @@ public void evictAndPersist() { * @param account The account being observed. * @param type true The account type. */ - public void upsert(String account, AccountType type) throws ValueValidationException { + public void upsert(String account, AccountType type) { if (account != null && type != null) { upsert(new ObservingAccount(account, Instant.now(), type)); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java index 579749b686..9055af4d20 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java @@ -53,23 +53,23 @@ public class StellarPaymentObserver implements HealthCheckable { private static final int MIN_RESULTS = 1; final Server server; - private StellarPaymentObserverConfig config; + private final StellarPaymentObserverConfig config; final List paymentListeners; final StellarPaymentStreamerCursorStore paymentStreamerCursorStore; final Map, String> mapStreamToAccount = new HashMap<>(); final PaymentObservingAccountsManager paymentObservingAccountsManager; SSEStream stream; - ExponentialBackoffTimer publishingBackoffTimer; - ExponentialBackoffTimer streamBackoffTimer; + final ExponentialBackoffTimer publishingBackoffTimer; + final ExponentialBackoffTimer streamBackoffTimer; int silenceTimeoutCount = 0; ObserverStatus status = RUNNING; Instant lastActivityTime; - ScheduledExecutorService silenceWatcher = Executors.newSingleThreadScheduledExecutor(); - ScheduledExecutorService statusWatcher = Executors.newSingleThreadScheduledExecutor(); + final ScheduledExecutorService silenceWatcher = Executors.newSingleThreadScheduledExecutor(); + final ScheduledExecutorService statusWatcher = Executors.newSingleThreadScheduledExecutor(); public StellarPaymentObserver( String horizonServer, 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 ec329be10d..0a63dbeaa5 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 @@ -13,7 +13,7 @@ @Service @DependsOn("configManager") public class HealthCheckService { - HealthCheckProcessor processor; + final HealthCheckProcessor processor; public HealthCheckService(List checkables) { checkables.forEach( 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 44b56f84e1..37fe79856a 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 @@ -14,15 +14,15 @@ public class MetricEmitterService { private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); - private MetricConfig metricConfig; + private final MetricConfig metricConfig; private final JdbcSep31TransactionRepo sep31TransactionStore; - AtomicInteger pendingStellarTxns = new AtomicInteger(0); - AtomicInteger pendingCustomerInfoUpdateTxns = new AtomicInteger(0); - AtomicInteger pendingSenderTxns = new AtomicInteger(0); - AtomicInteger pendingReceiverTxns = new AtomicInteger(0); - AtomicInteger pendingExternalTxns = new AtomicInteger(0); - AtomicInteger completedTxns = new AtomicInteger(0); - AtomicInteger errorTxns = new AtomicInteger(0); + final AtomicInteger pendingStellarTxns = new AtomicInteger(0); + final AtomicInteger pendingCustomerInfoUpdateTxns = new AtomicInteger(0); + final AtomicInteger pendingSenderTxns = new AtomicInteger(0); + final AtomicInteger pendingReceiverTxns = new AtomicInteger(0); + final AtomicInteger pendingExternalTxns = new AtomicInteger(0); + final AtomicInteger completedTxns = new AtomicInteger(0); + final AtomicInteger errorTxns = new AtomicInteger(0); public MetricEmitterService( MetricConfig metricConfig, JdbcSep31TransactionRepo sep31TransactionRepo) { 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 3caa0da961..2a7422efb4 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 @@ -185,36 +185,34 @@ TransactionEvent receivedPaymentToEvent( TransactionEvent.StatusChange statusChange, String message, StellarTransaction newStellarTransaction) { - TransactionEvent event = - TransactionEvent.builder() - .eventId(UUID.randomUUID().toString()) - .type(TransactionEvent.Type.TRANSACTION_STATUS_CHANGED) - .id(txn.getId()) - .sep(TransactionEvent.Sep.SEP_31) - .kind(TransactionEvent.Kind.RECEIVE) - .status(statusChange.getTo()) - .statusChange(statusChange) - .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(txn.getUpdatedAt()) - .completedAt(null) - .transferReceivedAt(txn.getTransferReceivedAt()) - .message(message) - .refunds(null) - .stellarTransactions(List.of(newStellarTransaction)) - .externalTransactionId(payment.getExternalTransactionId()) - .custodialTransactionId(null) - .sourceAccount(payment.getFrom()) - .destinationAccount(payment.getTo()) - .customers(txn.getCustomers()) - .creator(txn.getCreator()) - .build(); - return event; + return TransactionEvent.builder() + .eventId(UUID.randomUUID().toString()) + .type(TransactionEvent.Type.TRANSACTION_STATUS_CHANGED) + .id(txn.getId()) + .sep(TransactionEvent.Sep.SEP_31) + .kind(TransactionEvent.Kind.RECEIVE) + .status(statusChange.getTo()) + .statusChange(statusChange) + .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(txn.getUpdatedAt()) + .completedAt(null) + .transferReceivedAt(txn.getTransferReceivedAt()) + .message(message) + .refunds(null) + .stellarTransactions(List.of(newStellarTransaction)) + .externalTransactionId(payment.getExternalTransactionId()) + .custodialTransactionId(null) + .sourceAccount(payment.getFrom()) + .destinationAccount(payment.getTo()) + .customers(txn.getCustomers()) + .creator(txn.getCreator()) + .build(); } Instant parsePaymentTime(String paymentTimeStr) { 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 index 0522d9f767..fd975c380d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java @@ -25,8 +25,7 @@ public PropertyAssetsService(AssetsConfig assetsConfig) throws InvalidConfigExce if (assets == null || assets.getAssets() == null || assets.getAssets().size() == 0) { error("Invalid asset defined. assets JSON=", assetsJson); throw new InvalidConfigException( - String.format( - "Invalid assets defined in configuration. Please check the logs for details.")); + "Invalid assets defined in configuration. Please check the logs for details."); } break; case YAML: 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 8a7ee8ac67..d3f01f2ae6 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 @@ -36,7 +36,7 @@ public class TransactionService { private final Sep38QuoteStore quoteStore; private final Sep31TransactionStore txnStore; private final List assets; - static List validStatuses = + static final List validStatuses = List.of( PENDING_STELLAR.getName(), PENDING_CUSTOMER_INFO_UPDATE.getName(), diff --git a/platform/src/main/java/org/stellar/anchor/platform/sqlite/SQLiteDialect.java b/platform/src/main/java/org/stellar/anchor/platform/sqlite/SQLiteDialect.java index a4530c9fb4..fe20ddaae9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/sqlite/SQLiteDialect.java +++ b/platform/src/main/java/org/stellar/anchor/platform/sqlite/SQLiteDialect.java @@ -7,6 +7,7 @@ import org.hibernate.dialect.function.VarArgsSQLFunction; import org.hibernate.type.StringType; +@SuppressWarnings({"SameReturnValue", "unused"}) public class SQLiteDialect extends Dialect { public SQLiteDialect() { registerColumnType(Types.BIT, "integer"); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/EmptyAssetService.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/EmptyAssetService.kt deleted file mode 100644 index a967e293b1..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/EmptyAssetService.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.stellar.anchor.platform - -import org.stellar.anchor.api.sep.AssetInfo -import org.stellar.anchor.asset.AssetService - -class EmptyAssetService : AssetService { - override fun listAllAssets(): MutableList { - TODO("Not yet implemented") - } - - override fun getAsset(code: String?, issuer: String?): AssetInfo { - TODO("Not yet implemented") - } - - override fun getAsset(code: String?): AssetInfo { - TODO("Not yet implemented") - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt index 2b896f9482..7b48407ee2 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.platform import io.mockk.* 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 index cb6972895d..2dbc18774a 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/configurator/ConfigManagerTest.kt @@ -57,7 +57,7 @@ class ConfigManagerTest { ) val gotConfig = configManager.processConfigurations(null) - assertTrue(gotConfig.sameAs(wantedConfig)) + assertTrue(gotConfig.equals(wantedConfig)) } @Test @@ -93,6 +93,6 @@ class ConfigManagerTest { ) val gotConfig = configManager.processConfigurations(null) - assertTrue(gotConfig.sameAs(wantedConfig)) + assertTrue(gotConfig.equals(wantedConfig)) } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt index bd1e9bc364..a4fc4e796c 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/HealthControllerTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.platform.controller import io.mockk.MockKAnnotations diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/Sep38ControllerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/controller/Sep38ControllerTest.kt deleted file mode 100644 index dfd105b0d6..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/controller/Sep38ControllerTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.stellar.anchor.platform.controller - -import org.mockito.BDDMockito.* -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest - -@WebMvcTest(Sep38Controller::class) -class Sep38ControllerTest { - // Temporarily comment this out to merge with the new integration test module. - - // @Autowired private lateinit var mockMvc: MockMvc - // - // @MockBean private lateinit var sep38Service: Sep38Service - // - // @BeforeEach - // fun setUp() { - // // we set the result of the mocked service - // given(sep38Service.info) - // .willReturn(InfoResponse(ResourceJsonAssetService("test_assets.json").listAllAssets())) - // } - // - // @Test - // @Throws(Exception::class) - // fun getInfo() { - // val wantResponse = FileUtil.getResourceFileAsString("test_sep38_get_info.json") - // - // mockMvc - // .perform(MockMvcRequestBuilders.get("/sep38/info").accept(MediaType.APPLICATION_JSON)) - // .andExpect(MockMvcResultMatchers.status().isOk) - // .andExpect(MockMvcResultMatchers.content().json(wantResponse)) - // } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManagerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManagerTest.kt index 925415a21f..e6b8f32a0d 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManagerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/PaymentObservingAccountsManagerTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.platform.observer.stellar import io.mockk.* diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt index 5dcd021dac..a40bb34072 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.platform.observer.stellar import com.google.gson.reflect.TypeToken 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 45278c14da..f2733cf721 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 @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.platform.service import io.mockk.* 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 831f513804..226e04e048 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 @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.platform.service import io.mockk.MockKAnnotations 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 cdba8e2d6c..f178f42b34 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 @@ -29,6 +29,7 @@ import org.stellar.anchor.sep31.* import org.stellar.anchor.sep38.Sep38Quote import org.stellar.anchor.sep38.Sep38QuoteStore +@Suppress("unused") class TransactionServiceTest { companion object { private const val fiatUSD = "iso4217:USD" 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 c02a7597c0..2427d6c59c 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package org.stellar.anchor.sep31 import com.google.gson.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 86e64ef638..f391c58864 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 @@ -59,7 +59,6 @@ static void startAnchorReferenceServer() { if (strPort != null) { port = Integer.parseInt(strPort); } - String contextPath = System.getProperty("ANCHOR_REFERENCE_CONTEXT_PATH"); AnchorReferenceServer.start(port, "/"); } From 824c2d26c893e3131d71cecf63a1ea86685e7299 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 22 Nov 2022 16:39:01 -0800 Subject: [PATCH 0057/1439] Fix root compose and adjust configs (#672) --- ...eference-server-docker-compose-config.yaml | 6 +++--- docker-compose.yaml | 10 ++++++++-- .../anchor-docker-compose-config.yaml | 20 +++++++++---------- 3 files changed, 21 insertions(+), 15 deletions(-) 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 7691db4847..d4d0d5b773 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 @@ -8,8 +8,8 @@ server: # anchor.settings: version: 0.0.1 - platformApiEndpoint: http://host.docker.internal:8080 - hostUrl: http://host.docker.internal:8081 + platformApiEndpoint: http://localhost:8080 + hostUrl: http://localhost:8081 integration-auth: authType: JWT_TOKEN @@ -25,7 +25,7 @@ event: listenerType: kafka kafka.listener: - bootstrapServer: host.docker.internal:29092 + bootstrapServer: kafka:29092 useSingleQueue: false eventTypeToQueue: all: ap_event_single_queue diff --git a/docker-compose.yaml b/docker-compose.yaml index 0a93677126..64d688a365 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,6 +9,12 @@ services: - STELLAR_ANCHOR_CONFIG=file:/config/anchor-docker-compose-config.yaml - REFERENCE_SERVER_CONFIG_ENV=file:/reference-config/anchor-reference-server-docker-compose-config.yaml - LOG_APPENDER=console_appender + - SECRET_DATA_USERNAME=postgres + - SECRET_DATA_PASSWORD=password + - SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret + - SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret + - SECRET_SEP10_JWT_SECRET=secret + - SECRET_SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X depends_on: - db - kafka @@ -46,7 +52,7 @@ services: environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://host.docker.internal:29092 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 \ No newline at end of file + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 5766de8e98..50ce183c1a 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -5,7 +5,7 @@ logging: stellar_level: DEBUG callback_api: - base_url: http://host.docker.internal:8081 + base_url: http://localhost:8081 auth: type: JWT_TOKEN expiration_milliseconds: 30000 @@ -20,7 +20,7 @@ events: publisher: type: kafka kafka: - bootstrap_server: host.docker.internal:29092 + bootstrap_server: kafka:29092 sep1: enabled: true @@ -32,11 +32,11 @@ sep1: 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" + 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" @@ -56,14 +56,14 @@ sep1: sep10: enabled: true - home_domain: host.docker.internal:8080 + home_domain: localhost:8080 sep12: enabled: true sep24: enabled: true - interactiveUrl: http://host.docker.internal:8081/sep24/interactive + interactiveUrl: http://localhost:8081/sep24/interactive sep31: enabled: true @@ -308,7 +308,7 @@ data: ## @type: string ## Location of the database # - url: jdbc:postgresql://host.docker.internal:5440/ + url: jdbc:postgresql://db:5432/ ## @param: initial_connection_pool_size ## @type: integer From 40b458ebd294cddebb7d996e94cb915ceba1efe8 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 2 Dec 2022 07:16:09 +0800 Subject: [PATCH 0058/1439] temporarily disable the e2e tests in the GH workflow (#676) --- .github/workflows/end_to_end_tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index c446aa75c0..067970f656 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -6,10 +6,10 @@ on: push: branches: - main - - develop - - 'release/**' - - 'releases/**' - - 'hotfix/**' +# - develop +# - 'release/**' +# - 'releases/**' +# - 'hotfix/**' jobs: end_to_end_tests: From 760eb253ebf5a58d340876b86a77dd5192aa1dda Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 7 Dec 2022 21:17:03 +0800 Subject: [PATCH 0059/1439] ANCHOR-58: Review and refactor configuration validators (#671) * Added CallbackApiConfig validator and tests * Added PaymentObserverConfig validation tests * Add EventConfig validation tests * Clean up PropertiesSep12Config and remove the validator interface. * Remove validators from Sep12, 31, 38 config because they are unnecessary * Add Sep1 validation tests * Added server/port validation to NetUtil and refactor tests * Added SEP10 validation and tests * Add Sep24Config validation and tests * Added AppConfig validation and tests * Refactored EventConfig validation and tests --- .../org/stellar/anchor/auth/AuthInfo.java | 4 + .../stellar/anchor/config/Sep31Config.java | 4 - .../org/stellar/anchor/util/ListHelper.java | 10 + .../java/org/stellar/anchor/util/NetUtil.java | 41 ++++ .../stellar/anchor/sep10/Sep10ServiceTest.kt | 23 +-- .../org/stellar/anchor/util/NetUtilTest.kt | 105 +++++++--- gradle/libs.versions.toml | 3 +- .../platform/AnchorPlatformIntegrationTest.kt | 1 - .../integration-test.anchor-config.yaml | 3 + platform/build.gradle.kts | 5 +- .../platform/ConfigManagementBeans.java | 9 +- .../platform/config/CallbackApiConfig.java | 30 ++- .../anchor/platform/config/KafkaConfig.java | 4 + .../anchor/platform/config/MskConfig.java | 13 ++ .../config/PaymentObserverConfig.java | 10 +- .../platform/config/PropertyAppConfig.java | 61 +++++- .../platform/config/PropertyEventConfig.java | 161 ++++++++++++---- .../platform/config/PropertyMetricConfig.java | 2 +- .../platform/config/PropertySep10Config.java | 141 ++++++++++++-- .../platform/config/PropertySep12Config.java | 15 +- .../platform/config/PropertySep1Config.java | 110 +++++------ .../platform/config/PropertySep24Config.java | 25 ++- .../platform/config/PropertySep31Config.java | 34 +--- .../platform/config/PropertySep38Config.java | 15 +- .../anchor/platform/config/SqsConfig.java | 4 + .../platform/configurator/ConfigManager.java | 4 + .../config/anchor-config-default-values.yaml | 8 +- .../anchor/platform/config/AppConfigTest.kt | 126 +++++++++--- .../platform/config/CallbackApiConfigTest.kt | 75 ++++++++ .../anchor/platform/config/EventConfigTest.kt | 180 ++++++++++++------ .../config/PaymentObserverConfigTest.kt | 85 +++++++++ .../anchor/platform/config/Sep10ConfigTest.kt | 167 ++++++++++++++++ .../anchor/platform/config/Sep12ConfigTest.kt | 20 -- .../anchor/platform/config/Sep1ConfigTest.kt | 34 ++-- .../anchor/platform/config/Sep24ConfigTest.kt | 49 +++++ .../anchor/platform/config/Sep31ConfigTest.kt | 22 --- .../anchor/platform/config/Sep38ConfigTest.kt | 18 -- .../platform/config/ValidationHelper.kt | 10 + 38 files changed, 1224 insertions(+), 407 deletions(-) create mode 100644 core/src/main/java/org/stellar/anchor/util/ListHelper.java create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/CallbackApiConfigTest.kt create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/PaymentObserverConfigTest.kt create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep10ConfigTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep12ConfigTest.kt create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep31ConfigTest.kt delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep38ConfigTest.kt create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/ValidationHelper.kt diff --git a/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java b/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java index 25e9ca63de..114c70eba7 100644 --- a/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java +++ b/core/src/main/java/org/stellar/anchor/auth/AuthInfo.java @@ -1,8 +1,12 @@ package org.stellar.anchor.auth; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor public class AuthInfo { AuthType type; String secret; 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 596ef25c7a..2222491002 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep31Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep31Config.java @@ -3,10 +3,6 @@ public interface Sep31Config { boolean isEnabled(); - String getFeeIntegrationEndPoint(); - - String getUniqueAddressIntegrationEndPoint(); - PaymentType getPaymentType(); DepositInfoGeneratorType getDepositInfoGeneratorType(); diff --git a/core/src/main/java/org/stellar/anchor/util/ListHelper.java b/core/src/main/java/org/stellar/anchor/util/ListHelper.java new file mode 100644 index 0000000000..240fd59f62 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/util/ListHelper.java @@ -0,0 +1,10 @@ +package org.stellar.anchor.util; + +import java.util.Collection; + +public class ListHelper { + public static boolean isEmpty(Collection list) { + if (list == null) return true; + return list.isEmpty(); + } +} 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 4ae44efe72..1f1058aa9c 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,10 @@ package org.stellar.anchor.util; +import static org.stellar.anchor.util.StringHelper.isEmpty; + +import io.jsonwebtoken.lang.Strings; import java.io.IOException; +import java.net.InetAddress; import java.net.URL; import java.util.Objects; import okhttp3.Call; @@ -18,6 +22,9 @@ public static String fetch(String url) throws IOException { @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isUrlValid(String url) { + if (isEmpty(url)) { + return false; + } /* Try creating a valid URL */ try { new URL(url).toURI(); @@ -27,6 +34,40 @@ public static boolean isUrlValid(String url) { } } + public static boolean isServerPortValid(String serverPort) { + if (isEmpty(serverPort)) return false; + String[] tokens = Strings.split(serverPort, ":"); + if (tokens == null) { + return isHostnameValid(serverPort); + } + switch (tokens.length) { + case 2: + String strPort = tokens[1]; + try { + int port = Integer.parseInt(strPort); + if (port > 65535 || port < 0) { + return false; + } + } catch (NumberFormatException ex) { + return false; + } + case 1: + return isHostnameValid(tokens[0]); + case 0: + default: + return false; + } + } + + static boolean isHostnameValid(String hostname) { + try { + InetAddress.getAllByName(hostname); + return true; + } catch (Exception exc) { + return false; + } + } + static Call getCall(Request request) { return OkHttpUtil.buildClient().newCall(request); } 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 cfd71e5aaa..265fd0ac92 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -7,12 +7,8 @@ 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 @@ -113,7 +109,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`() + this.httpClient = `create httpClient`() } @AfterEach @@ -122,26 +118,11 @@ 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()) + private fun `create httpClient`(): OkHttpClient { 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() } 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 ced3aaed85..1d9762b638 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/NetUtilTest.kt @@ -10,6 +10,10 @@ 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.params.ParameterizedTest +import org.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource +import org.stellar.anchor.util.NetUtil.isServerPortValid import org.stellar.anchor.util.NetUtil.isUrlValid internal class NetUtilTest { @@ -64,31 +68,84 @@ internal class NetUtilTest { 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")) + @ParameterizedTest + @ValueSource( + strings = + [ + "http://www.stellar.org", + "http://www.stellar.org:8000", + "https://www.stellar.org/", + "https://www.stellar.org/.well-known/stellar.toml", + "https://www.stellar.org/sep1?q=123&p=false", + "https://www.stellar.org/sep1?q=&p=false", + "https://www.stellar.org/a/b/c", + "https://www.stellar.org/a/", + "http://192.168.100.1", + "http://192.168.100.1/a/", + "ftp://ftp.stellar.org", + "ftp://ftp.stellar.org/a/b/c", + "ftp://ftp.stellar.org/a/", + "file:///home/johndoe/a.toml" + ] + ) + fun `test valid urls with isUrlValid()`(testValue: String?) { + assertTrue(isUrlValid(testValue)) } - @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")) + @ParameterizedTest + @NullSource + @ValueSource( + strings = + [ + "http:// www.stellar.org", + "https:// www.stellar.org", + "https:// www.stellar.org/a /", + "https:// www.stellar.org/a?p=123&q= false", + "https://192.168.100 .1", + "abc://www.stellar.org", + "http:// www.stellar.org", + "http://www.stellar.org:-100", + "http://www.stellar.org:abc", + "" + ] + ) + fun `test bad urls with isUrlValid()`(testValue: String?) { + assertFalse(isUrlValid(testValue)) + } + + @ParameterizedTest + @ValueSource( + strings = + [ + "www.stellar.org", + "localhost", + "127.0.0.1", + "8.8.8.8", + "stellar.org", + "localhost", + "localhost:8080", + "127.0.0.1:8080" + ] + ) + fun `test valid server port with isServerPortValid`(testValue: String) { + assertTrue(isServerPortValid(testValue)) + } + + @ParameterizedTest + @NullSource + @ValueSource( + strings = + [ + "www1.stellar.org", + "www .stellar.org", + "localhost:88080", + "localhost:-10", + "localhos", + "I am not a good host name", + "" + ] + ) + fun `test bad server port with isServerPortValid`(testValue: String?) { + assertFalse(isServerPortValid(testValue)) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73e13d30eb..4984f74a85 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] # Library versions +abdera = "1.1.3" apache-commons-lang3 = "3.12.0" aws-iam-auth = "1.1.4" aws-rds = "1.12.248" @@ -45,13 +46,13 @@ sqlite-jdbc = "3.34.0" slf4j = "1.7.35" toml4j = "0.7.2" - # Plugin versions spotless = "6.9.1" spring-boot = "2.6.8" spring-dependency-management = "1.1.0" [libraries] +abdera = { module = "org.apache.abdera:abdera-i18n", version.ref = "abdera" } 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" } 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 564869f9f9..42dd586f61 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 @@ -347,7 +347,6 @@ class AnchorPlatformIntegrationTest { assertEquals(true, sep10Config.enabled) assertEquals("localhost:8080", sep10Config.homeDomain) assertEquals(false, sep10Config.isClientAttributionRequired) - assertEquals(listOf("lobstr.co", "preview.lobstr.co"), sep10Config.clientAttributionAllowList) assertEquals(900, sep10Config.authTimeout) assertEquals(86400, sep10Config.jwtTimeout) } 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 51913afa7c..9a3cc6bbb7 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -51,6 +51,9 @@ sep31: sep38: enabled: true +events: + enabled: true + assets: type: json value: | diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index c6d00bee5b..7828dddf0b 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -16,7 +16,9 @@ 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( + 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") @@ -24,6 +26,7 @@ dependencies { implementation(libs.spring.aws.messaging) implementation(libs.spring.kafka) + implementation(libs.abdera) implementation(libs.aws.rds) implementation(libs.aws.iam.auth) implementation(libs.commons.cli) 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 51fb501395..5820d2e205 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java @@ -4,7 +4,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.config.*; import org.stellar.anchor.platform.config.*; import org.stellar.anchor.platform.configurator.ConfigManager; @@ -60,8 +59,8 @@ Sep1Config sep1Config() { @Bean @ConfigurationProperties(prefix = "sep10") - Sep10Config sep10Config(SecretConfig secretConfig, JwtService jwtService) { - return new PropertySep10Config(secretConfig, jwtService); + Sep10Config sep10Config(SecretConfig secretConfig) { + return new PropertySep10Config(secretConfig); } @Bean @@ -78,8 +77,8 @@ Sep24Config sep24Config() { @Bean @ConfigurationProperties(prefix = "sep31") - Sep31Config sep31Config(CallbackApiConfig callbackApiConfig) { - return new PropertySep31Config(callbackApiConfig); + Sep31Config sep31Config() { + return new PropertySep31Config(); } @Bean 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 index 318fea84e8..7f6a2c4a37 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java @@ -1,5 +1,7 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.StringHelper.isEmpty; + import java.util.List; import lombok.Data; import org.jetbrains.annotations.NotNull; @@ -7,6 +9,7 @@ import org.springframework.validation.Validator; import org.stellar.anchor.auth.AuthInfo; import org.stellar.anchor.auth.AuthType; +import org.stellar.anchor.util.NetUtil; @Data public class CallbackApiConfig implements Validator { @@ -34,12 +37,31 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { CallbackApiConfig config = (CallbackApiConfig) target; + validateBaseUrl(config, errors); + validateAuth(config, errors); + } + + void validateBaseUrl(CallbackApiConfig config, Errors errors) { + if (isEmpty(config.baseUrl)) { + errors.rejectValue( + "baseUrl", + "empty-callback-api-base-url", + "The callback_api.base_url cannot be empty and must be defined"); + } + if (!NetUtil.isUrlValid(config.baseUrl)) { + errors.rejectValue( + "baseUrl", + "mal-formatted-callback-api-base-url", + "The callback_api.base_url is not a valid URL"); + } + } + void validateAuth(CallbackApiConfig config, Errors errors) { if (List.of(AuthType.API_KEY, AuthType.JWT_TOKEN).contains(config.getAuth().getType())) { - if (config.getAuth().getSecret() == null) { + if (isEmpty(config.getAuth().getSecret())) { errors.rejectValue( - "platformToAnchorSecret", - "empty-platformToAnchorSecret", + "auth", + "empty-secret-callback-api-secret", "Please set environment variable [SECRET.CALLBACK_API.AUTH_SECRET] for auth type:" + config.getAuth().getType()); } @@ -47,7 +69,7 @@ public void validate(@NotNull Object target, @NotNull Errors errors) { if (AuthType.JWT_TOKEN == config.getAuth().getType() && (Long.parseLong(config.getAuth().getExpirationMilliseconds()) < MIN_EXPIRATION)) { errors.rejectValue( - "expirationMilliseconds", + "auth", "min-expirationMilliseconds", "expirationMilliseconds cannot be lower than " + MIN_EXPIRATION); } 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 76e1e84507..0e3553ccc3 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 @@ -1,8 +1,12 @@ package org.stellar.anchor.platform.config; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor public class KafkaConfig { /** * A comma-separated list of host:port pairs that are the addresses of one or more brokers in a 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 index 0be307bf80..364939ef6b 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/MskConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/MskConfig.java @@ -2,9 +2,22 @@ import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; @Data @EqualsAndHashCode(callSuper = true) +@NoArgsConstructor public class MskConfig extends KafkaConfig { boolean useIAM; + + MskConfig( + boolean useIAM, + String bootstrapServer, + String clientId, + int retires, + int lingerMs, + int batchSize) { + super(bootstrapServer, clientId, retires, lingerMs, batchSize); + this.useIAM = useIAM; + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java index 46875b89ec..461896daca 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PaymentObserverConfig.java @@ -38,12 +38,20 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { PaymentObserverConfig config = (PaymentObserverConfig) target; + validateStellar(config, errors); + validateConfig(config, errors); + } + + void validateConfig(PaymentObserverConfig config, Errors errors) { ValidationUtils.rejectIfEmptyOrWhitespace( errors, "payment_observer.type", "empty-payment-observer-type", "payment_observer.type must not be empty"); - if (config.type.equals(PaymentObserverType.STELLAR)) { + } + + void validateStellar(PaymentObserverConfig config, Errors errors) { + if (PaymentObserverType.STELLAR.equals(config.type)) { if (config.stellar == null) { errors.reject( "empty-payment-observer-stellar", 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 3929db2fe6..e1f0aebaac 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 @@ -1,14 +1,17 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.StringHelper.isEmpty; + import java.util.List; import lombok.Data; +import org.apache.abdera.i18n.rfc4646.Lang; 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.AppConfig; -import org.stellar.anchor.util.UrlValidationUtil; +import org.stellar.anchor.util.NetUtil; @Data public class PropertyAppConfig implements AppConfig, Validator { @@ -29,14 +32,58 @@ public boolean supports(@NotNull Class clazz) { } @Override - public void validate(@NotNull Object target, Errors errors) { + public void validate(@NotNull Object target, @NotNull Errors errors) { AppConfig config = (AppConfig) target; + validateConfig(config, errors); + validateLanguage(config, errors); + } + + void validateConfig(AppConfig config, Errors errors) { ValidationUtils.rejectIfEmpty( - errors, "stellarNetworkPassphrase", "empty-stellarNetworkPassphrase"); - ValidationUtils.rejectIfEmpty(errors, "hostUrl", "empty-hostUrl"); - ValidationUtils.rejectIfEmpty(errors, "horizonUrl", "empty-horizonUrl"); - UrlValidationUtil.rejectIfMalformed(config.getHostUrl(), "hostUrl", errors); - UrlValidationUtil.rejectIfMalformed(config.getHorizonUrl(), "horizonUrl", errors); + errors, + "stellarNetworkPassphrase", + "stellar-network-passphrase-empty", + "stellar_network.network_passphrase is not defined."); + + if (isEmpty(config.getHostUrl())) { + errors.rejectValue("hostUrl", "host-url-empty", "The host_url is not defined."); + } else { + if (!NetUtil.isUrlValid(config.getHostUrl())) { + errors.rejectValue( + "hostUrl", + "host-url-invalid", + String.format("The host_url:%s is not in valid format.", config.getHostUrl())); + } + } + + if (isEmpty(config.getHorizonUrl())) { + errors.rejectValue( + "horizonUrl", "horizon-url-empty", "The stellar_network.horizon_url is not defined."); + } else { + if (!NetUtil.isUrlValid(config.getHorizonUrl())) { + errors.rejectValue( + "horizonUrl", + "horizon-url-invaliad", + String.format( + "The stellar_network.horizon_url:%s is not in valid format.", + config.getHorizonUrl())); + } + } + } + + void validateLanguage(AppConfig config, Errors errors) { + if (config.getLanguages() != null) { + for (String lang : config.getLanguages()) { + try { + Lang.parse(lang); + } catch (IllegalArgumentException iaex) { + errors.rejectValue( + "languages", + "languages-invalid", + String.format("%s defined in languages is not valid", lang)); + } + } + } } } 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 6780e2c761..5ce6c1db04 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,5 +1,8 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.StringHelper.isEmpty; + +import java.util.HashMap; import java.util.Map; import lombok.Data; import org.jetbrains.annotations.NotNull; @@ -8,12 +11,30 @@ import org.springframework.validation.Validator; import org.stellar.anchor.config.event.EventConfig; import org.stellar.anchor.config.event.PublisherConfig; +import org.stellar.anchor.platform.configurator.ConfigMap; +import org.stellar.anchor.platform.configurator.SepConfigManager; @Data public class PropertyEventConfig implements EventConfig, Validator { private boolean enabled = false; private PropertyPublisherConfig publisher; - private Map eventTypeToQueue; + private Map eventTypeToQueue = new HashMap<>(); + + public PropertyEventConfig() { + ConfigMap configMap = SepConfigManager.getInstance().getConfigMap(); + if (configMap != null) { + eventTypeToQueue.put( + "quote_created", configMap.getString("events.event_type_to_queue.quote_created")); + eventTypeToQueue.put( + "transaction_created", + configMap.getString("events.event_type_to_queue.transaction_created")); + eventTypeToQueue.put( + "transaction_status_changed", + configMap.getString("events.transaction_status_changed.quote_created")); + eventTypeToQueue.put( + "transaction_error", configMap.getString("events.event_type_to_queue.transaction_error")); + } + } @Override public boolean supports(@NotNull Class clazz) { @@ -22,59 +43,123 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { - EventConfig config = (EventConfig) target; + PropertyEventConfig config = (PropertyEventConfig) target; if (!config.isEnabled()) { return; } - // Validate publisher type - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "publisher.type", ""); + validateConfig(config, errors); 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"); + validateMsk(config, errors); + break; // continue to the kafka case. DO NOT break case "kafka": - ValidationUtils.rejectIfEmptyOrWhitespace( - errors, "publisher.kafka.bootstrapServer", "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"); - + validateKafka(config, errors); 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"); + validateSqs(config, errors); break; default: errors.rejectValue( - "publisherType", "invalidType-publisherType", "publisherType set to unknown type"); + "publisher.type", + "invalidType-publisher-type", + "events.publisher.type must be one of 'kafka', 'sqs', or msk"); + } + } + + void validateConfig(EventConfig config, Errors errors) { + if (config.getPublisher() == null) + errors.rejectValue( + "publisher.type", + "publisher-type-empty", + "events.publisher.type is not defined. Please specify the type: KAFKA, SQS, or MSK"); + if (isEmpty(config.getEventTypeToQueue().get("quote_created"))) { + errors.rejectValue( + "publisher.event_type_to_queue.quote_created", + "publisher-event-type-to-queue-quote-created-empty", + "events.publisher.event_type_to_queue.quote_created is not defined. Please specify the queue to publish the QUOTE_CREATED event"); + } + if (isEmpty(config.getEventTypeToQueue().get("transaction_created"))) { + errors.rejectValue( + "publisher.event_type_to_queue.transaction_created", + "publisher-event-type-to-transaction_created-empty", + "events.publisher.event_type_to_queue.transaction_created is not defined. Please specify the queue to publish the QUOTE_CREATED event"); + } + if (isEmpty(config.getEventTypeToQueue().get("transaction_status_changed"))) { + errors.rejectValue( + "publisher.event_type_to_queue.transaction_status_changed", + "publisher-event-type-to-queue-transaction_status_changed-empty", + "events.publisher.event_type_to_queue.transaction_status_changed is not defined. Please specify the queue to publish the QUOTE_CREATED event"); + } + if (isEmpty(config.getEventTypeToQueue().get("transaction_error"))) { + errors.rejectValue( + "publisher.event_type_to_queue.transaction_error", + "publisher-event-type-to-queue-transaction_error-empty", + "events.publisher.event_type_to_queue.transaction_error is not defined. Please specify the queue to publish the QUOTE_CREATED event"); + } + } + + void validateSqs(PropertyEventConfig config, Errors errors) { + if (isEmpty(config.getPublisher().getSqs().awsRegion)) { + errors.rejectValue( + "publisher.sqs.awsRegion", + "sqs-aws-region-empty", + "events.publisher.sqs.aws_region must be defined"); + } + } + + void validateKafka(PropertyEventConfig config, Errors errors) { + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, "publisher.kafka.bootstrapServer", "kafka-bootstrap-server-empty"); + if (config.publisher.kafka.retries < 0) { + errors.rejectValue( + "publisher.kafka.retries", + "kafka-retries-invalid", + "events.publisher.kafka.retries must be greater than 0"); + } + + if (config.publisher.kafka.lingerMs < 0) { + errors.rejectValue( + "publisher.kafka.lingerMs", + "kafka-linger-ms-invalid", + "events.publisher.kafka.linger_ms must be greater than 0"); + } + + if (config.publisher.kafka.batchSize < 0) { + errors.rejectValue( + "publisher.kafka.batchSize", + "kafka-batch-size-invalid", + "events.publisher.kafka.batch_size must be greater than 0"); + } + } + + void validateMsk(PropertyEventConfig config, Errors errors) { + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, "publisher.msk.bootstrapServer", "msk-bootstrap-server-empty"); + + if (config.publisher.msk.retries < 0) { + errors.rejectValue( + "publisher.msk.retries", + "msk-retries-invalid", + "events.publisher.msk.retries must be greater than 0"); + } + + if (config.publisher.msk.lingerMs < 0) { + errors.rejectValue( + "publisher.msk.lingerMs", + "msk-linger-ms-invalid", + "events.publisher.msk.linger_ms must be greater than 0"); + } + + if (config.publisher.msk.batchSize < 0) { + errors.rejectValue( + "publisher.msk.batchSize", + "msk-batch-size-invalid", + "events.publisher.msk.batch_size must be greater than 0"); } } } 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 1c673632ae..75e35595d6 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 @@ -9,7 +9,7 @@ @Data public class PropertyMetricConfig implements MetricConfig, Validator { - private boolean enbaled = false; + private boolean enabled = false; private boolean extrasEnabled = false; private Integer runInterval = 30; 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 45c562d670..f6248f445e 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,16 +1,18 @@ package org.stellar.anchor.platform.config; +import static java.lang.String.format; import static org.stellar.anchor.util.StringHelper.isEmpty; import java.util.List; import lombok.Data; import org.jetbrains.annotations.NotNull; 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; +import org.stellar.anchor.util.ListHelper; +import org.stellar.anchor.util.NetUtil; +import org.stellar.sdk.KeyPair; @Data public class PropertySep10Config implements Sep10Config, Validator { @@ -24,11 +26,9 @@ public class PropertySep10Config implements Sep10Config, Validator { private List omnibusAccountList; private boolean requireKnownOmnibusAccount; private SecretConfig secretConfig; - private JwtService jwtService; - public PropertySep10Config(SecretConfig secretConfig, JwtService jwtService) { + public PropertySep10Config(SecretConfig secretConfig) { this.secretConfig = secretConfig; - this.jwtService = jwtService; } @Override @@ -38,22 +38,135 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { - Sep10Config config = (Sep10Config) target; + PropertySep10Config config = (PropertySep10Config) target; if (config.getEnabled()) { - ValidationUtils.rejectIfEmptyOrWhitespace(errors, "homeDomain", "empty-homeDomain"); - if (isEmpty(secretConfig.getSep10SigningSeed())) { + validateConfig(config, errors); + validateClientAttribution(config, errors); + validateOmnibusAccounts(config, errors); + } + } + + void validateConfig(Sep10Config config, Errors errors) { + if (isEmpty(secretConfig.getSep10SigningSeed())) { + errors.rejectValue( + null, + "sep10-signing-seed-empty", + "Please set environment variable SECRET_SEP10_SIGNING_SEED"); + } + + if (!isEmpty(secretConfig.getSep10SigningSeed())) { + try { + KeyPair.fromSecretSeed(secretConfig.getSep10SigningSeed()); + } catch (Throwable ex) { errors.rejectValue( null, - "empty-sep10SigningSeed", - "Please set environment variable secret.sep10.signing_seed"); + "sep10-signing-seed-invalid", + "The signing seed of SECRET_SEP10_SIGNING_SEED is invalid"); } - if (isEmpty(secretConfig.getSep10JwtSecretKey()) || isEmpty(jwtService.getJwtKey())) { + } + + if (isEmpty(secretConfig.getSep10JwtSecretKey())) { + errors.rejectValue( + null, + "sep10-jwt-secret-empty", + "Please set environment variable SECRET_SEP10_JWT_SECRET"); + } + + if (!isEmpty(config.getHomeDomain())) { + if (!NetUtil.isServerPortValid(config.getHomeDomain())) { errors.rejectValue( - null, - "empty-sep10JwtSecret", - "Please set environment variable secret.sep10.jwt_secret"); + "homeDomain", + "sep10-home-domain-invalid", + "The sep10.home_domain does not have valid format."); + } + } + + if (config.getAuthTimeout() <= 0) { + errors.rejectValue( + "authTimeout", + "sep10-auth-timeout-invalid", + "The sep10.auth_timeout must be greater than 0"); + } + + if (config.getJwtTimeout() <= 0) { + errors.rejectValue( + "jwtTimeout", + "sep10-jwt-timeout-invalid", + "The sep10.jwt_timeout must be greater than 0"); + } + } + + void validateClientAttribution(PropertySep10Config config, Errors errors) { + if (config.clientAttributionRequired) { + if (ListHelper.isEmpty(config.clientAttributionAllowList) + && ListHelper.isEmpty(config.clientAttributionDenyList)) { + errors.reject( + "sep10-client-attribution-lists-empty", + "One of sep10.client_attribution_allow_list and sep10.client_attribution_deny_list must NOT be empty while the sep10.client_attribution_required is set to true"); + } + if (!ListHelper.isEmpty(config.clientAttributionAllowList) + && !ListHelper.isEmpty(config.clientAttributionDenyList)) { + errors.reject( + "sep10-client-attribution-lists-conflict", + "Only one of sep10.client_attribution_allow_list and sep10.client_attribution_deny_list can be defined while the sep10.client_attribution_required is set to true"); } + + if (!ListHelper.isEmpty(config.clientAttributionAllowList)) { + for (String clientDomain : config.clientAttributionAllowList) { + if (!NetUtil.isServerPortValid(clientDomain)) { + errors.rejectValue( + "clientAttributionAllowList", + "sep10-client_attribution_allow_list_invalid", + format("%s is not a valid value for client domain.", clientDomain)); + } + } + } + + if (!ListHelper.isEmpty(config.clientAttributionDenyList)) { + for (String clientDomain : config.clientAttributionDenyList) { + if (!NetUtil.isServerPortValid(clientDomain)) { + errors.rejectValue( + "clientAttributionDenyList", + "sep10-client_attribution_deny_list_invalid", + format("%s is not a valid value for client domain.", clientDomain)); + } + } + } + } else { + if (!ListHelper.isEmpty(config.clientAttributionAllowList)) { + errors.rejectValue( + "clientAttributionAllowList", + "sep10-client-attribution-allow-list-not-empty", + "sep10.client_attribution_allow_list is not not empty while the sep10.client_attribution_required is set to false"); + } + if (!ListHelper.isEmpty(config.clientAttributionDenyList)) { + errors.rejectValue( + "clientAttributionDenyList", + "sep10-client-attribution-deny-list-not-empty", + "sep10.client_attrbituion_deny_list is not not empty while the sep10.client_attribution_required is set to false"); + } + } + } + + void validateOmnibusAccounts(Sep10Config config, Errors errors) { + for (String account : config.getOmnibusAccountList()) { + try { + if (account != null) KeyPair.fromAccountId(account); + } catch (Throwable ex) { + errors.rejectValue( + "omnibusAccountList", + "sep10-omnibus-account-not-valid", + format("Invalid omnibus account:%s in sep10.omnibus_account_list", account)); + } + } + + if (config.isRequireKnownOmnibusAccount() + && ListHelper.isEmpty(config.getOmnibusAccountList())) { + errors.rejectValue( + "omnibusAccountList", + "sep10-omnibus-account-list-empty", + "sep10.omnibus_account_list is empty while sep10.require_known_omnibus_account is set to true"); } } } 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 57da720f79..8b8c32b2cb 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 @@ -1,27 +1,14 @@ package org.stellar.anchor.platform.config; import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.springframework.validation.Errors; -import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep12Config; @Data -public class PropertySep12Config implements Sep12Config, Validator { +public class PropertySep12Config implements Sep12Config { Boolean enabled; String customerIntegrationEndPoint; public PropertySep12Config(CallbackApiConfig callbackApiConfig) { this.customerIntegrationEndPoint = callbackApiConfig.getBaseUrl(); } - - @Override - public boolean supports(@NotNull Class clazz) { - return Sep12Config.class.isAssignableFrom(clazz); - } - - @Override - public void validate(@NotNull Object target, @NotNull Errors errors) { - Sep12Config config = (Sep12Config) target; - } } 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 dbb75bba34..7a7ad9f209 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,6 +1,6 @@ package org.stellar.anchor.platform.config; -import static org.stellar.anchor.util.StringHelper.isNotEmpty; +import static org.stellar.anchor.util.StringHelper.isEmpty; import java.io.File; import lombok.AllArgsConstructor; @@ -37,67 +37,61 @@ public void validate(@NotNull Object target, @NotNull Errors errors) { Sep1Config config = (Sep1Config) target; if (config.isEnabled()) { - 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"); + validateConfig(config, errors); + if (!errors.hasErrors()) validateTomlTypeAndValue(config, errors); + } + } - 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: + void validateTomlTypeAndValue(Sep1Config config, Errors errors) { + if (isEmpty(config.getType())) { + errors.rejectValue("type", "sep1-toml-type-empty", "sep1.toml.type must not be empty"); + } else { + switch (config.getType().toLowerCase()) { + case "string": + try { + Sep1Helper.parse(config.getValue()); + } catch (IllegalStateException isex) { errors.rejectValue( - "type", - "invalid-sep1Type", + "value", + "sep1-toml-value-string-invalid", 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"); + "sep1.toml.value does not contain a valid TOML. %s", isex.getMessage())); + } + break; + case "url": + if (!NetUtil.isUrlValid(config.getValue())) { + errors.rejectValue( + "value", + "sep1-toml-value-url-invalid", + String.format("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", + "sep1-toml-value-file-does-not-exist", + String.format( + "sep1.toml.value=%s specifies a file that does not exist", config.getValue())); + } + break; + default: + errors.rejectValue( + "type", + "sep1-toml-type-invalid", + String.format( + "'%s' is not a valid sep1.toml.type. Only 'string', 'url' and 'file' are supported", + config.getValue())); + break; } } } + + void validateConfig(Sep1Config ignoredConfig, Errors errors) { + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, "type", "sep1-toml-type-empty", "sep1.toml.type is must be specified"); + ValidationUtils.rejectIfEmptyOrWhitespace( + errors, "value", "sep1-toml-value-empty", "sep1.toml.value is must be specified"); + } } 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 3b72a6c60b..4372c16e2f 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,10 +1,13 @@ package org.stellar.anchor.platform.config; +import static org.stellar.anchor.util.StringHelper.isEmpty; + import lombok.Data; import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep24Config; +import org.stellar.anchor.util.NetUtil; @Data public class PropertySep24Config implements Sep24Config, Validator { @@ -19,6 +22,26 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { - Sep24Config config = (Sep24Config) target; + PropertySep24Config config = (PropertySep24Config) target; + if (isEmpty(config.getInteractiveUrl())) { + errors.rejectValue( + "interactiveUrl", + "sep24-interactive-url-invalid", + "sep24.interactive_url is not defined."); + } else if (!NetUtil.isUrlValid(config.getInteractiveUrl())) { + errors.rejectValue( + "interactiveUrl", + "sep24-interactive-url-invalid", + String.format("sep24.interactive_url:%s is not valid", config.getInteractiveUrl())); + } + + if (config.getInteractiveJwtExpiration() <= 0) { + errors.rejectValue( + "interactiveJwtExpiration", + "sep24-interactive-jwt-expiration-invalid", + String.format( + "sep24.interactive_jwt_expiration:%s is not valid", + config.getInteractiveJwtExpiration())); + } } } 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 7f7e5398b6..139af8db84 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,46 +1,16 @@ package org.stellar.anchor.platform.config; -import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.API; import static org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.SELF; import static org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND; -import java.util.Objects; import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.springframework.validation.Errors; -import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep31Config; @Data -public class PropertySep31Config implements Sep31Config, Validator { +public class PropertySep31Config implements Sep31Config { boolean enabled; - String feeIntegrationEndPoint; - String uniqueAddressIntegrationEndPoint; PaymentType paymentType = STRICT_SEND; DepositInfoGeneratorType depositInfoGeneratorType = SELF; - public PropertySep31Config(CallbackApiConfig callbackApiConfig) { - this.feeIntegrationEndPoint = callbackApiConfig.getBaseUrl(); - this.uniqueAddressIntegrationEndPoint = callbackApiConfig.getBaseUrl(); - } - - @Override - public boolean supports(@NotNull Class clazz) { - return Sep31Config.class.isAssignableFrom(clazz); - } - - @Override - public void validate(@NotNull Object target, @NotNull Errors errors) { - Sep31Config config = (Sep31Config) target; - if (config.isEnabled()) { - if (config.getDepositInfoGeneratorType().equals(API)) { - if (Objects.toString(uniqueAddressIntegrationEndPoint, "").isEmpty()) { - errors.rejectValue( - "uniqueAddressIntegrationEndPoint", - "badConfig-uniqueAddressIntegrationEndPoint", - "depositInfoGeneratorType set as API, but uniqueAddressIntegrationEndPoint not properly configured"); - } - } - } - } + public PropertySep31Config() {} } 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 fcc1654236..e8cdb37369 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 @@ -1,22 +1,9 @@ package org.stellar.anchor.platform.config; import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.springframework.validation.Errors; -import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep38Config; @Data -public class PropertySep38Config implements Sep38Config, Validator { +public class PropertySep38Config implements Sep38Config { boolean enabled; - - @Override - public boolean supports(@NotNull Class clazz) { - return Sep38Config.class.isAssignableFrom(clazz); - } - - @Override - public void validate(@NotNull Object target, @NotNull Errors errors) { - Sep38Config config = (Sep38Config) target; - } } 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 index ff683209e4..fb6b6b14ca 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/SqsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/SqsConfig.java @@ -1,8 +1,12 @@ package org.stellar.anchor.platform.config; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor +@AllArgsConstructor public class SqsConfig { boolean useIAM; String awsRegion; 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 index bd25ebce9c..5327a8bb9c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java @@ -38,6 +38,10 @@ public static ConfigManager getInstance() { return configManager; } + public ConfigMap getConfigMap() { + return configMap; + } + void sanitize(ConfigMap configMap) { SecretManager.getInstance() .secretVars 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 ffd0612ad9..d79bc650ae 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -133,16 +133,16 @@ payment_observer: silence_timeout_retries: 5 # The initial backoff (cool-down) time (in seconds) before reconnecting to the Stellar network - initial_stream_backoff_time: 1 + initial_stream_backoff_time: 5 # The maximum backoff (cool-down) time (in seconds) before reconnecting to the Stellar network max_stream_backoff_time: 300 # The initial backoff (cool-down) time (in seconds) before reconnecting to the event publisher - initial_event_backoff_time: 1 + initial_event_backoff_time: 5 # The initial backoff (cool-down) time (in seconds) before reconnecting to the event publisher - max_event_backoff_time: 1 + max_event_backoff_time: 300 ## @param: languages ## @supported_values: en @@ -231,7 +231,7 @@ sep10: ## @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 + client_attribution_allow_list: ## @param: client_attribution_deny_list ## Set the black list of the client domain. The domains are comma-separated. 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 063aafa961..bcf5c76ad4 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 @@ -1,36 +1,104 @@ package org.stellar.anchor.platform.config -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.fail -import org.junit.jupiter.api.Test +import java.util.stream.Stream +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +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 AppConfigTest { - @Test - fun testAppConfigValid() { - val appConfig = PropertyAppConfig() - 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) - if (errors.hasErrors()) { - errors.printStackTrace() - fail("found validation error(s)") - } +import org.springframework.validation.Errors + +class AppConfigTest { + lateinit var config: PropertyAppConfig + lateinit var errors: Errors + + @BeforeEach + fun setUp() { + config = PropertyAppConfig() + config.stellarNetworkPassphrase = "Test SDF Network ; September 2015" + config.horizonUrl = "https://horizon-testnet.stellar.org" + config.hostUrl = "http://localhost:8080" + errors = BindException(config, "config") + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = [""]) + fun `test empty host_url`(url: String?) { + config.hostUrl = url + config.validateConfig(config, errors) + assertErrorCode(errors, "host-url-empty") + } + + @ParameterizedTest + @ValueSource(strings = ["https://stellar.org", "https://stellar.org:8080"]) + fun `test valid host_url`(url: String) { + config.hostUrl = url + config.validateConfig(config, errors) + assertFalse(errors.hasErrors()) + } + + @ParameterizedTest + @ValueSource(strings = ["https ://stellar.org", "stellar.org", "abc"]) + fun `test invalid host_url`(url: String) { + config.hostUrl = url + config.validateConfig(config, errors) + assertErrorCode(errors, "host-url-invalid") + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = [""]) + fun `test empty horizon_url`(url: String?) { + config.horizonUrl = url + config.validateConfig(config, errors) + assertErrorCode(errors, "horizon-url-empty") + } + @ParameterizedTest + @ValueSource( + strings = ["https://horizon-testnet.stellar.org", "https://horizon-testnet.stellar.org:8080"] + ) + fun `test valid horizon_url`(url: String) { + config.hostUrl = url + config.validateConfig(config, errors) + assertFalse(errors.hasErrors()) + } + + @ParameterizedTest + @ValueSource(strings = ["https://horizon-testnet.stellar. org", "stellar.org", "abc"]) + fun `test invalid horizon_url`(url: String) { + config.hostUrl = url + config.validateConfig(config, errors) + assertErrorCode(errors, "host-url-invalid") + } + + @ParameterizedTest + @NullSource + @MethodSource("validLanguages") + fun `test valid languages`(langs: List?) { + config.languages = langs + config.validateLanguage(config, errors) + assertFalse(errors.hasErrors()) + } + + @ParameterizedTest + @MethodSource("invalidLanguages") + fun `test invalid languages`(langs: List) { + config.languages = langs + config.validateLanguage(config, errors) + assertErrorCode(errors, "languages-invalid") } - @Test - fun testAppConfigBadHorizonUrl() { - val appConfig = PropertyAppConfig() - appConfig.horizonUrl = "not-a-url" - appConfig.stellarNetworkPassphrase = "Test SDF Network ; September 2015" - appConfig.hostUrl = "http://localhost:2222" - val errors = BindException(appConfig, "appConfig") - ValidationUtils.invokeValidator(appConfig, appConfig, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "invalidUrl-horizonUrl") } + companion object { + @JvmStatic + fun validLanguages(): Stream> { + return Stream.of(listOf(), listOf("en", "en-us", "EN", "EN-US"), listOf("zh-tw", "zh")) + } + @JvmStatic + fun invalidLanguages(): Stream> { + return Stream.of(listOf("1234", "EN", "EN-US")) + } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/CallbackApiConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/CallbackApiConfigTest.kt new file mode 100644 index 0000000000..25269d3197 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/CallbackApiConfigTest.kt @@ -0,0 +1,75 @@ +package org.stellar.anchor.platform.config + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +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.Errors +import org.stellar.anchor.auth.AuthInfo +import org.stellar.anchor.auth.AuthType.JWT_TOKEN + +class CallbackApiConfigTest { + lateinit var config: CallbackApiConfig + lateinit var errors: Errors + lateinit var secretConfig: PropertySecretConfig + + @BeforeEach + fun setUp() { + secretConfig = mockk() + config = CallbackApiConfig(secretConfig) + errors = BindException(config, "config") + } + + @Test + fun `test base_url`() { + config.baseUrl = "http://localhost:8080" + config.validateBaseUrl(config, errors) + assertEquals(0, errors.errorCount) + + config.baseUrl = "https://www.stellar.org" + config.validateBaseUrl(config, errors) + assertEquals(0, errors.errorCount) + } + + @Test + fun `test mal-formatted url`() { + // mal-formatted base_url + config.baseUrl = "http://localhost; 8080" + config.validateBaseUrl(config, errors) + assertEquals(1, errors.errorCount) + assertEquals("mal-formatted-callback-api-base-url", errors.allErrors[0].code) + } + + @Test + fun `test empty url`() { + // empty base_url + config.baseUrl = "" + config.validateBaseUrl(config, errors) + assertEquals(2, errors.errorCount) + assertEquals("empty-callback-api-base-url", errors.allErrors[0].code) + } + + @Test + fun `test JWT_TOKEN callback api secret`() { + every { secretConfig.callbackApiSecret } returns "secret" + config.setAuth(AuthInfo(JWT_TOKEN, null, "30000")) + config.validateAuth(config, errors) + assertEquals(0, errors.errorCount) + } + + @ParameterizedTest + @ValueSource(strings = [""]) + @NullSource + fun `test empty secret`(secretValue: String?) { + every { secretConfig.callbackApiSecret } returns secretValue + config.setAuth(AuthInfo(JWT_TOKEN, null, "30000")) + config.validateAuth(config, errors) + assertEquals(1, errors.errorCount) + assertEquals("empty-secret-callback-api-secret", errors.allErrors[0].code) + } +} 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 9290a13625..bbb7e06f8a 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 @@ -1,85 +1,141 @@ package org.stellar.anchor.platform.config -import kotlin.test.assertContains +import java.util.stream.Stream import kotlin.test.assertEquals +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource import org.springframework.validation.BindException +import org.springframework.validation.Errors import org.springframework.validation.ValidationUtils -open class EventConfigTest { - @Test - fun testDisabledEventConfig() { - // events are disabled - val eventConfig = PropertyEventConfig() - eventConfig.isEnabled = false - val errors = BindException(eventConfig, "eventConfig") - ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) - assertEquals(0, errors.errorCount) - } +class EventConfigTest { + lateinit var config: PropertyEventConfig + lateinit var errors: Errors - @Test - fun testEnabledForKafka() { - // Events correctly configured for kafka - val eventConfig = PropertyEventConfig() - val kafkaConfig = KafkaConfig() - - kafkaConfig.bootstrapServer = "localhost:29092" - eventConfig.publisher = PropertyPublisherConfig() - eventConfig.publisher.type = "kafka" - eventConfig.publisher.kafka = kafkaConfig - eventConfig.isEnabled = true + @BeforeEach + fun setUp() { + config = PropertyEventConfig() + config.eventTypeToQueue.put("quote_created", "ap_quote_created") + config.eventTypeToQueue.put("transaction_created", "ap_transaction_created") + config.eventTypeToQueue.put("transaction_status_changed", "ap_transaction_status_changed") + config.eventTypeToQueue.put("transaction_error", "ap_transaction_error") - val errors = BindException(eventConfig, "eventConfig") - ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) - assertEquals(0, errors.errorCount) + errors = BindException(config, "config") } @Test - fun testEnabledForSqs() { - // Events correctly configured for kafka - val eventConfig = PropertyEventConfig() - val sqsConfig = SqsConfig() - - sqsConfig.awsRegion = "us-east" - eventConfig.publisher = PropertyPublisherConfig() - eventConfig.publisher.type = "sqs" - eventConfig.publisher.sqs = sqsConfig - eventConfig.isEnabled = true - - val errors = BindException(eventConfig, "eventConfig") - ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) + fun `test enabled flag`() { + config.isEnabled = false + ValidationUtils.invokeValidator(config, config, errors) assertEquals(0, errors.errorCount) } - @Test - fun testKafkaMissingFields() { - val eventConfig = PropertyEventConfig() - val kafkaConfig = KafkaConfig() + @ParameterizedTest + @MethodSource("generatedKafkaConfig") + fun `test Kafka configurations`(errorCount: Int, errorCode: String, kafkaConfig: KafkaConfig) { + config.isEnabled = true + config.publisher = PropertyPublisherConfig() + config.publisher.type = "kafka" + config.publisher.kafka = kafkaConfig + config.validateKafka(config, errors) + assertEquals(errorCount, errors.errorCount) + if (errorCount > 0) { + assertEquals(errorCode, errors.allErrors[0].code) + } + } - eventConfig.publisher = PropertyPublisherConfig() - eventConfig.publisher.type = "kafka" - eventConfig.publisher.kafka = kafkaConfig - eventConfig.isEnabled = true + @ParameterizedTest + @MethodSource("generatedSqsConfig") + fun `test Sqs configurations`(errorCount: Int, errorCode: String, sqsConfig: SqsConfig) { + config.isEnabled = true + config.publisher = PropertyPublisherConfig() + config.publisher.type = "sqs" + config.publisher.sqs = sqsConfig + config.validateSqs(config, errors) + assertEquals(errorCount, errors.errorCount) + if (errorCount > 0) { + assertEquals(errorCode, errors.allErrors[0].code) + } + } - val errors = BindException(eventConfig, "eventConfig") - ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "empty-bootstrapServer") } + @ParameterizedTest + @MethodSource("generatedMskConfig") + fun `test Msk configurations`(errorCount: Int, errorCode: String, mskConfig: MskConfig) { + config.isEnabled = true + config.publisher = PropertyPublisherConfig() + config.publisher.type = "msk" + config.publisher.msk = mskConfig + config.validateMsk(config, errors) + assertEquals(errorCount, errors.errorCount) + if (errorCount > 0) { + assertEquals(errorCode, errors.allErrors[0].code) + } } - @Test - fun testSqsMissingFields() { - val eventConfig = PropertyEventConfig() - val sqsConfig = SqsConfig() + companion object { + @JvmStatic + fun generatedKafkaConfig(): Stream { + return Stream.of( + Arguments.of(0, "no-error", KafkaConfig("localhost:29092", "client_id", 5, 10, 500)), + Arguments.of( + 1, + "kafka-retries-invalid", + KafkaConfig("localhost:29092", "client_id", -1, 10, 500) + ), + Arguments.of( + 1, + "kafka-linger-ms-invalid", + KafkaConfig("localhost:29092", "client_id", 5, -10, 500) + ), + Arguments.of( + 1, + "kafka-batch-size-invalid", + KafkaConfig("localhost:29092", "client_id", 5, 10, -1) + ), + Arguments.of(1, "kafka-bootstrap-server-empty", KafkaConfig("", "client_id", 1, 10, 500)), + Arguments.of(1, "kafka-bootstrap-server-empty", KafkaConfig(null, "client_id", 1, 10, 500)) + ) + } - eventConfig.publisher = PropertyPublisherConfig() - eventConfig.publisher.type = "sqs" - eventConfig.publisher.sqs = sqsConfig - eventConfig.isEnabled = true + @JvmStatic + fun generatedSqsConfig(): Stream { + return Stream.of( + Arguments.of(0, "no-error", SqsConfig(true, "us-east-1")), + Arguments.of(0, "no-error", SqsConfig(false, "us-east-1")), + Arguments.of(1, "sqs-aws-region-empty", SqsConfig(true, null)), + Arguments.of(1, "sqs-aws-region-empty", SqsConfig(false, null)) + ) + } - val errors = BindException(eventConfig, "eventConfig") - ValidationUtils.invokeValidator(eventConfig, eventConfig, errors) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "empty-aws-region") } + @JvmStatic + fun generatedMskConfig(): Stream { + return Stream.of( + Arguments.of(0, "no-error", MskConfig(true, "localhost:29092", "client_id", 5, 10, 500)), + Arguments.of( + 1, + "msk-retries-invalid", + MskConfig(true, "localhost:29092", "client_id", -1, 10, 500) + ), + Arguments.of( + 1, + "msk-linger-ms-invalid", + MskConfig(true, "localhost:29092", "client_id", 5, -10, 500) + ), + Arguments.of( + 1, + "msk-batch-size-invalid", + MskConfig(true, "localhost:29092", "client_id", 5, 10, -1) + ), + Arguments.of(1, "msk-bootstrap-server-empty", MskConfig(true, "", "client_id", 1, 10, 500)), + Arguments.of( + 1, + "msk-bootstrap-server-empty", + MskConfig(true, null, "client_id", 1, 10, 500) + ) + ) + } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/PaymentObserverConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/PaymentObserverConfigTest.kt new file mode 100644 index 0000000000..fa167e8123 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/PaymentObserverConfigTest.kt @@ -0,0 +1,85 @@ +package org.stellar.anchor.platform.config + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.springframework.validation.BindException +import org.springframework.validation.Errors +import org.stellar.anchor.platform.config.PaymentObserverConfig.PaymentObserverType.STELLAR +import org.stellar.anchor.platform.config.PaymentObserverConfig.StellarPaymentObserverConfig + +class PaymentObserverConfigTest { + lateinit var config: PaymentObserverConfig + lateinit var errors: Errors + lateinit var stellarConfig: StellarPaymentObserverConfig + + @BeforeEach + fun setUp() { + config = PaymentObserverConfig() + errors = BindException(config, "config") + stellarConfig = StellarPaymentObserverConfig() + } + + @Test + fun `test stellar payment observer config`() { + config.type = STELLAR + stellarConfig = StellarPaymentObserverConfig(90, 5, 5, 5, 300, 5, 300) + config.setStellar(stellarConfig) + config.validateStellar(config, errors) + assertEquals(0, errors.errorCount) + } + + @ParameterizedTest + @CsvSource( + value = + [ + "90,5,5,5,300,5,300,0,no-error", + "0,5,5,5,300,5,300,1,invalid-payment-observer-silence-check-interval", + "90,0,5,5,300,5,300,1,invalid-payment-observer-stellar-silence-timeout", + "90,5,0,5,300,5,300,1,invalid-payment-observer-stellar-silence-timeout-retries", + "90,5,5,0,300,5,300,1,invalid-payment-observer-stellar-initial-stream-backoff-time", + "90,5,5,5,0,5,300,1,invalid-payment-observer-stellar-max-stream-backoff-time", + "90,5,5,5,300,0,300,1,invalid-payment-observer-stellar-initial-event-backoff-time", + "90,5,5,5,300,5,0,1,invalid-payment-observer-stellar-max-event-backoff-time" + ] + ) + fun `test invalid stellar config`( + p0: String, + p1: String, + p2: String, + p3: String, + p4: String, + p5: String, + p6: String, + errorCount: String, + errorCode: String + ) { + config.type = STELLAR + config.setStellar( + StellarPaymentObserverConfig( + p0.toInt(), + p1.toInt(), + p2.toInt(), + p3.toInt(), + p4.toInt(), + p5.toInt(), + p6.toInt() + ) + ) + config.validateStellar(config, errors) + assertEquals(errorCount.toInt(), errors.errorCount) + if (errors.errorCount > 0) { + assertEquals(errorCode, errors.allErrors[0].code) + } + } + + fun `test empty stellar payment observer config`() { + config.type = STELLAR + config.setStellar(null) + config.validateStellar(config, errors) + assertEquals(1, errors.errorCount) + assertEquals("empty-payment-observer-stellar", errors.allErrors[0].code) + } +} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep10ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep10ConfigTest.kt new file mode 100644 index 0000000000..0e2c5ecc73 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep10ConfigTest.kt @@ -0,0 +1,167 @@ +package org.stellar.anchor.platform.config + +import io.mockk.every +import io.mockk.mockk +import java.util.stream.Stream +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource +import org.springframework.validation.BindException +import org.springframework.validation.Errors + +class Sep10ConfigTest { + lateinit var config: PropertySep10Config + lateinit var errors: Errors + lateinit var secretConfig: PropertySecretConfig + + @BeforeEach + fun setUp() { + secretConfig = mockk() + config = PropertySep10Config(secretConfig) + config.enabled = true + errors = BindException(config, "config") + every { secretConfig.sep10SigningSeed } returns + "SDNMFWJGLVR4O2XV3SNEJVF53MMLQWYFYFC7HT7JZ5235AXPETHB4K3D" + every { secretConfig.sep10JwtSecretKey } returns "secret" + } + + @Test + fun `test default sep10 config`() { + config.validateConfig(config, errors) + assertFalse(errors.hasErrors()) + } + + @Test + fun `test client attribution and lists`() { + config.isClientAttributionRequired = true + config.clientAttributionAllowList = listOf("stellar.org", "lobstr.com") + config.validateClientAttribution(config, errors) + assertFalse(errors.hasErrors()) + + config.clientAttributionAllowList = listOf() + config.clientAttributionDenyList = listOf("stellar.org", "lobstr.com") + config.validateClientAttribution(config, errors) + assertFalse(errors.hasErrors()) + } + + @Test + fun `test client attribution allow list is not empty while client_attrbution_required is set to false`() { + config.isClientAttributionRequired = false + config.clientAttributionAllowList = listOf("stellar.org", "lobstr.com") + config.validateClientAttribution(config, errors) + assertErrorCode(errors, "sep10-client-attribution-allow-list-not-empty") + } + + @Test + fun `test client attribution deny list is not empty while client_attrbution_required is set to false`() { + config.isClientAttributionRequired = false + config.clientAttributionDenyList = listOf("stellar.org", "lobstr.com") + config.validateClientAttribution(config, errors) + assertErrorCode(errors, "sep10-client-attribution-deny-list-not-empty") + } + + @Test + fun `test both client attribution deny and allow lists are defined`() { + config.isClientAttributionRequired = true + config.clientAttributionAllowList = listOf("stellar.org", "lobstr.com") + config.clientAttributionDenyList = listOf("mgi.com") + config.validateClientAttribution(config, errors) + assertErrorCode(errors, "sep10-client-attribution-lists-conflict") + } + + @Test + fun `test both client attribution deny and allow lists are empty`() { + config.isClientAttributionRequired = true + config.clientAttributionAllowList = listOf() + config.clientAttributionDenyList = listOf() + config.validateClientAttribution(config, errors) + assertErrorCode(errors, "sep10-client-attribution-lists-empty") + } + + @ParameterizedTest + @MethodSource("validOmnibusAccounts") + fun `test omnibus account list`(omnibusAccounts: List) { + config.omnibusAccountList = omnibusAccounts + config.validateOmnibusAccounts(config, errors) + assertFalse(errors.hasErrors()) + } + + @ParameterizedTest + @MethodSource("invalidOmnibusAccounts") + fun `test invalid omnibus account list`(omnibusAccounts: List) { + config.omnibusAccountList = omnibusAccounts + config.validateOmnibusAccounts(config, errors) + assertErrorCode(errors, "sep10-omnibus-account-not-valid") + } + + @Test + fun `test required known omnibus account`() { + config.isRequireKnownOmnibusAccount = true + config.omnibusAccountList = listOf("GCS2KBEGIWILNKFYY6ORT72Y2HUFYG6IIIOESHVQC3E5NIYT3L2I5F5E") + config.validateOmnibusAccounts(config, errors) + assertFalse(errors.hasErrors()) + } + + @Test + fun `test known omnibus account required but not defined`() { + config.isRequireKnownOmnibusAccount = true + config.omnibusAccountList = listOf() + config.validateOmnibusAccounts(config, errors) + assertErrorCode(errors, "sep10-omnibus-account-list-empty") + } + + @ParameterizedTest + @ValueSource(strings = ["stellar.org", "moneygram.com"]) + fun `test valid home domains`(value: String) { + config.homeDomain = value + config.validateConfig(config, errors) + assertFalse(errors.hasErrors()) + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = ["bad key", "GCS2KBEGIWILNKFYY6ORT72Y2HUFYG6IIIOESHVQC3E5NIYT3L2I5F5E"]) + fun `test invalid sep10 seeds`(value: String?) { + every { secretConfig.sep10SigningSeed } returns value + config.validateConfig(config, errors) + assertTrue(errors.hasErrors()) + } + + @ParameterizedTest + @ValueSource(strings = ["stellar .org", "abc", "299.0.0.1"]) + fun `test invalid home domains`(value: String) { + config.homeDomain = value + config.validateConfig(config, errors) + assertTrue(errors.hasErrors()) + assertErrorCode(errors, "sep10-home-domain-invalid") + } + + companion object { + @JvmStatic + fun validOmnibusAccounts(): Stream> { + return Stream.of( + listOf(), + listOf("GAU2XSVTXY6GADVFFLDJLWO44SC6MAWPMHZTI4QHYUKV6BGGJFAIEYGB"), + listOf( + "GCS2KBEGIWILNKFYY6ORT72Y2HUFYG6IIIOESHVQC3E5NIYT3L2I5F5E", + "GAU2XSVTXY6GADVFFLDJLWO44SC6MAWPMHZTI4QHYUKV6BGGJFAIEYGB" + ) + ) + } + @JvmStatic + fun invalidOmnibusAccounts(): Stream> { + return Stream.of( + listOf("SBBGHY3KIEI4XM2G2MD76DB3F3EPC6A2NR57CY2PFJVE66T7UTAE3SKD"), + listOf( + "GCS2KBEGIWILNKFYY6ORT72Y2HUFYG6IIIOESHVQC3E5NIYT3L2I5F5E", + "SBBGHY3KIEI4XM2G2MD76DB3F3EPC6A2NR57CY2PFJVE66T7UTAE3SKD" + ), + listOf("1234") + ) + } + } +} 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 deleted file mode 100644 index 4e4ed4f30d..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep12ConfigTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.stellar.anchor.platform.config - -import kotlin.test.assertEquals -import org.junit.jupiter.api.* -import org.springframework.validation.BindException -import org.springframework.validation.ValidationUtils - -open class Sep12ConfigTest { - @Test - fun testSep12ConfigValid() { - val config = CallbackApiConfig(PropertySecretConfig()) - config.baseUrl = "https://localhost:8081" - val sep12Config = PropertySep12Config(config) - sep12Config.enabled = true - - val errors = BindException(sep12Config, "sep12Config") - ValidationUtils.invokeValidator(sep12Config, sep12Config, errors) - assertEquals(0, errors.errorCount) - } -} 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 55c77ada38..07a06861c6 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 @@ -11,7 +11,7 @@ import org.junit.jupiter.params.provider.ValueSource import org.springframework.validation.BindException import org.springframework.validation.ValidationUtils -open class Sep1ConfigTest { +class Sep1ConfigTest { companion object { fun getTestTomlAsFile(): String { val resource: URL = @@ -58,41 +58,53 @@ open class Sep1ConfigTest { } @ParameterizedTest - @NullSource - @ValueSource(strings = ["bad", "strin g", ""]) + @ValueSource(strings = ["bad", "strin g"]) fun `test bad Sep1Config values`(type: String?) { val errors = validate(PropertySep1Config(true, type, getTestTomlAsUrl())) assertEquals(1, errors.errorCount) + assertEquals("sep1-toml-type-invalid", errors.allErrors[0].code) + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = [""]) + fun `test empty Sep1Config values`(type: String?) { + val errors = validate(PropertySep1Config(true, type, getTestTomlAsUrl())) + assertEquals(1, errors.errorCount) + assertEquals("sep1-toml-type-empty", errors.allErrors[0].code) } @ParameterizedTest @ValueSource(strings = ["bad file", "c:/hello"]) fun `test file of Sep1Config does not exist`(file: String) { - val errors = validate(PropertySep1Config(true, "file", file)) + var errors = validate(PropertySep1Config(true, "file", file)) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "doesNotExist-sep1Value") } + assertEquals("sep1-toml-value-file-does-not-exist", errors.allErrors[0].code) + + errors = validate(PropertySep1Config(false, "file", file)) + assertEquals(0, errors.errorCount) } @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") } + assertEquals(1, errors.errorCount) + assertEquals("sep1-toml-value-empty", errors.allErrors[0].code) errors = validate(PropertySep1Config(true, type, "")) - assertEquals(2, errors.errorCount) - errors.message?.let { assertContains(it, "empty-sep1Value") } + assertEquals(1, errors.errorCount) + assertEquals("sep1-toml-value-empty", errors.allErrors[0].code) } @Test fun `test Sep1Config empty types`() { var errors = validate(PropertySep1Config(true, null, "test value")) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "empty-sep1Type") } + errors.message?.let { assertContains(it, "sep1-toml-type-empty") } errors = validate(PropertySep1Config(true, "", "test value")) assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "empty-sep1Type") } + errors.message?.let { assertContains(it, "sep1-toml-type-empty") } } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt new file mode 100644 index 0000000000..a62cef147c --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt @@ -0,0 +1,49 @@ +package org.stellar.anchor.platform.config + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.springframework.validation.BindException +import org.springframework.validation.Errors + +class Sep24ConfigTest { + lateinit var config: PropertySep24Config + lateinit var errors: Errors + + @BeforeEach + fun setUp() { + config = PropertySep24Config() + config.enabled = true + errors = BindException(config, "config") + } + + @Test + fun `test valid sep24 configuration`() { + config.interactiveUrl = "https://www.stellar.org" + config.interactiveJwtExpiration = 1200 + + config.validate(config, errors) + assertFalse(errors.hasErrors()) + } + + @ParameterizedTest + @ValueSource(strings = ["", "123", "http://abc .com"]) + fun `test bad interactive url`(url: String) { + config.interactiveUrl = url + config.validate(config, errors) + assertTrue(errors.hasErrors()) + assertErrorCode(errors, "sep24-interactive-url-invalid") + } + + @ParameterizedTest + @ValueSource(ints = [-1, Integer.MIN_VALUE, 0]) + fun `test bad interactive jwt expiration`(expiration: Integer) { + config.setInteractiveJwtExpiration(expiration.toInt()) + config.validate(config, errors) + assertTrue(errors.hasErrors()) + assertErrorCode(errors, "sep24-interactive-url-invalid") + } +} 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 deleted file mode 100644 index 4162c695c7..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep31ConfigTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.stellar.anchor.platform.config - -import kotlin.test.assertEquals -import org.junit.jupiter.api.Test -import org.springframework.validation.BindException -import org.springframework.validation.ValidationUtils -import org.stellar.anchor.config.Sep31Config.DepositInfoGeneratorType.API - -open class Sep31ConfigTest { - @Test - fun testSep31Valid() { - val callbackApiConfig = CallbackApiConfig(PropertySecretConfig()) - callbackApiConfig.baseUrl = "http://localhost:8080" - - val sep31Config = PropertySep31Config(callbackApiConfig) - sep31Config.depositInfoGeneratorType = API - - val errors = BindException(sep31Config, "sep31Config") - ValidationUtils.invokeValidator(sep31Config, sep31Config, errors) - assertEquals(0, errors.errorCount) - } -} 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 deleted file mode 100644 index 142788a86d..0000000000 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep38ConfigTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.stellar.anchor.platform.config - -import kotlin.test.assertEquals -import org.junit.jupiter.api.* -import org.springframework.validation.BindException -import org.springframework.validation.ValidationUtils - -open class Sep38ConfigTest { - @Test - fun testSep38ConfigValid() { - val sep38Config = PropertySep38Config() - sep38Config.enabled = true - - val errors = BindException(sep38Config, "sep38Config") - ValidationUtils.invokeValidator(sep38Config, sep38Config, errors) - assertEquals(0, errors.errorCount) - } -} diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/ValidationHelper.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/ValidationHelper.kt new file mode 100644 index 0000000000..a703b3f7f0 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/ValidationHelper.kt @@ -0,0 +1,10 @@ +package org.stellar.anchor.platform.config + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.springframework.validation.Errors + +fun assertErrorCode(errors: Errors, code: String) { + assertTrue(errors.hasErrors()) + assertEquals(code, errors.allErrors[0].code) +} From 66995c14c0e308fcf8960e2d279bf16214cb558b Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 13 Dec 2022 22:56:34 +0800 Subject: [PATCH 0060/1439] ANCHOR-81,102,114: SEP-24 transaction data model and pass stellar-anchor-tests (#677) * Refactored SEP-24 data model * Fix to pass the integration test * Pass the stellar anchor tests * clean up and revert default config values * Add SEP-24 to stellar anchor test as part of GH workflow. * Clean up Sep24Helper * Add check_certificate field to CallbackApiConfig --- .github/workflows/basic_tests.yml | 4 +- .../anchor/reference/service/RateService.java | 6 +- .../InvalidStellarAccountException.java | 7 + .../api/sep/sep24/TransactionResponse.java | 7 +- .../org/stellar/anchor/sep24/Sep24Helper.java | 54 +++--- .../anchor/sep24/Sep24RefundPayment.java | 11 +- .../stellar/anchor/sep24/Sep24Refunds.java | 8 +- .../stellar/anchor/sep24/Sep24Service.java | 15 +- .../anchor/sep24/Sep24Transaction.java | 40 ++--- .../anchor/sep24/Sep24TransactionBuilder.java | 15 +- .../org/stellar/anchor/util/DateUtil.java | 13 +- .../org/stellar/anchor/util/SepHelper.java | 34 +++- .../anchor/sep24/PojoSep24Refunds.java | 2 +- .../anchor/sep24/PojoSep24Transaction.java | 23 +-- .../anchor/model/Sep24TransactionTest.kt | 6 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 53 +++--- .../org/stellar/anchor/util/DateUtilTest.kt | 3 +- .../org/stellar/anchor/util/SepHelperTest.kt | 34 ++++ .../integration-test.anchor-config.yaml | 2 +- .../anchor/platform/CallbackApiBeans.java | 47 ++++- .../platform/config/CallbackApiConfig.java | 2 + .../configurator/DataConfigAdapter.java | 2 +- .../platform/data/JdbcSep24RefundPayment.java | 15 -- .../platform/data/JdbcSep24Refunds.java | 41 +++++ .../platform/data/JdbcSep24Transaction.java | 165 ++++++++++++------ .../data/JdbcSep24TransactionRepo.java | 5 +- .../data/JdbcSep24TransactionStore.java | 37 ++-- .../platform/data/JdbcSep31Transaction.java | 6 +- .../data/PaymentObservingAccount.java | 4 + .../config/anchor-config-default-values.yaml | 4 + .../config/anchor-config-schema-v1.yaml | 1 + .../anchor/platform/config/Sep24ConfigTest.kt | 4 +- .../PaymentOperationToEventListenerTest.kt | 8 +- .../service/TransactionServiceTest.kt | 11 +- 34 files changed, 433 insertions(+), 256 deletions(-) create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidStellarAccountException.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index e7dc41e1eb..30c3792cf8 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -49,7 +49,7 @@ jobs: sep_validation_suite: needs: [build_and_test] runs-on: ubuntu-latest - name: Validate SEPs (1, 10, 12, 31, 38) + name: Validate SEPs (1, 10, 12, 24, 31, 38) env: PR_NUMBER: ${{github.event.pull_request.number}} BRANCH_NAME: ${{github.ref}} # e.g. refs/heads/main @@ -82,7 +82,7 @@ jobs: HOME_DOMAIN: ${{ steps.endpoint-finder.outputs.HOME_DOMAIN }} run: | npm install -g @stellar/anchor-tests - 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 + stellar-anchor-tests --home-domain $HOME_DOMAIN --seps 1 10 12 24 31 38 --sep-config platform/src/test/resources/stellar-anchor-tests-sep-config.json complete: if: always() diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/RateService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/RateService.java index 1c8d9980b9..9dd03e15aa 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/RateService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/RateService.java @@ -2,7 +2,8 @@ import static java.math.RoundingMode.HALF_DOWN; import static org.stellar.anchor.api.callback.GetRateRequest.Type.*; -import static org.stellar.anchor.util.MathHelper.*; +import static org.stellar.anchor.util.MathHelper.decimal; +import static org.stellar.anchor.util.MathHelper.formatAmount; import static org.stellar.anchor.util.SepHelper.validateAmount; import java.math.BigDecimal; @@ -153,8 +154,9 @@ private Quote createQuote( if (strExpiresAfter == null) { expiresAfter = Instant.now(); } else { - expiresAfter = Instant.ofEpochSecond(DateUtil.fromISO8601UTC(strExpiresAfter)); + expiresAfter = DateUtil.fromISO8601UTC(strExpiresAfter); } + ZonedDateTime expiresAt = ZonedDateTime.ofInstant(expiresAfter, ZoneId.of("UTC")) .plusDays(1) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidStellarAccountException.java b/api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidStellarAccountException.java new file mode 100644 index 0000000000..705db9d3e5 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/exception/InvalidStellarAccountException.java @@ -0,0 +1,7 @@ +package org.stellar.anchor.api.exception; + +public class InvalidStellarAccountException extends AnchorException { + public InvalidStellarAccountException(String message) { + super(message); + } +} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index 5a4ef64edf..1a78cf3e8d 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -1,6 +1,7 @@ package org.stellar.anchor.api.sep.sep24; import com.google.gson.annotations.SerializedName; +import java.time.Instant; import lombok.Data; @Data @@ -15,7 +16,7 @@ public class TransactionResponse { Integer status_eta; @SerializedName("more_info_url") - String moreInfoUrl = ""; + String moreInfoUrl = "http://www.stellar.org"; @SerializedName("amount_in") String amountIn = "0"; @@ -36,10 +37,10 @@ public class TransactionResponse { String amountFeeAsset; @SerializedName("started_at") - String startedAt = ""; + Instant startedAt = Instant.EPOCH; @SerializedName("completed_at") - String completedAt = ""; + Instant completedAt = Instant.EPOCH; @SerializedName("stellar_transaction_id") String stellarTransactionId = ""; diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java index 1bfc3305dd..0bd33e7a2c 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java @@ -5,6 +5,7 @@ import static org.stellar.anchor.util.Log.debugF; import static org.stellar.anchor.util.MathHelper.decimal; +import com.google.gson.Gson; import java.math.BigDecimal; import java.net.MalformedURLException; import java.net.URI; @@ -20,7 +21,7 @@ import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.Sep24Config; -import org.stellar.anchor.util.DateUtil; +import org.stellar.anchor.util.GsonUtils; public class Sep24Helper { private static final List needsMoreInfoUrlDeposit = @@ -42,6 +43,8 @@ public class Sep24Helper { PENDING_ANCHOR.toString(), PENDING_USER.toString()); + private static final Gson gson = GsonUtils.getInstance(); + public static String constructMoreInfoUrl( JwtService jwtService, Sep24Config sep24Config, Sep24Transaction txn, String lang) throws URISyntaxException, MalformedURLException { @@ -49,9 +52,7 @@ public static String constructMoreInfoUrl( JwtToken token = JwtToken.of( "moreInfoUrl", - (txn.getSep10AccountMemo() == null || txn.getSep10AccountMemo().length() == 0) - ? txn.getSep10Account() - : txn.getSep10Account() + ":" + txn.getSep10AccountMemo(), + txn.getSep10Account(), Instant.now().getEpochSecond(), Instant.now().getEpochSecond() + sep24Config.getInteractiveJwtExpiration(), txn.getTransactionId(), @@ -83,26 +84,16 @@ public static TransactionResponse fromDepositTxn( boolean allowMoreInfoUrl) throws MalformedURLException, URISyntaxException { - DepositTransactionResponse txnR = new DepositTransactionResponse(); - BeanUtils.copyProperties(txn, txnR); + DepositTransactionResponse txnR = + gson.fromJson(gson.toJson(txn), DepositTransactionResponse.class); + + setSharedTransactionResponseFields(txnR, txn); - txnR.setId(txn.getTransactionId()); - txnR.setDepositMemo(txn.getMemo()); - txnR.setDepositMemoType(txn.getMemoType()); - txnR.setFrom(txn.getFromAccount()); - txnR.setTo(txn.getToAccount()); txnR.setDepositMemo(txn.getMemo()); txnR.setDepositMemoType(txn.getMemoType()); - txnR.setStartedAt( - (txn.getStartedAt() == null) ? null : DateUtil.toISO8601UTC(txn.getStartedAt())); - txnR.setCompletedAt( - (txn.getCompletedAt() == null) ? null : DateUtil.toISO8601UTC(txn.getCompletedAt())); - if (allowMoreInfoUrl && needsMoreInfoUrlDeposit.contains(txn.getStatus())) { txnR.setMoreInfoUrl(constructMoreInfoUrl(jwtService, sep24Config, txn, lang)); - } else { - txnR.setMoreInfoUrl(null); } return txnR; @@ -116,16 +107,10 @@ public static WithdrawTransactionResponse fromWithdrawTxn( boolean allowMoreInfoUrl) throws MalformedURLException, URISyntaxException { - WithdrawTransactionResponse txnR = new WithdrawTransactionResponse(); - BeanUtils.copyProperties(txn, txnR); + WithdrawTransactionResponse txnR = + gson.fromJson(gson.toJson(txn), WithdrawTransactionResponse.class); - txnR.setStartedAt( - (txn.getStartedAt() == null) ? null : DateUtil.toISO8601UTC(txn.getStartedAt())); - txnR.setCompletedAt( - (txn.getCompletedAt() == null) ? null : DateUtil.toISO8601UTC(txn.getCompletedAt())); - txnR.setId(txn.getTransactionId()); - txnR.setFrom(txn.getFromAccount()); - txnR.setTo(txn.getToAccount()); + setSharedTransactionResponseFields(txnR, txn); txnR.setWithdrawMemo(txn.getMemo()); txnR.setWithdrawMemoType(txn.getMemoType()); @@ -133,18 +118,27 @@ public static WithdrawTransactionResponse fromWithdrawTxn( if (allowMoreInfoUrl && needsMoreInfoUrlWithdraw.contains(txn.getStatus())) { txnR.setMoreInfoUrl(constructMoreInfoUrl(jwtService, sep24Config, txn, lang)); - } else { - txnR.setMoreInfoUrl(null); } return txnR; } + private static void setSharedTransactionResponseFields( + TransactionResponse txnR, Sep24Transaction txn) { + txnR.setId(txn.getTransactionId()); + if (txn.getFromAccount() != null) txnR.setFrom(txn.getFromAccount()); + if (txn.getToAccount() != null) txnR.setTo(txn.getToAccount()); + if (txn.getStartedAt() != null) txnR.setStartedAt(txn.getStartedAt()); + if (txn.getCompletedAt() != null) txnR.setCompletedAt(txn.getCompletedAt()); + } + public static TransactionResponse updateRefundInfo( TransactionResponse response, Sep24Transaction txn, AssetInfo assetInfo) { debugF("Calculating refund information"); - List refundPayments = txn.getRefundPayments(); + if (txn.getRefunds() == null) return response; + + List refundPayments = txn.getRefunds().getRefundPayments(); response.setRefunded(false); BigDecimal totalAmount = BigDecimal.ZERO; BigDecimal totalFee = BigDecimal.ZERO; diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java index 91bf94e870..eacee4fb6f 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java @@ -11,13 +11,7 @@ public interface Sep24RefundPayment { */ String getId(); - /** - * stellar or external. - * - * @return the type. - */ - String getIdType(); - + void setId(String id); /** * The amount sent back to the user for the payment identified by id, in units of amount_in_asset. * @@ -25,10 +19,13 @@ public interface Sep24RefundPayment { */ String getAmount(); + void setAmount(String amount); /** * The amount charged as a fee for processing the refund, in units of amount_in_asset. * * @return the fee. */ String getFee(); + + void setFee(String fee); } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java index f25c894517..92508e1984 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java @@ -6,7 +6,13 @@ public interface Sep24Refunds { String getAmountRefunded(); + void setAmountRefunded(String amountRefunded); + String getAmountFee(); - List getPayments(); + void setAmountFee(String amountFee); + + List getRefundPayments(); + + void setRefundPayments(List refundPayments); } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 800390263a..0fcc981752 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -129,9 +129,8 @@ public InteractiveTransactionResponse withdraw( .amountOut(strAmount) .assetCode(assetCode) .assetIssuer(withdrawRequest.get("asset_issuer")) - .startedAt(Instant.now().getEpochSecond()) + .startedAt(Instant.now()) .sep10Account(token.getAccount()) - .sep10AccountMemo(token.getAccountMemo()) .fromAccount(sourceAccount) .clientDomain(token.getClientDomain()); @@ -167,14 +166,14 @@ public InteractiveTransactionResponse withdraw( public InteractiveTransactionResponse deposit( String fullRequestUrl, JwtToken token, Map depositRequest) throws SepException, MalformedURLException, URISyntaxException { - info("Creating withdrawal transaction."); + info("Creating deposit transaction."); if (token == null) { info("missing SEP-10 token"); throw new SepValidationException("missing token"); } if (depositRequest == null) { - info("missing withdraw request"); + info("missing deposit request"); throw new SepValidationException("no request"); } @@ -249,9 +248,8 @@ public InteractiveTransactionResponse deposit( .amountOut(strAmount) .assetCode(assetCode) .assetIssuer(depositRequest.get("asset_issuer")) - .startedAt(Instant.now().getEpochSecond()) + .startedAt(Instant.now()) .sep10Account(token.getAccount()) - .sep10AccountMemo(token.getAccountMemo()) .toAccount(destinationAccount) .clientDomain(token.getClientDomain()) .claimableBalanceSupported(claimableSupported); @@ -349,7 +347,7 @@ public GetTransactionResponse findTransaction(JwtToken token, GetTransactionRequ // If the token has a memo, make sure the transaction belongs to the account with the same memo. if (token.getAccountMemo() != null - && !token.getAccountMemo().equals(txn.getSep10AccountMemo())) { + && txn.getSep10Account().equals(token.getAccount() + ":" + token.getAccountMemo())) { infoF( "no transactions found with account:{} memo:{}", token.getAccount(), @@ -396,7 +394,8 @@ TransactionResponse fromTxn(Sep24Transaction txn, String lang) } // Calculate refund information. - AssetInfo assetInfo = assetService.getAsset(txn.getAssetCode(), txn.getAssetIssuer()); + AssetInfo assetInfo = + assetService.getAsset(txn.getRequestAssetCode(), txn.getRequestAssetIssuer()); return Sep24Helper.updateRefundInfo(response, txn, assetInfo); } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java index 33e7ca4f85..e9a0897bfc 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Transaction.java @@ -1,6 +1,6 @@ package org.stellar.anchor.sep24; -import java.util.List; +import java.time.Instant; @SuppressWarnings("unused") public interface Sep24Transaction { @@ -66,9 +66,9 @@ public interface Sep24Transaction { * * @return The started_at field of the SEP-24 transaction history. */ - Long getStartedAt(); + Instant getStartedAt(); - void setStartedAt(Long startedAt); + void setStartedAt(Instant startedAt); /** * The date and time of transaction reaching completed or refunded @@ -76,18 +76,18 @@ public interface Sep24Transaction { * * @return completed field of the SEP-24 transaction history. */ - Long getCompletedAt(); + Instant getCompletedAt(); - void setCompletedAt(Long completedAt); + void setCompletedAt(Instant completedAt); /** * The code of the asset of interest. E.g. BTC, ETH, USD, INR, etc. * * @return asset_code field of the SEP-24 transaction history. */ - String getAssetCode(); + String getRequestAssetCode(); - void setAssetCode(String assetCode); + void setRequestAssetCode(String assetCode); /** * The issuer of the stellar asset the user wants to receive for their deposit with the anchor. If @@ -96,9 +96,9 @@ public interface Sep24Transaction { * * @return the asset issuer of the transaction's asset_code . */ - String getAssetIssuer(); + String getRequestAssetIssuer(); - void setAssetIssuer(String assetIssuer); + void setRequestAssetIssuer(String assetIssuer); /** * The Stellar account used to authenticate SEP-10;. @@ -109,15 +109,6 @@ public interface Sep24Transaction { void setSep10Account(String sep10Account); - /** - * The memo used to authenticate SEP-10. This is for pooled/omnibus account authentication. - * - * @return the memo. - */ - String getSep10AccountMemo(); - - void setSep10AccountMemo(String sep10AccountMemo); - /** * If this is a withdrawal, this is the anchor's Stellar account that the user transferred (or * will transfer) their issued asset to. @@ -253,14 +244,13 @@ public interface Sep24Transaction { void setAmountFeeAsset(String amountFeeAsset); - /** - * The list of refund payments associated with the transaction. - * - * @return the refund payments - */ - List getRefundPayments(); + Boolean getRefunded(); + + void setRefunded(Boolean refunded); + + Sep24Refunds getRefunds(); - void setRefundPayments(List payments); + void setRefunds(Sep24Refunds refunds); enum Kind { DEPOSIT("deposit"), diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java index 6b9235208b..e2d13889ab 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionBuilder.java @@ -1,5 +1,7 @@ package org.stellar.anchor.sep24; +import java.time.Instant; + @SuppressWarnings("unused") public class Sep24TransactionBuilder { final Sep24Transaction txn; @@ -24,21 +26,21 @@ public Sep24TransactionBuilder kind(String kind) { } public Sep24TransactionBuilder assetCode(String assetCode) { - txn.setAssetCode(assetCode); + txn.setRequestAssetCode(assetCode); return this; } public Sep24TransactionBuilder assetIssuer(String assetIssuer) { - txn.setAssetIssuer(assetIssuer); + txn.setRequestAssetIssuer(assetIssuer); return this; } - public Sep24TransactionBuilder startedAt(long time) { + public Sep24TransactionBuilder startedAt(Instant time) { txn.setStartedAt(time); return this; } - public Sep24TransactionBuilder completedAt(long time) { + public Sep24TransactionBuilder completedAt(Instant time) { txn.setCompletedAt(time); return this; } @@ -48,11 +50,6 @@ public Sep24TransactionBuilder sep10Account(String stellarAccount) { return this; } - public Sep24TransactionBuilder sep10AccountMemo(String accountMemo) { - txn.setSep10AccountMemo(accountMemo); - return this; - } - public Sep24TransactionBuilder withdrawAnchorAccount(String withdrawAnchorAccount) { txn.setWithdrawAnchorAccount(withdrawAnchorAccount); return this; diff --git a/core/src/main/java/org/stellar/anchor/util/DateUtil.java b/core/src/main/java/org/stellar/anchor/util/DateUtil.java index 5bfaa616ab..6c1956889a 100644 --- a/core/src/main/java/org/stellar/anchor/util/DateUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/DateUtil.java @@ -9,12 +9,11 @@ public class DateUtil { /** * From epoch seconds to UTC ISO8601 string. * - * @param epochSeconds the epoch seconds + * @param instant the Instant object * @return The string in ISO8601 format. */ - public static String toISO8601UTC(long epochSeconds) { - ZonedDateTime zdt = - ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), ZoneOffset.UTC); + public static String toISO8601UTC(Instant instant) { + ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC); return zdt.format(DateTimeFormatter.ISO_INSTANT); } @@ -22,10 +21,10 @@ public static String toISO8601UTC(long epochSeconds) { * From ISO 8601 string to epoch seconds. * * @param str The string in ISO8601 format. - * @return the epoch seconds + * @return the Instant */ - public static long fromISO8601UTC(String str) { + public static Instant fromISO8601UTC(String str) { ZonedDateTime dt = ZonedDateTime.parse(str); - return dt.toEpochSecond(); + return dt.toInstant(); } } 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 f5cbd69ff2..087d9ec9d8 100644 --- a/core/src/main/java/org/stellar/anchor/util/SepHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/SepHelper.java @@ -9,8 +9,11 @@ import java.util.UUID; import org.stellar.anchor.api.exception.AnchorException; import org.stellar.anchor.api.exception.BadRequestException; +import org.stellar.anchor.api.exception.InvalidStellarAccountException; import org.stellar.anchor.api.sep.SepTransactionStatus; -import org.stellar.sdk.xdr.MemoType; +import org.stellar.sdk.AccountConverter; +import org.stellar.sdk.KeyPair; +import org.stellar.sdk.xdr.*; public class SepHelper { /** @@ -45,6 +48,35 @@ public static String memoTypeString(MemoType memoType) { return result; } + /** + * Retrieves the memo of the account. + * + * @param strAccount + * @return If the account is in the format of 1) G..., returns null 2) G...:memo, returns the memo + * 3) M..., returns null + */ + public static String getAccountMemo(String strAccount) throws InvalidStellarAccountException { + String[] tokens = strAccount.split(":"); + switch (tokens.length) { + case 1: + AccountConverter accountConverter; + if (tokens[0].startsWith("G")) { + accountConverter = AccountConverter.disableMuxed(); + } else { + accountConverter = AccountConverter.enableMuxed(); + } + // Check if the account is a valid G... or M... + accountConverter.encode(tokens[0]); + return null; + case 2: + KeyPair.fromAccountId(tokens[0]); + return tokens[1]; + default: + throw new InvalidStellarAccountException( + String.format("Invalid stellar account: %s", strAccount)); + } + } + public static boolean amountEquals(String amount1, String amount2) { return decimal(amount1).compareTo(decimal(amount2)) == 0; } diff --git a/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Refunds.java b/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Refunds.java index 36bd411882..9db8492e5a 100644 --- a/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Refunds.java +++ b/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Refunds.java @@ -7,5 +7,5 @@ public class PojoSep24Refunds implements Sep24Refunds { String amountRefunded; String amountFee; - List payments; + List refundPayments; } diff --git a/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java b/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java index 5f59057918..d390c7be15 100644 --- a/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep24/PojoSep24Transaction.java @@ -1,7 +1,6 @@ package org.stellar.anchor.sep24; -import java.util.ArrayList; -import java.util.List; +import java.time.Instant; import lombok.Data; @Data @@ -13,12 +12,11 @@ public class PojoSep24Transaction implements Sep24Transaction { String externalTransactionId; String status; String kind; - Long startedAt; - Long completedAt; - String assetCode; - String assetIssuer; + Instant startedAt; + Instant completedAt; + String requestAssetCode; + String requestAssetIssuer; String sep10Account; - String sep10AccountMemo; String withdrawAnchorAccount; String fromAccount; String toAccount; @@ -33,14 +31,7 @@ public class PojoSep24Transaction implements Sep24Transaction { String amountInAsset; String amountOutAsset; String amountFeeAsset; - String muxedAccount; - private List refundPayments; - @Override - public void setRefundPayments(List payments) { - refundPayments = new ArrayList<>(payments.size()); - payments.stream() - .filter(p -> p instanceof PojoSep24RefundPayment) - .forEach(fp -> refundPayments.add((PojoSep24RefundPayment) fp)); - } + Boolean refunded; + Sep24Refunds refunds; } 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 55d01d6a9d..f15b4fe05a 100644 --- a/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/model/Sep24TransactionTest.kt @@ -2,6 +2,7 @@ package org.stellar.anchor.model import io.mockk.every import io.mockk.mockk +import java.time.Instant import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.stellar.anchor.sep24.PojoSep24Transaction @@ -14,10 +15,11 @@ internal class Sep24TransactionTest { val store = mockk() every { store.newInstance() } returns PojoSep24Transaction() val builder = Sep24TransactionBuilder(store) + val instantNow = Instant.now() var txn = builder .transactionId("txnId") - .completedAt(10) + .completedAt(instantNow) .withdrawAnchorAccount("account") .memo("memo") .amountFee("20") @@ -27,7 +29,7 @@ internal class Sep24TransactionTest { .build() assertEquals(txn.transactionId, "txnId") - assertEquals(txn.completedAt, 10) + assertEquals(txn.completedAt, instantNow) assertEquals(txn.withdrawAnchorAccount, "account") assertEquals(txn.memo, "memo") assertEquals(txn.amountFee, "20") 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 185d667105..1c01d14686 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -6,6 +6,7 @@ import io.mockk.* import io.mockk.impl.annotations.MockK import java.net.URI import java.nio.charset.Charset +import java.time.Instant import org.apache.http.client.utils.URLEncodedUtils import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.* @@ -36,7 +37,6 @@ 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 import org.stellar.anchor.util.MemoHelper.makeMemo import org.stellar.sdk.MemoHash @@ -46,19 +46,18 @@ import org.stellar.sdk.MemoText internal class Sep24ServiceTest { companion object { const val TEST_SEP24_INTERACTIVE_URL = "https://test-anchor.stellar.org" + val TEST_STARTED_AT: Instant = Instant.now() + val TEST_COMPLETED_AT: Instant = Instant.now().plusSeconds(100) } @MockK(relaxed = true) lateinit var appConfig: AppConfig @MockK(relaxed = true) lateinit var secretConfig: SecretConfig - @MockK(relaxed = true) lateinit var sep24Config: Sep24Config + @MockK(relaxed = true) private lateinit var txnStore: Sep24TransactionStore private val assetService: AssetService = ResourceJsonAssetService("test_assets.json") private lateinit var jwtService: JwtService - - @MockK(relaxed = true) private lateinit var txnStore: Sep24TransactionStore - private lateinit var sep24Service: Sep24Service private val gson = GsonUtils.getInstance() @@ -111,9 +110,9 @@ internal class Sep24ServiceTest { assertEquals(slotTxn.captured.status, "incomplete") assertEquals(slotTxn.captured.kind, "withdrawal") - assertEquals(slotTxn.captured.assetCode, "USDC") + assertEquals(slotTxn.captured.requestAssetCode, "USDC") assertEquals( - slotTxn.captured.assetIssuer, + slotTxn.captured.requestAssetIssuer, "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" ) assertEquals(slotTxn.captured.sep10Account, TEST_ACCOUNT) @@ -236,8 +235,8 @@ internal class Sep24ServiceTest { assertEquals(slotTxn.captured.status, "incomplete") assertEquals(slotTxn.captured.kind, "deposit") - assertEquals(slotTxn.captured.assetCode, TEST_ASSET) - assertEquals(slotTxn.captured.assetIssuer, TEST_ASSET_ISSUER_ACCOUNT_ID) + assertEquals(slotTxn.captured.requestAssetCode, TEST_ASSET) + assertEquals(slotTxn.captured.requestAssetIssuer, TEST_ASSET_ISSUER_ACCOUNT_ID) assertEquals(slotTxn.captured.sep10Account, TEST_ACCOUNT) assertEquals(slotTxn.captured.toAccount, TEST_ACCOUNT) assertEquals(slotTxn.captured.clientDomain, TEST_CLIENT_DOMAIN) @@ -331,14 +330,14 @@ internal class Sep24ServiceTest { assertEquals(response.transactions[0].id, TEST_TRANSACTION_ID_0) assertEquals(response.transactions[0].status, "incomplete") assertEquals(response.transactions[0].kind, kind) - assertEquals(response.transactions[0].startedAt, DateUtil.toISO8601UTC(1000)) - assertEquals(response.transactions[0].completedAt, DateUtil.toISO8601UTC(2000)) + assertEquals(response.transactions[0].startedAt, TEST_STARTED_AT) + assertEquals(response.transactions[0].completedAt, TEST_COMPLETED_AT) assertEquals(response.transactions[1].id, TEST_TRANSACTION_ID_1) assertEquals(response.transactions[1].status, "completed") assertEquals(response.transactions[1].kind, kind) - assertEquals(response.transactions[1].startedAt, DateUtil.toISO8601UTC(1000)) - assertEquals(response.transactions[1].completedAt, DateUtil.toISO8601UTC(2000)) + assertEquals(response.transactions[1].startedAt, TEST_STARTED_AT) + assertEquals(response.transactions[1].completedAt, TEST_COMPLETED_AT) } @ParameterizedTest @@ -375,8 +374,8 @@ internal class Sep24ServiceTest { assertEquals(response.transaction.id, TEST_TRANSACTION_ID_0) assertEquals(response.transaction.status, "incomplete") assertEquals(response.transaction.kind, kind) - assertEquals(response.transaction.startedAt, DateUtil.toISO8601UTC(1000)) - assertEquals(response.transaction.completedAt, DateUtil.toISO8601UTC(2000)) + assertEquals(response.transaction.startedAt, TEST_STARTED_AT) + assertEquals(response.transaction.completedAt, TEST_COMPLETED_AT) verify(exactly = 1) { txnStore.findByTransactionId(TEST_TRANSACTION_ID_0) } // test with stellar transaction Id @@ -466,11 +465,11 @@ internal class Sep24ServiceTest { txn.transactionId = TEST_TRANSACTION_ID_0 txn.status = "incomplete" txn.kind = kind - txn.startedAt = 1000 - txn.completedAt = 2000 + txn.startedAt = TEST_STARTED_AT + txn.completedAt = TEST_COMPLETED_AT - txn.assetCode = TEST_ASSET - txn.assetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID + txn.requestAssetCode = TEST_ASSET + txn.requestAssetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID txn.sep10Account = TEST_ACCOUNT txn.toAccount = TEST_ACCOUNT txn.fromAccount = TEST_ACCOUNT @@ -489,11 +488,11 @@ internal class Sep24ServiceTest { txn.transactionId = TEST_TRANSACTION_ID_0 txn.status = "incomplete" txn.kind = kind - txn.startedAt = 1000 - txn.completedAt = 2000 + txn.startedAt = TEST_STARTED_AT + txn.completedAt = TEST_COMPLETED_AT - txn.assetCode = TEST_ASSET - txn.assetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID + txn.requestAssetCode = TEST_ASSET + txn.requestAssetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID txn.sep10Account = TEST_ACCOUNT txn.toAccount = TEST_ACCOUNT txn.fromAccount = TEST_ACCOUNT @@ -507,11 +506,11 @@ internal class Sep24ServiceTest { txn.transactionId = TEST_TRANSACTION_ID_1 txn.status = "completed" txn.kind = kind - txn.startedAt = 1000 - txn.completedAt = 2000 + txn.startedAt = TEST_STARTED_AT + txn.completedAt = TEST_COMPLETED_AT - txn.assetCode = TEST_ASSET - txn.assetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID + txn.requestAssetCode = TEST_ASSET + txn.requestAssetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID txn.sep10Account = TEST_ACCOUNT txn.toAccount = TEST_ACCOUNT txn.fromAccount = TEST_ACCOUNT 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 5cf0d0464d..f6bf551761 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/DateUtilTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/DateUtilTest.kt @@ -1,5 +1,6 @@ package org.stellar.anchor.util +import java.time.Instant import java.time.format.DateTimeParseException import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test @@ -7,7 +8,7 @@ import org.junit.jupiter.api.Test internal class DateUtilTest { @Test fun `test fromISO8601UTC`() { - val t1 = System.currentTimeMillis() / 1000 + val t1 = Instant.now() val t2 = DateUtil.fromISO8601UTC(DateUtil.toISO8601UTC(t1)) assert(t1 == t2) } 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 053c8b3389..e113b217f6 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/SepHelperTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/SepHelperTest.kt @@ -1,6 +1,11 @@ package org.stellar.anchor.util +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource +import org.junit.jupiter.params.provider.ValueSource import org.stellar.sdk.xdr.MemoType internal class SepHelperTest { @@ -12,4 +17,33 @@ internal class SepHelperTest { assert(SepHelper.memoTypeString(MemoType.MEMO_NONE).equals("none")) assert(SepHelper.memoTypeString(MemoType.MEMO_RETURN).equals("return")) } + + @ParameterizedTest + @CsvSource( + value = + [ + "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG,", + "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG:1234,1234", + "MCEO75Y6YKE53HM6N46IJYH3LK3YYFZ4QWGNUKCSSIQSH3KOAD7BEAAAAAAAAAAAPNT2W," + ] + ) + fun `test valid stellar account`(strAccount: String, expectedMemo: String?) { + val gotMemo = SepHelper.getAccountMemo(strAccount) + assertEquals(gotMemo, expectedMemo) + } + + @ParameterizedTest + @ValueSource( + strings = + [ + "ABC", + "ABC:123", + "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKL__", + "MCEO75Y6YKE53HM6N46IJYH3LK3YYFZ4QWGNUKCSSIQSH3KOAD7BEAAAAAAAAAAAPNT2W___", + "AMCEO75Y6YKE53HM6N46IJYH3LK3YYFZ4QWGNUKCSSIQSH3KOAD7BEAAAAAAAAAAAPNT2W" + ] + ) + fun `test invalid stellar account`(strAccount: String) { + assertThrows { SepHelper.getAccountMemo(strAccount) } + } } 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 9a3cc6bbb7..07929a0e95 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -217,7 +217,7 @@ assets: "iso4217:USD" ] }, - "sep24_enabled": false, + "sep24_enabled": true, "sep31_enabled": true, "sep38_enabled": true }, diff --git a/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java b/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java index 42509b8d04..9654967859 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java @@ -1,8 +1,14 @@ package org.stellar.anchor.platform; import com.google.gson.Gson; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; +import okhttp3.OkHttpClient.Builder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.stellar.anchor.api.callback.CustomerIntegration; @@ -20,15 +26,42 @@ @Configuration public class CallbackApiBeans { + TrustManager[] trustAllCerts = + new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted( + java.security.cert.X509Certificate[] chain, String authType) {} + + @Override + public void checkServerTrusted( + java.security.cert.X509Certificate[] chain, String authType) {} + + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[] {}; + } + } + }; @Bean - OkHttpClient httpClient() { - return new OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.MINUTES) - .readTimeout(10, TimeUnit.MINUTES) - .writeTimeout(10, TimeUnit.MINUTES) - .callTimeout(10, TimeUnit.MINUTES) - .build(); + OkHttpClient httpClient(CallbackApiConfig callbackApiConfig) + throws NoSuchAlgorithmException, KeyManagementException { + Builder builder = + new Builder() + .connectTimeout(10, TimeUnit.MINUTES) + .readTimeout(10, TimeUnit.MINUTES) + .writeTimeout(10, TimeUnit.MINUTES) + .callTimeout(10, TimeUnit.MINUTES); + + if (!callbackApiConfig.getCheckCertificate()) { + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); + builder + .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) trustAllCerts[0]) + .hostnameVerifier((hostname, session) -> true); + } + return builder.build(); } @Bean 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 index 7f6a2c4a37..0573524426 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/CallbackApiConfig.java @@ -17,6 +17,8 @@ public class CallbackApiConfig implements Validator { String baseUrl; + Boolean checkCertificate; + AuthInfo auth; PropertySecretConfig secretConfig; 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 645c85a360..2e6c613355 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 @@ -97,7 +97,7 @@ 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"); + set("spring.jpa.hibernate.ddl-auto", "update"); copy(config, "data.url", "spring.datasource.url"); set("spring.datasource.username", SecretManager.getInstance().get("secret.data.username")); set("spring.datasource.password", SecretManager.getInstance().get("secret.data.password")); diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java index d9fd495dc0..3d60b84f74 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java @@ -1,7 +1,5 @@ package org.stellar.anchor.platform.data; -import com.fasterxml.jackson.annotation.JsonIgnore; -import java.util.UUID; import javax.persistence.*; import lombok.Getter; import lombok.Setter; @@ -9,21 +7,8 @@ @Getter @Setter -@Entity -@Table(name = "sep24_refund_payment") public class JdbcSep24RefundPayment implements Sep24RefundPayment { - @Id - @GeneratedValue - @Column(name = "sep24_refund_payment_id") - UUID jdbcId; - - @JsonIgnore - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "transaction_id", nullable = false) - JdbcSep24Transaction transaction; - String id; - String idType; String amount; String fee; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java new file mode 100644 index 0000000000..d3a376c837 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java @@ -0,0 +1,41 @@ +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.sep24.Sep24RefundPayment; +import org.stellar.anchor.sep24.Sep24Refunds; + +@Data +public class JdbcSep24Refunds implements Sep24Refunds { + @SerializedName("amount_refunded") + String amountRefunded; + + @SerializedName("amount_fee") + String amountFee; + + @SerializedName("payments") + List refundPayments; + + @Override + public List getRefundPayments() { + if (refundPayments == null) return null; + // getPayments() is made for Gson serialization. + List payments = new ArrayList<>(refundPayments.size()); + payments.addAll(refundPayments); + return payments; + } + + @Override + public void setRefundPayments(List refundPayments) { + this.refundPayments = new ArrayList<>(refundPayments.size()); + for (Sep24RefundPayment rp : refundPayments) { + if (rp instanceof JdbcSep24RefundPayment) + this.refundPayments.add((JdbcSep24RefundPayment) rp); + else + throw new ClassCastException( + String.format("Error casting %s to JdbcSep24RefundPayment", rp.getClass())); + } + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index b3b573c001..5dc315ddbf 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -1,99 +1,148 @@ package org.stellar.anchor.platform.data; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import com.vladmihalcea.hibernate.type.json.JsonType; +import java.time.Instant; import javax.persistence.*; import lombok.Getter; import lombok.Setter; -import org.stellar.anchor.sep24.Sep24RefundPayment; +import org.hibernate.annotations.TypeDef; +import org.stellar.anchor.sep24.Sep24Refunds; import org.stellar.anchor.sep24.Sep24Transaction; +import org.stellar.anchor.util.GsonUtils; @Getter @Setter @Entity +@Access(AccessType.FIELD) @Table(name = "sep24_transaction") +@TypeDef(name = "json", typeClass = JsonType.class) public class JdbcSep24Transaction implements Sep24Transaction, SepTransaction { - @Id - @GeneratedValue - @Column(name = "sep_transaction_id") - UUID jdbcId; + static Gson gson = GsonUtils.getInstance(); - String id; - - String transactionId; - - String stellarTransactionId; - - String externalTransactionId; - - String status; + @Id String id; String kind; - Long startedAt; + String status; - Long completedAt; + @SerializedName("status_eta") + String statusEta; - String assetCode; // * + @SerializedName("kyc_verified") + String kycVerified; - String assetIssuer; // * + @SerializedName("more_info_url") + String moreInfoUrl; - String sep10Account; // * + @SerializedName("amount_in") + String amountIn; - String sep10AccountMemo; // * + @SerializedName("amount_in_asset") + String amountInAsset; - String withdrawAnchorAccount; + @SerializedName("amount_out") + String amountOut; - String fromAccount; // * + @SerializedName("amount_out_asset") + String amountOutAsset; - String toAccount; // * + @SerializedName("amount_fee") + String amountFee; - String memoType; + @SerializedName("amount_fee_asset") + String amountFeeAsset; - String memo; + @SerializedName("started_at") + Instant startedAt; - String clientDomain; + @SerializedName("completed_at") + Instant completedAt; - Boolean claimableBalanceSupported; + @SerializedName("transaction_id") + String transactionId; - String amountIn; + @SerializedName("stellar_transaction_id") + String stellarTransactionId; - String amountOut; + @SerializedName("external_transaction_id") + String externalTransactionId; - String amountFee; + String message; - String amountInAsset; + Boolean refunded; - String amountOutAsset; + // Ignored by JPA + @Transient Sep24Refunds refunds; - String amountFeeAsset; + @Access(AccessType.PROPERTY) + @Column(name = "refunds") + public String getRefundsJson() { + return gson.toJson(this.refunds); + } - String muxedAccount; + public void setRefundsJson(String refundsJson) { + if (refundsJson != null) { + this.refunds = gson.fromJson(refundsJson, JdbcSep24Refunds.class); + } + } - @OneToMany(fetch = FetchType.LAZY, mappedBy = "transaction") - List refundPayments; + /** + * If this is a withdrawal, this is the anchor's Stellar account that the user transferred (or + * will transfer) their issued asset to. + */ + @SerializedName("withdraw_anchor_account") + String withdrawAnchorAccount; - @Override - public List getRefundPayments() { - return refundPayments; - } + /** The memo for deposit or withdraw */ + String memo; - @Override - public void setRefundPayments(List payments) { - refundPayments = new ArrayList<>(payments.size()); - payments.stream() - .filter(p -> p instanceof JdbcSep24RefundPayment) - .forEach(fp -> refundPayments.add((JdbcSep24RefundPayment) fp)); - } + /** The memo type of the transaction */ + @SerializedName("memo_type") + String memoType; - @Override - public String getId() { - return jdbcId.toString(); - } + /** + * Sent from address. + * + *

In a deposit transaction, this would be a non-stellar account such as, BTC, IBAN, or bank + * account. + * + *

In a withdrawal transaction, this would be the stellar account the assets were withdrawn + * from. + */ + @SerializedName("from_account") + String fromAccount; + + /** + * Sent to address. + * + *

In a deposit transaction, this would be a stellar account the assets were deposited to. + * + *

In a withdrawal transaction, this would be the non-stellar account such as BTC, IBAN, or + * bank account. + */ + @SerializedName("to_account") + String toAccount; + + @SerializedName("request_asset_code") + String requestAssetCode; + + @SerializedName("request_asset_issuer") + String requestAssetIssuer; + + /** + * The SEP10 account used for authentication. + * + *

The account can be in the format of 1) stellar_account (G...) 2) stellar_account:memo + * (G...:2810101841641761712) 3) muxed account (M...) + */ + @SerializedName("sep10_account") + String sep10Account; + + @SerializedName("client_domain") + String clientDomain; - @Override - public void setId(String id) { - jdbcId = UUID.fromString(id); - } + @SerializedName("claimable_balance_supported") + Boolean claimableBalanceSupported; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java index 652d3c16b6..e3f6646114 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java @@ -15,9 +15,6 @@ public interface JdbcSep24TransactionRepo extends CrudRepository findBySep10AccountAndAssetCodeOrderByStartedAtDesc( + List findBySep10AccountAndRequestAssetCodeOrderByStartedAtDesc( String stellarAccount, String assetCode); - - List findBySep10AccountAndSep10AccountMemoAndAssetCodeOrderByStartedAtDesc( - String stellarAccount, String accountMemo, String assetCode); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java index ec55d0aac3..5b415415f3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java @@ -1,5 +1,6 @@ package org.stellar.anchor.platform.data; +import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.List; import java.util.stream.Collectors; @@ -42,14 +43,12 @@ public Sep24Transaction findByExternalTransactionId(String externalTransactionId public List findTransactions( String accountId, String accountMemo, GetTransactionsRequest tr) throws SepValidationException { - List txns; - if (accountMemo == null) - txns = - txnRepo.findBySep10AccountAndAssetCodeOrderByStartedAtDesc(accountId, tr.getAssetCode()); - else - txns = - txnRepo.findBySep10AccountAndSep10AccountMemoAndAssetCodeOrderByStartedAtDesc( - accountId, accountMemo, tr.getAssetCode()); + + if (accountMemo != null) accountId = accountId + ":" + accountMemo; + + List txns = + txnRepo.findBySep10AccountAndRequestAssetCodeOrderByStartedAtDesc( + accountId, tr.getAssetCode()); // TODO: This should be replaced by Couchbase query int limit = Integer.MAX_VALUE; @@ -57,23 +56,17 @@ public List findTransactions( limit = tr.getLimit(); } - final long noOlderThan; - final long olderThan; + Instant noOlderThan = Instant.EPOCH; + Instant olderThan = Instant.now(); if (tr.getPagingId() != null) { Sep24Transaction txn = txnRepo.findOneByTransactionId(tr.getPagingId()); - if (txn == null) { - olderThan = System.currentTimeMillis() / 1000; - } else { + if (txn != null) { olderThan = txn.getStartedAt(); } - } else { - olderThan = System.currentTimeMillis() / 1000; } - if (tr.getNoOlderThan() == null) { - noOlderThan = 0L; - } else { + if (tr.getNoOlderThan() != null) { try { noOlderThan = DateUtil.fromISO8601UTC(tr.getNoOlderThan()); } catch (DateTimeParseException dtpex) { @@ -82,13 +75,17 @@ public List findTransactions( } } + final Instant finalNoOlderThan = noOlderThan; + final Instant finalOlderThan = olderThan; + txns = txns.stream() .filter(txn -> (tr.getKind() == null || tr.getKind().equals(txn.getKind()))) - .filter(txn -> (txn.getStartedAt() >= noOlderThan - 1)) - .filter(txn -> (txn.getStartedAt() < olderThan - 1)) + .filter(txn -> (txn.getStartedAt().isAfter(finalNoOlderThan))) + .filter(txn -> (txn.getStartedAt().isBefore(finalOlderThan))) .limit(limit) .collect(Collectors.toList()); + return txns; } 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 dfb14ec191..e41ffbd3c0 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 @@ -8,7 +8,8 @@ import java.util.List; import java.util.Map; import javax.persistence.*; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.stellar.anchor.api.sep.AssetInfo; @@ -19,7 +20,8 @@ import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.util.GsonUtils; -@Data +@Getter +@Setter @Entity @Access(AccessType.FIELD) @Table(name = "sep31_transaction") 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 index 44d1524773..aae84e376d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java @@ -1,16 +1,20 @@ package org.stellar.anchor.platform.data; +import com.vladmihalcea.hibernate.type.json.JsonType; import java.time.Instant; import javax.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.TypeDef; @Getter @Setter @Entity +@Access(AccessType.FIELD) @NoArgsConstructor @Table(name = "stellar_payment_observing_account") +@TypeDef(name = "json", typeClass = JsonType.class) public class PaymentObservingAccount { public PaymentObservingAccount(String account, Instant lastObserved) { this.account = account; 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 d79bc650ae..f5a4043a8c 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -49,6 +49,9 @@ callback_api: # base_url: http://localhost:8081 + ## If the flag is set to false, all certificates from the business servers will be trusted. + check_certificate: false + ## 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. @@ -500,6 +503,7 @@ data: # url: jdbc:h2:mem:anchor-platform + ## @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 2d52520b79..676b7bfb2e 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -3,6 +3,7 @@ assets.value: callback_api.auth.expiration_milliseconds: callback_api.auth.type: callback_api.base_url: +callback_api.check_certificate: data.ddl_auto: data.flyway_enabled: data.flyway_location: diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt index a62cef147c..c12b14e856 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt @@ -40,8 +40,8 @@ class Sep24ConfigTest { @ParameterizedTest @ValueSource(ints = [-1, Integer.MIN_VALUE, 0]) - fun `test bad interactive jwt expiration`(expiration: Integer) { - config.setInteractiveJwtExpiration(expiration.toInt()) + fun `test bad interactive jwt expiration`(expiration: Int) { + config.setInteractiveJwtExpiration(expiration) config.validate(config, errors) assertTrue(errors.hasErrors()) assertErrorCode(errors, "sep24-interactive-url-invalid") 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 f2733cf721..465202af6c 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 @@ -6,10 +6,12 @@ import io.mockk.* 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.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode.STRICT import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.sep.SepTransactionStatus import org.stellar.anchor.api.shared.* @@ -244,7 +246,7 @@ class PaymentOperationToEventListenerTest { wantSep31Tx.updatedAt = transferReceivedAt wantSep31Tx.stellarTransactions = listOf(stellarTransaction) - assertEquals(wantSep31Tx, slotTx.captured) + JSONAssert.assertEquals(gson.toJson(wantSep31Tx), gson.toJson(slotTx.captured), true) } @Test @@ -389,6 +391,6 @@ class PaymentOperationToEventListenerTest { wantSep31Tx.updatedAt = transferReceivedAt wantSep31Tx.stellarTransactions = listOf(stellarTransaction) - assertEquals(wantSep31Tx, slotTx.captured) + JSONAssert.assertEquals(gson.toJson(wantSep31Tx), gson.toJson(slotTx.captured), STRICT) } } 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 f178f42b34..d65d7237c8 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 @@ -10,6 +10,8 @@ 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.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode.STRICT import org.stellar.anchor.api.exception.AnchorException import org.stellar.anchor.api.exception.BadRequestException import org.stellar.anchor.api.exception.NotFoundException @@ -28,6 +30,7 @@ import org.stellar.anchor.platform.data.JdbcSep31Transaction import org.stellar.anchor.sep31.* import org.stellar.anchor.sep38.Sep38Quote import org.stellar.anchor.sep38.Sep38QuoteStore +import org.stellar.anchor.util.GsonUtils @Suppress("unused") class TransactionServiceTest { @@ -37,6 +40,7 @@ class TransactionServiceTest { "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" private const val TEST_ACCOUNT = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" private const val TEST_MEMO = "test memo" + private val gson = GsonUtils.getInstance() } @MockK(relaxed = true) private lateinit var sep38QuoteStore: Sep38QuoteStore @@ -338,6 +342,7 @@ class TransactionServiceTest { fun test_updateSep31Transaction() { val txId = "my-tx-id" val quoteId = "my-quote-id" + val gson = GsonUtils.getInstance() // mock times val mockStartedAt = Instant.now().minusSeconds(180) @@ -426,6 +431,10 @@ class TransactionServiceTest { wantSep31TransactionUpdated.externalTransactionId = "external-id" wantSep31TransactionUpdated.transferReceivedAt = mockTransferReceivedAt wantSep31TransactionUpdated.refunds = Refunds.of(mockRefunds, sep31TransactionStore) - assertEquals(wantSep31TransactionUpdated, mockSep31Transaction) + JSONAssert.assertEquals( + gson.toJson(wantSep31TransactionUpdated), + gson.toJson(mockSep31Transaction), + STRICT + ) } } From 93d94d9bacbf31c71acde51b0da2ae6aab338449 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 15 Dec 2022 12:44:16 +0800 Subject: [PATCH 0061/1439] Add IntelliJ run configuration files to .run folder (#681) * Add IntelliJ run configuration files to .run folder * Renamed and updated documentation --- .run/1. Platform Server.run.xml | 32 ++++++++++++++++++++++ .run/2. Business Server.run.xml | 22 +++++++++++++++ docs/02 - Contributing/A - CONTRIBUTING.md | 6 ++++ 3 files changed, 60 insertions(+) create mode 100644 .run/1. Platform Server.run.xml create mode 100644 .run/2. Business Server.run.xml diff --git a/.run/1. Platform Server.run.xml b/.run/1. Platform Server.run.xml new file mode 100644 index 0000000000..5804a71022 --- /dev/null +++ b/.run/1. Platform Server.run.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/2. Business Server.run.xml b/.run/2. Business Server.run.xml new file mode 100644 index 0000000000..b0164ddadf --- /dev/null +++ b/.run/2. Business Server.run.xml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/docs/02 - Contributing/A - CONTRIBUTING.md b/docs/02 - Contributing/A - CONTRIBUTING.md index 797b0214cb..c3d53a5c9c 100644 --- a/docs/02 - Contributing/A - CONTRIBUTING.md +++ b/docs/02 - Contributing/A - CONTRIBUTING.md @@ -98,3 +98,9 @@ Please refer to [Database Migration](/docs/02%20-%20Contributing/D%20-%20Databas ## Publishing Please refer to [Publishing the SDK](/docs/02%20-%20Contributing/E%20-%20Publishing%20the%20SDK.md) for information on this topic. + +## IntelliJ Run Configurations + +Please import the run configurations from the `.run` folder +- `1. Platform Server.run.xml` runs the platform server +- `2. Business Server.run.xml` runs the reference business server \ No newline at end of file From 2d6faf74d03edc9cece4cc6ab2c6ee83b2624091 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 15 Dec 2022 13:18:23 +0800 Subject: [PATCH 0062/1439] Fix pre-commit git hook (#682) --- build.gradle.kts | 4 ++-- scripts/pre-commit.sh | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5607839a30..00e23a5500 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,14 +7,14 @@ plugins { } tasks { - register("installLocalGitHook") { + register("updateGitHook") { from("scripts/pre-commit.sh") { rename { it.removeSuffix(".sh") } } into(".git/hooks") doLast { project.exec { commandLine("chmod", "+x", ".git/hooks/pre-commit") } } } - "build" { dependsOn("installLocalGitHook") } + "build" { dependsOn("updateGitHook") } } subprojects { diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh index 2e6c3d1409..68355274bc 100755 --- a/scripts/pre-commit.sh +++ b/scripts/pre-commit.sh @@ -1,15 +1,15 @@ #!/bin/sh -echo '[git hook] Code Formatting: executing gradle spotlessApply before commit' +# find staged files +stagedFiles=$(git diff --staged --name-only) -# Stash any unstaged changes -git stash -q --keep-index - -# Run the spotlessApply to format the code +# run spotlessApply +echo "Running spotlessApply. Formatting code..." ./gradlew spotlessApply -# Add the format changes to the git commit -git add . - -# unstash the unstashed changes -git stash pop -q +# add staged files +for file in $stagedFiles; do + if test -f "$file"; then + git add $file + fi +done From 12acb5af0ba1899452ecacb4732abede163e779f Mon Sep 17 00:00:00 2001 From: Gleb Date: Thu, 15 Dec 2022 15:54:51 -0800 Subject: [PATCH 0063/1439] ANCHOR-102: Fix running tests locally (#680) --- .../org/stellar/anchor/api/sep/sep24/InfoResponse.java | 2 +- .../main/resources/anchor-docker-compose-config.yaml | 2 +- platform/src/main/resources/example.anchor-config.yaml | 10 +++------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java index dfe176a22c..459a2ae29d 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java @@ -12,7 +12,7 @@ public class InfoResponse { Map withdraw = new HashMap<>(); FeeResponse fee = new FeeResponse(); - @SerializedName("feature_flags") + @SerializedName("features") FeatureFlagResponse featureFlags = new FeatureFlagResponse(true, true); @SuppressWarnings("unused") diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 50ce183c1a..5d392c1f9e 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -234,7 +234,7 @@ assets: "iso4217:USD" ] }, - "sep24_enabled": false, + "sep24_enabled": true, "sep31_enabled": true, "sep38_enabled": true }, diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml index e25355e13d..34d3833133 100644 --- a/platform/src/main/resources/example.anchor-config.yaml +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -18,11 +18,6 @@ platform_api: type: NONE expiration_milliseconds: 30000 -payment_observer: - enabled: false - circle_url: https://api-sandbox.circle.com - tracked_wallet: all - languages: en logging: @@ -87,7 +82,8 @@ sep12: enabled: true sep24: - enabled: false + enabled: true + interactiveUrl: http://localhost:8081/sep24/interactive sep31: enabled: true @@ -281,7 +277,7 @@ assets: "iso4217:USD" ] }, - "sep24_enabled": false, + "sep24_enabled": true, "sep31_enabled": true, "sep38_enabled": true }, From 2e0c5860392a790c805f5531b56fb86fa64da49b Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 20 Dec 2022 10:43:22 -0800 Subject: [PATCH 0064/1439] [ANCHOR-115] Fix SEP-24 schema (#684) * Fix SEP-24 schema * Trigger --- .../api/sep/sep24/TransactionResponse.java | 23 ++++++++----------- .../org/stellar/anchor/sep24/Sep24Helper.java | 1 - .../anchor/api/sep/sep24/Sep24DtoTests.kt | 4 ---- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index 1a78cf3e8d..c4b7badc4b 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -19,47 +19,42 @@ public class TransactionResponse { String moreInfoUrl = "http://www.stellar.org"; @SerializedName("amount_in") - String amountIn = "0"; + String amountIn; @SerializedName("amount_in_asset") String amountInAsset; @SerializedName("amount_out") - String amountOut = "0"; + String amountOut; @SerializedName("amount_out_asset") String amountOutAsset; @SerializedName("amount_fee") - String amountFee = "0"; + String amountFee; @SerializedName("amount_fee_asset") String amountFeeAsset; @SerializedName("started_at") - Instant startedAt = Instant.EPOCH; + Instant startedAt; @SerializedName("completed_at") - Instant completedAt = Instant.EPOCH; + Instant completedAt; @SerializedName("stellar_transaction_id") - String stellarTransactionId = ""; + String stellarTransactionId; @SerializedName("external_transaction_id") String externalTransactionId; String message; + @Deprecated // Deprecated in favor of refunds Boolean refunded = false; Refunds refunds; - String from = ""; + String from; - String to = ""; - - @SerializedName("account_memo") - String accountMemo; - - @SerializedName("muxed_account") - String muxedAccount; + String to; } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java index 0bd33e7a2c..c7e444f52c 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java @@ -1,6 +1,5 @@ package org.stellar.anchor.sep24; -import static javax.print.attribute.standard.JobState.COMPLETED; import static org.stellar.anchor.api.sep.SepTransactionStatus.*; import static org.stellar.anchor.util.Log.debugF; import static org.stellar.anchor.util.MathHelper.decimal; 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 422a87e459..13d344b8bd 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 @@ -117,9 +117,5 @@ internal class Sep24DtoTests { tr.setExternalTransactionId("") tr.getMessage() tr.setMessage("") - tr.getAccountMemo() - tr.setAccountMemo("") - tr.getMuxedAccount() - tr.setMuxedAccount("") } } From 2d17d437b90840fe151e53f7211df494a9103992 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 21 Dec 2022 18:00:39 +0800 Subject: [PATCH 0065/1439] docs: Add more SEP statuses to Platform API.doc (#685) * Add SEP-24 related statuses to Platform API.doc * Increment the version string --- .../Communication/Platform API.yml | 61 +++++++++++++------ 1 file changed, 41 insertions(+), 20 deletions(-) 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 bf05e58cef..5d76962f22 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: description: The Platform API specification for the Stellar Anchor Platform project. - version: "1.0" + version: "1.0.1" title: Platform API tags: - name: "Transactions" @@ -25,21 +25,21 @@ paths: required: false schema: type: integer - enum: [31] + enum: [ 24, 31 ] - in: query name: order description: Either ascending or descending by `order_by` value. required: false schema: type: string - enum: ["asc", "desc"] + enum: [ "asc", "desc" ] default: "desc" - in: query name: order_by description: The data to be used for ordering transactions. schema: type: string - enum: ["started_at", "completed_at", "transfer_received_at"] + enum: [ "started_at", "completed_at", "transfer_received_at" ] default: "started_at" - in: query name: cursor @@ -110,7 +110,7 @@ paths: summary: Accepts updated information on transactions. operationId: patchTransactions description: - Accepts one or more objects containing updated information on transactions. Note that requests containing + Accepts one or more objects containing updated information on transactions. Note that requests containing invalid data for any transaction will result in a 400 Bad Request and no transactions will be updated. Note that this endpoint accepts a subset of transaction information defined in the `PatchTransaction` schema. responses: @@ -154,7 +154,7 @@ paths: summary: Fetches a single transaction. operationId: getTransaction description: - Provides the information necessary for the business to determine the state of the transaction identified by + Provides the information necessary for the business to determine the state of the transaction identified by `id`, decide if any action must be taken to continue processing the transaction, and act on the decision. parameters: - in: path @@ -188,7 +188,7 @@ paths: summary: Fetches the quotes provided to client applications. operationId: getQuotes description: - Only relevant for SEP-38 (used by SEP-31). Fetches the quotes that were created by client applications using + Only relevant for SEP-38 (used by SEP-31). Fetches the quotes that were created by client applications using SEP-38's `POST /quote` endpoint. parameters: - in: query @@ -197,7 +197,7 @@ paths: required: false schema: type: string - enum: ["asc", "desc"] + enum: [ "asc", "desc" ] default: "desc" - in: query name: order_by @@ -205,12 +205,12 @@ paths: required: false schema: type: string - enum: ["created_at", "expires_at", "used_at"] + enum: [ "created_at", "expires_at", "used_at" ] default: "created_at" - in: query name: cursor description: - Cursor used for pagination. Each response will include a `cursor` value that can be used to view + Cursor used for pagination. Each response will include a `cursor` value that can be used to view the next page, assuming all other parameters kept the same. required: false schema: @@ -256,7 +256,7 @@ paths: summary: Fetch a single quote provided to a client application. operationId: getQuote description: - Only relevant for SEP-38 (used by SEP-31). Fetches the quote that ascreated by a client application using + Only relevant for SEP-38 (used by SEP-31). Fetches the quote that ascreated by a client application using SEP-38's `POST /quote` endpoint. parameters: - in: path @@ -287,20 +287,41 @@ components: type: string sep: type: integer - enum: [31] + enum: [ 24, 31 ] kind: type: string - enum: ["receive"] + enum: [ + # SEP24 + "deposit", + "withdrawal", + #SEP31 + "receive" + ] status: type: string enum: [ - "pending_sender", - "pending_stellar", - "pending_customer_info_update", - "pending_receiver", - "pending_external", - "completed", - "error" + # Shared + incomplete, + completed, + refunded, + expired, + error, + pending_stellar, + pending_external, + # SEP24 + pending_user_transfer_start, + pending_user_transfer_complete, + pending_anchor, + pending_trust, + pending_user, + no_market, + too_small, + too_large, + # SEP31 + pending_sender, + pending_receiver, + pending_transaction_info_update, + pending_customer_info_update ] amount_expected: $ref: '#/components/schemas/Amount' From 6656912e0d24c3ffbf99a4a7e23fec3697be9d14 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 22 Dec 2022 10:31:51 +0800 Subject: [PATCH 0066/1439] [ANCHOR-99]: Add SEP24 to get transaction endpoint (#683) * Add SEP24 to get transaction endpoint --- .../anchor/api/sep/SepTransactionStatus.java | 7 + .../api/sep/sep24/TransactionResponse.java | 14 +- .../anchor/sep31/Sep31Transaction.java | 37 -- .../anchor/{Constants.kt => TestConstants.kt} | 2 +- .../asset/ResourceJsonAssetServiceTest.kt | 4 +- .../org/stellar/anchor/auth/JwtTokenTest.kt | 4 +- .../stellar/anchor/sep10/Sep10ServiceTest.kt | 18 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 106 +---- .../anchor/sep24/Sep24ServiceTestHelper.kt | 79 ++++ .../stellar/anchor/sep31/Sep31ServiceTest.kt | 8 +- .../anchor/sep31/Sep31TransactionTest.kt | 63 --- .../kotlin/org/stellar/anchor/util/LogTest.kt | 4 +- .../stellar/anchor/platform/Sep24Client.kt | 7 + .../platform/AnchorPlatformIntegrationTest.kt | 375 ++--------------- .../anchor/platform/CallbackApiTests.kt | 260 ++++++++++++ .../anchor/platform/PlatformApiTests.kt | 223 ++++++++++ .../stellar/anchor/platform/PlatformTests.kt | 212 ---------- .../org/stellar/anchor/platform/Sep10Tests.kt | 86 ++-- .../org/stellar/anchor/platform/Sep12Tests.kt | 116 +++--- .../org/stellar/anchor/platform/Sep24Tests.kt | 228 ++++++++-- .../org/stellar/anchor/platform/Sep31Tests.kt | 243 ++++++++--- .../org/stellar/anchor/platform/Sep38Tests.kt | 140 ++++--- .../stellar/anchor/platform/SepTestSuite.kt | 35 +- .../anchor/platform/StellarObserverTests.kt | 69 ++++ .../platform/data/JdbcSep24Transaction.java | 10 + .../platform/data/JdbcSep31Transaction.java | 21 +- .../platform/service/TransactionService.java | 134 ++++-- .../platform/data/JdbcSep31TransactionTest.kt | 29 -- .../service/TransactionServiceTest.kt | 388 ++++++++++-------- 29 files changed, 1659 insertions(+), 1263 deletions(-) rename core/src/test/kotlin/org/stellar/anchor/{Constants.kt => TestConstants.kt} (98%) create mode 100644 core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/StellarObserverTests.kt 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 dceeae0fa8..f9037cabbc 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 @@ -1,5 +1,7 @@ package org.stellar.anchor.api.sep; +import java.util.Arrays; + public enum SepTransactionStatus { PENDING_ANCHOR("pending_anchor", "processing"), PENDING_TRUST("pending_trust", "waiting for a trustline to be established"), @@ -47,4 +49,9 @@ public String getName() { public String getDescription() { return description; } + + public static boolean isValid(String status) { + return Arrays.stream(SepTransactionStatus.values()) + .anyMatch(e -> e.name.equalsIgnoreCase(status)); + } } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index c4b7badc4b..22dc95bb0f 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -19,19 +19,19 @@ public class TransactionResponse { String moreInfoUrl = "http://www.stellar.org"; @SerializedName("amount_in") - String amountIn; + String amountIn = "0"; @SerializedName("amount_in_asset") String amountInAsset; @SerializedName("amount_out") - String amountOut; + String amountOut = "0"; @SerializedName("amount_out_asset") String amountOutAsset; @SerializedName("amount_fee") - String amountFee; + String amountFee = "0"; @SerializedName("amount_fee_asset") String amountFeeAsset; @@ -40,10 +40,10 @@ public class TransactionResponse { Instant startedAt; @SerializedName("completed_at") - Instant completedAt; + Instant completedAt = Instant.EPOCH; @SerializedName("stellar_transaction_id") - String stellarTransactionId; + String stellarTransactionId = ""; @SerializedName("external_transaction_id") String externalTransactionId; @@ -54,7 +54,7 @@ public class TransactionResponse { Boolean refunded = false; Refunds refunds; - String from; + String from = ""; - String to; + String to = ""; } 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 46858c780a..3b466a0bdb 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -6,7 +6,6 @@ import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep31.Sep31GetTransactionResponse; import org.stellar.anchor.api.shared.*; -import org.stellar.anchor.event.models.TransactionEvent; @SuppressWarnings("unused") public interface Sep31Transaction { @@ -173,40 +172,4 @@ 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()); - } - - 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(getStellarTransactions()) - .externalTransactionId(getExternalTransactionId()) - // TODO .custodialTransactionId(txn.get) - .customers(getCustomers()) - .creator(getCreator()) - .build(); - } } diff --git a/core/src/test/kotlin/org/stellar/anchor/Constants.kt b/core/src/test/kotlin/org/stellar/anchor/TestConstants.kt similarity index 98% rename from core/src/test/kotlin/org/stellar/anchor/Constants.kt rename to core/src/test/kotlin/org/stellar/anchor/TestConstants.kt index 541e5ac832..cb2b4aa714 100644 --- a/core/src/test/kotlin/org/stellar/anchor/Constants.kt +++ b/core/src/test/kotlin/org/stellar/anchor/TestConstants.kt @@ -1,6 +1,6 @@ package org.stellar.anchor -class Constants { +class TestConstants { companion object { const val TEST_SIGNING_SEED = "SBVEOFAHGJCKGR4AAM7RTDRCP6RMYYV5YUV32ZK7ZD3VPDGGHYLXTZRZ" const val TEST_ACCOUNT = "GBLGJA4TUN5XOGTV6WO2BWYUI2OZR5GYQ5PDPCRMQ5XEPJOYWB2X4CJO" 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 da3c74907b..dfa80e2036 100644 --- a/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt @@ -5,8 +5,8 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import org.stellar.anchor.Constants.Companion.TEST_ASSET -import org.stellar.anchor.Constants.Companion.TEST_ASSET_ISSUER_ACCOUNT_ID +import org.stellar.anchor.TestConstants.Companion.TEST_ASSET +import org.stellar.anchor.TestConstants.Companion.TEST_ASSET_ISSUER_ACCOUNT_ID import org.stellar.anchor.api.exception.SepNotFoundException internal class ResourceJsonAssetServiceTest { 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 4439886a89..3219453150 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/JwtTokenTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/JwtTokenTest.kt @@ -3,8 +3,8 @@ package org.stellar.anchor.auth import java.time.Instant import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -import org.stellar.anchor.Constants.Companion.TEST_ACCOUNT -import org.stellar.anchor.Constants.Companion.TEST_CLIENT_DOMAIN +import org.stellar.anchor.TestConstants.Companion.TEST_ACCOUNT +import org.stellar.anchor.TestConstants.Companion.TEST_CLIENT_DOMAIN class JwtTokenTest { private val issuedAt = Instant.now().epochSecond 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 265fd0ac92..09c961c4ab 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -21,15 +21,15 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.ValueSource -import org.stellar.anchor.Constants.Companion.TEST_ACCOUNT -import org.stellar.anchor.Constants.Companion.TEST_CLIENT_DOMAIN -import org.stellar.anchor.Constants.Companion.TEST_CLIENT_TOML -import org.stellar.anchor.Constants.Companion.TEST_HOME_DOMAIN -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_MEMO -import org.stellar.anchor.Constants.Companion.TEST_NETWORK_PASS_PHRASE -import org.stellar.anchor.Constants.Companion.TEST_SIGNING_SEED +import org.stellar.anchor.TestConstants.Companion.TEST_ACCOUNT +import org.stellar.anchor.TestConstants.Companion.TEST_CLIENT_DOMAIN +import org.stellar.anchor.TestConstants.Companion.TEST_CLIENT_TOML +import org.stellar.anchor.TestConstants.Companion.TEST_HOME_DOMAIN +import org.stellar.anchor.TestConstants.Companion.TEST_HOST_URL +import org.stellar.anchor.TestConstants.Companion.TEST_JWT_SECRET +import org.stellar.anchor.TestConstants.Companion.TEST_MEMO +import org.stellar.anchor.TestConstants.Companion.TEST_NETWORK_PASS_PHRASE +import org.stellar.anchor.TestConstants.Companion.TEST_SIGNING_SEED import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.exception.SepNotAuthorizedException import org.stellar.anchor.api.exception.SepValidationException 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 1c01d14686..16dba7905d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -15,14 +15,14 @@ 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.Constants -import org.stellar.anchor.Constants.Companion.TEST_ACCOUNT -import org.stellar.anchor.Constants.Companion.TEST_ASSET -import org.stellar.anchor.Constants.Companion.TEST_ASSET_ISSUER_ACCOUNT_ID -import org.stellar.anchor.Constants.Companion.TEST_CLIENT_DOMAIN -import org.stellar.anchor.Constants.Companion.TEST_MEMO -import org.stellar.anchor.Constants.Companion.TEST_TRANSACTION_ID_0 -import org.stellar.anchor.Constants.Companion.TEST_TRANSACTION_ID_1 +import org.stellar.anchor.TestConstants +import org.stellar.anchor.TestConstants.Companion.TEST_ACCOUNT +import org.stellar.anchor.TestConstants.Companion.TEST_ASSET +import org.stellar.anchor.TestConstants.Companion.TEST_ASSET_ISSUER_ACCOUNT_ID +import org.stellar.anchor.TestConstants.Companion.TEST_CLIENT_DOMAIN +import org.stellar.anchor.TestConstants.Companion.TEST_MEMO +import org.stellar.anchor.TestConstants.Companion.TEST_TRANSACTION_ID_0 +import org.stellar.anchor.TestConstants.Companion.TEST_TRANSACTION_ID_1 import org.stellar.anchor.TestHelper import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.exception.SepNotAuthorizedException @@ -65,9 +65,9 @@ internal class Sep24ServiceTest { @BeforeEach fun setUp() { MockKAnnotations.init(this, relaxUnitFun = true) - every { appConfig.stellarNetworkPassphrase } returns Constants.TEST_NETWORK_PASS_PHRASE - every { appConfig.hostUrl } returns Constants.TEST_HOST_URL - every { secretConfig.sep10JwtSecretKey } returns Constants.TEST_JWT_SECRET + every { appConfig.stellarNetworkPassphrase } returns TestConstants.TEST_NETWORK_PASS_PHRASE + every { appConfig.hostUrl } returns TestConstants.TEST_HOST_URL + every { secretConfig.sep10JwtSecretKey } returns TestConstants.TEST_JWT_SECRET every { sep24Config.interactiveUrl } returns TEST_SEP24_INTERACTIVE_URL every { sep24Config.interactiveJwtExpiration } returns 1000 @@ -85,14 +85,6 @@ internal class Sep24ServiceTest { unmockkAll() } - private fun createJwtToken(): JwtToken { - return TestHelper.createJwtToken(TEST_ACCOUNT, null, appConfig.hostUrl, TEST_CLIENT_DOMAIN) - } - - private fun createJwtWithMemo(): JwtToken { - return TestHelper.createJwtToken(TEST_ACCOUNT, TEST_MEMO, appConfig.hostUrl, TEST_CLIENT_DOMAIN) - } - @Test fun `test withdraw ok`() { val slotTxn = slot() @@ -145,19 +137,6 @@ internal class Sep24ServiceTest { assertEquals(TEST_CLIENT_DOMAIN, decodedToken.clientDomain) } - private fun createTestTransactionRequest(): MutableMap { - return mutableMapOf( - "lang" to "en", - "asset_code" to TEST_ASSET, - "asset_issuer" to TEST_ASSET_ISSUER_ACCOUNT_ID, - "account" to TEST_ACCOUNT, - "amount" to "123.4", - "email_address" to "jamie@stellar.org", - "first_name" to "Jamie", - "last_name" to "Li" - ) - } - @Test fun `test withdrawal with no token and no request failure`() { assertThrows { @@ -460,66 +439,11 @@ internal class Sep24ServiceTest { assertTrue(response.url.indexOf("lang=en-US") != -1) } - private fun createTestTransaction(kind: String): Sep24Transaction { - val txn = PojoSep24Transaction() - txn.transactionId = TEST_TRANSACTION_ID_0 - txn.status = "incomplete" - txn.kind = kind - txn.startedAt = TEST_STARTED_AT - txn.completedAt = TEST_COMPLETED_AT - - txn.requestAssetCode = TEST_ASSET - txn.requestAssetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID - txn.sep10Account = TEST_ACCOUNT - txn.toAccount = TEST_ACCOUNT - txn.fromAccount = TEST_ACCOUNT - txn.clientDomain = TEST_CLIENT_DOMAIN - txn.protocol = "sep24" - txn.amountIn = "321.4" - txn.amountOut = "321.4" - - return txn + private fun createJwtToken(): JwtToken { + return TestHelper.createJwtToken(TEST_ACCOUNT, null, appConfig.hostUrl, TEST_CLIENT_DOMAIN) } - private fun createTestTransactions(kind: String): MutableList { - val txns = ArrayList() - - var txn = PojoSep24Transaction() - txn.transactionId = TEST_TRANSACTION_ID_0 - txn.status = "incomplete" - txn.kind = kind - txn.startedAt = TEST_STARTED_AT - txn.completedAt = TEST_COMPLETED_AT - - txn.requestAssetCode = TEST_ASSET - txn.requestAssetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID - txn.sep10Account = TEST_ACCOUNT - txn.toAccount = TEST_ACCOUNT - txn.fromAccount = TEST_ACCOUNT - txn.clientDomain = TEST_CLIENT_DOMAIN - txn.protocol = "sep24" - txn.amountIn = "321.4" - txn.amountOut = "321.4" - txns.add(txn) - - txn = PojoSep24Transaction() - txn.transactionId = TEST_TRANSACTION_ID_1 - txn.status = "completed" - txn.kind = kind - txn.startedAt = TEST_STARTED_AT - txn.completedAt = TEST_COMPLETED_AT - - txn.requestAssetCode = TEST_ASSET - txn.requestAssetIssuer = TEST_ASSET_ISSUER_ACCOUNT_ID - txn.sep10Account = TEST_ACCOUNT - txn.toAccount = TEST_ACCOUNT - txn.fromAccount = TEST_ACCOUNT - txn.clientDomain = TEST_CLIENT_DOMAIN - txn.protocol = "sep24" - txn.amountIn = "456.7" - txn.amountOut = "456.7" - txns.add(txn) - - return txns + private fun createJwtWithMemo(): JwtToken { + return TestHelper.createJwtToken(TEST_ACCOUNT, TEST_MEMO, appConfig.hostUrl, TEST_CLIENT_DOMAIN) } } diff --git a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt new file mode 100644 index 0000000000..fef1b9aa24 --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTestHelper.kt @@ -0,0 +1,79 @@ +package org.stellar.anchor.sep24 + +import org.stellar.anchor.TestConstants + +fun createTestTransactionRequest(): MutableMap { + return mutableMapOf( + "lang" to "en", + "asset_code" to TestConstants.TEST_ASSET, + "asset_issuer" to TestConstants.TEST_ASSET_ISSUER_ACCOUNT_ID, + "account" to TestConstants.TEST_ACCOUNT, + "amount" to "123.4", + "email_address" to "jamie@stellar.org", + "first_name" to "Jamie", + "last_name" to "Li" + ) +} + +fun createTestTransaction(kind: String): Sep24Transaction { + val txn = PojoSep24Transaction() + txn.transactionId = TestConstants.TEST_TRANSACTION_ID_0 + txn.status = "incomplete" + txn.kind = kind + txn.startedAt = Sep24ServiceTest.TEST_STARTED_AT + txn.completedAt = Sep24ServiceTest.TEST_COMPLETED_AT + + txn.requestAssetCode = TestConstants.TEST_ASSET + txn.requestAssetIssuer = TestConstants.TEST_ASSET_ISSUER_ACCOUNT_ID + txn.sep10Account = TestConstants.TEST_ACCOUNT + txn.toAccount = TestConstants.TEST_ACCOUNT + txn.fromAccount = TestConstants.TEST_ACCOUNT + txn.clientDomain = TestConstants.TEST_CLIENT_DOMAIN + txn.protocol = "sep24" + txn.amountIn = "321.4" + txn.amountOut = "321.4" + + return txn +} + +fun createTestTransactions(kind: String): MutableList { + val txns = ArrayList() + + var txn = PojoSep24Transaction() + txn.transactionId = TestConstants.TEST_TRANSACTION_ID_0 + txn.status = "incomplete" + txn.kind = kind + txn.startedAt = Sep24ServiceTest.TEST_STARTED_AT + txn.completedAt = Sep24ServiceTest.TEST_COMPLETED_AT + + txn.requestAssetCode = TestConstants.TEST_ASSET + txn.requestAssetIssuer = TestConstants.TEST_ASSET_ISSUER_ACCOUNT_ID + txn.sep10Account = TestConstants.TEST_ACCOUNT + txn.toAccount = TestConstants.TEST_ACCOUNT + txn.fromAccount = TestConstants.TEST_ACCOUNT + txn.clientDomain = TestConstants.TEST_CLIENT_DOMAIN + txn.protocol = "sep24" + txn.amountIn = "321.4" + txn.amountOut = "321.4" + txns.add(txn) + + txn = PojoSep24Transaction() + txn.transactionId = TestConstants.TEST_TRANSACTION_ID_1 + txn.status = "completed" + txn.kind = kind + txn.startedAt = Sep24ServiceTest.TEST_STARTED_AT + txn.completedAt = Sep24ServiceTest.TEST_COMPLETED_AT + + txn.requestAssetCode = TestConstants.TEST_ASSET + txn.requestAssetIssuer = TestConstants.TEST_ASSET_ISSUER_ACCOUNT_ID + txn.sep10Account = TestConstants.TEST_ACCOUNT + txn.toAccount = TestConstants.TEST_ACCOUNT + txn.fromAccount = TestConstants.TEST_ACCOUNT + txn.clientDomain = TestConstants.TEST_CLIENT_DOMAIN + txn.protocol = "sep24" + txn.amountIn = "456.7" + txn.amountOut = "456.7" + txns.add(txn) + + return txns +} diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt index ce8e5f80f1..b1cd867604 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -16,7 +16,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.skyscreamer.jsonassert.JSONAssert -import org.stellar.anchor.Constants +import org.stellar.anchor.TestConstants import org.stellar.anchor.TestHelper import org.stellar.anchor.api.callback.CustomerIntegration import org.stellar.anchor.api.callback.FeeIntegration @@ -295,9 +295,9 @@ class Sep31ServiceTest { @BeforeEach fun setUp() { MockKAnnotations.init(this, relaxUnitFun = true) - every { appConfig.stellarNetworkPassphrase } returns Constants.TEST_NETWORK_PASS_PHRASE - every { appConfig.hostUrl } returns Constants.TEST_HOST_URL - every { secretConfig.sep10JwtSecretKey } returns Constants.TEST_JWT_SECRET + every { appConfig.stellarNetworkPassphrase } returns TestConstants.TEST_NETWORK_PASS_PHRASE + every { appConfig.hostUrl } returns TestConstants.TEST_HOST_URL + every { secretConfig.sep10JwtSecretKey } returns TestConstants.TEST_JWT_SECRET every { appConfig.languages } returns listOf("en") every { sep31Config.paymentType } returns STRICT_SEND every { txnStore.newTransaction() } returns PojoSep31Transaction() 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 34308bf026..7b6ac71d48 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31TransactionTest.kt @@ -10,15 +10,12 @@ 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 { @@ -137,66 +134,6 @@ class Sep31TransactionTest { unmockkAll() } - @Test - fun `test PlatformApiGetTransactionResponse correctness`() { - 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 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(stellarTransaction)) - .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 Sep31GetTransactionResponse correctness`() { val refunds = 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 3da9e13e85..3e5084fc3a 100644 --- a/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/util/LogTest.kt @@ -10,8 +10,8 @@ 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_NETWORK_PASS_PHRASE +import org.stellar.anchor.TestConstants.Companion.TEST_HOST_URL +import org.stellar.anchor.TestConstants.Companion.TEST_NETWORK_PASS_PHRASE import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.PII import org.stellar.anchor.util.Log.shorter diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt index 440c8a59ed..e5fa4aa4df 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt @@ -19,6 +19,13 @@ class Sep24Client(private val endpoint: String, private val jwt: String) : SepCl return gson.fromJson(responseBody, InteractiveTransactionResponse::class.java) } + fun deposit(requestData: Map?): InteractiveTransactionResponse { + val url = "$endpoint/transactions/deposit/interactive" + println("SEP24 $url") + val responseBody = httpPost(url, requestData!!, jwt) + return gson.fromJson(responseBody, InteractiveTransactionResponse::class.java) + } + fun getTransaction(id: String, assetCode: String): GetTransactionResponse { println("SEP24 $endpoint/transactions") val responseBody = httpGet("$endpoint/transaction?id=$id&asset_code=$assetCode", jwt) 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 42dd586f61..717eb314ce 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 @@ -1,80 +1,19 @@ package org.stellar.anchor.platform -import com.google.gson.Gson -import java.time.Instant -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.* -import java.util.concurrent.TimeUnit -import okhttp3.OkHttpClient -import okhttp3.Request import org.junit.jupiter.api.* -import org.junit.jupiter.api.Assertions.* -import org.skyscreamer.jsonassert.JSONAssert -import org.springframework.context.ConfigurableApplicationContext -import org.stellar.anchor.api.callback.GetFeeRequest -import org.stellar.anchor.api.callback.GetRateRequest -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.SEP31 -import org.stellar.anchor.auth.AuthHelper -import org.stellar.anchor.auth.JwtService -import org.stellar.anchor.config.AppConfig -import org.stellar.anchor.config.Sep10Config -import org.stellar.anchor.config.Sep1Config -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 +import org.stellar.anchor.util.Sep1Helper.TomlContent @TestMethodOrder(MethodOrderer.OrderAnnotation::class) 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" - private const val STELLAR_USD = - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + lateinit var toml: TomlContent + lateinit var jwt: String - private val platformToAnchorJwtService = JwtService(PLATFORM_TO_ANCHOR_SECRET) - private val authHelper = - AuthHelper.forJwtToken( - platformToAnchorJwtService, - JWT_EXPIRATION_MILLISECONDS, - "http://localhost:$SEP_SERVER_PORT" - ) + const val REFERENCE_SERVER_PORT = 8081 + const val SEP_SERVER_PORT = 8080 + const val OBSERVER_HEALTH_SERVER_PORT = 8083 - private lateinit var toml: Sep1Helper.TomlContent - private lateinit var jwt: String - private val httpClient: OkHttpClient = - OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.MINUTES) - .readTimeout(10, TimeUnit.MINUTES) - .writeTimeout(10, TimeUnit.MINUTES) - .build() - private val gson: Gson = GsonUtils.getInstance() - private val rci = - RestCustomerIntegration( - "http://localhost:$REFERENCE_SERVER_PORT", - httpClient, - authHelper, - gson - ) - private val rriClient = - RestRateIntegration("http://localhost:$REFERENCE_SERVER_PORT", httpClient, authHelper, gson) - private val rfiClient = - RestFeeIntegration("http://localhost:$REFERENCE_SERVER_PORT", httpClient, authHelper, gson) - - private lateinit var platformServerContext: ConfigurableApplicationContext init { val props = System.getProperties() props.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") @@ -82,7 +21,7 @@ class AnchorPlatformIntegrationTest { @BeforeAll @JvmStatic - fun setup() { + fun startServers() { val envMap = mapOf( "sep_server.port" to SEP_SERVER_PORT, @@ -96,305 +35,73 @@ class AnchorPlatformIntegrationTest { "secret.data.password" to "password" ) - platformServerContext = ServiceRunner.startSepServer(envMap) + ServiceRunner.startSepServer(envMap) ServiceRunner.startStellarObserver(envMap) + ServiceRunner.startAnchorReferenceServer() - AnchorReferenceServer.start(REFERENCE_SERVER_PORT, "/") - } - } + toml = + Sep1Helper.parse( + resourceAsString("http://localhost:$SEP_SERVER_PORT/.well-known/stellar.toml") + ) + + Sep10Tests.setup() - private fun readSep1Toml(): Sep1Helper.TomlContent { - val tomlString = resourceAsString("http://localhost:$SEP_SERVER_PORT/.well-known/stellar.toml") - return Sep1Helper.parse(tomlString) + if (!::jwt.isInitialized) { + jwt = sep10Client.auth() + } + + Sep12Tests.setup() + Sep24Tests.setup() + Sep31Tests.setup() + Sep38Tests.setup() + PlatformApiTests.setup() + } } @Test @Order(1) - fun runSep1Test() { - toml = readSep1Toml() + fun runSep10Test() { + sep10TestAll() } @Test @Order(2) - fun runSep10Test() { - jwt = sep10TestAll(toml) + fun runSep12Test() { + sep12TestAll() } @Test @Order(3) - fun runSep12Test() { - sep12TestAll(toml, jwt) + fun runSep24Test() { + sep24TestAll() } @Test @Order(4) - fun runSep24Test() { - sep24TestAll(toml, jwt) + fun runSep31Test() { + sep31TestAll() } @Test @Order(5) - fun runSep31Test() { - sep31TestAll(toml, jwt) + fun runSep38Test() { + sep38TestAll() } @Test @Order(6) - fun runSep38Test() { - sep38TestAll(toml, jwt) + fun runPlatformApiTest() { + platformTestAll() } @Test @Order(7) - fun runPlatformTest() { - platformTestAll(toml, jwt) + fun runCallbackApiTest() { + callbackApiTestAll() } - @Test @Order(8) - fun runSep31UnhappyPath() { - testSep31UnhappyPath() - } - - @Test - fun testCustomerIntegration() { - assertThrows { - rci.getCustomer(Sep12GetCustomerRequest.builder().id("1").build()) - } - } - - @Test - fun testRate_indicativePrices() { - val result = - rriClient.getRate( - GetRateRequest.builder() - .type(INDICATIVE_PRICES) - .sellAsset(FIAT_USD) - .sellAmount("100") - .buyAsset(STELLAR_USD) - .build() - ) - assertNotNull(result) - val wantBody = - """{ - "rate":{ - "price":"1.02", - "sell_amount": "100", - "buy_amount": "98.0392" - } - }""" - .trimMargin() - JSONAssert.assertEquals(wantBody, gson.toJson(result), true) - } - - @Test - fun testRate_indicativePrice() { - val result = - rriClient.getRate( - GetRateRequest.builder() - .type(INDICATIVE_PRICE) - .context(SEP31) - .sellAsset(FIAT_USD) - .sellAmount("100") - .buyAsset(STELLAR_USD) - .build() - ) - assertNotNull(result) - val wantBody = - """{ - "rate":{ - "total_price":"1.0303032801", - "price":"1.0200002473", - "sell_amount": "100", - "buy_amount": "97.0588", - "fee": { - "total": "1.00", - "asset": "$FIAT_USD", - "details": [ - { - "name": "Sell fee", - "description": "Fee related to selling the asset.", - "amount": "1.00" - } - ] - } - } - }""" - .trimMargin() - JSONAssert.assertEquals(wantBody, gson.toJson(result), true) - } - - @Test - fun testRate_firm() { - val rate = - rriClient - .getRate( - GetRateRequest.builder() - .type(FIRM) - .context(SEP31) - .sellAsset(FIAT_USD) - .buyAsset(STELLAR_USD) - .buyAmount("100") - .build() - ) - .rate - assertNotNull(rate) - - // check if id is a valid UUID - val id = rate.id - assertDoesNotThrow { UUID.fromString(id) } - var gotExpiresAt: Instant? = null - val expiresAtStr = rate.expiresAt.toString() - assertDoesNotThrow { - gotExpiresAt = DateTimeFormatter.ISO_INSTANT.parse(rate.expiresAt.toString(), Instant::from) - } - - val wantExpiresAt = - ZonedDateTime.now(ZoneId.of("UTC")) - .plusDays(1) - .withHour(12) - .withMinute(0) - .withSecond(0) - .withNano(0) - assertEquals(wantExpiresAt.toInstant(), gotExpiresAt) - - // check if rate was persisted by getting the rate with ID - val gotQuote = rriClient.getRate(GetRateRequest.builder().id(rate.id).build()) - assertEquals(rate.id, gotQuote.rate.id) - assertEquals("1.02", gotQuote.rate.price) - - val wantBody = - """{ - "rate":{ - "id": "$id", - "total_price":"1.03", - "price":"1.02", - "sell_amount": "103", - "buy_amount": "100", - "expires_at": "$expiresAtStr", - "fee": { - "total": "1.00", - "asset": "$FIAT_USD", - "details": [ - { - "name": "Sell fee", - "description": "Fee related to selling the asset.", - "amount": "1.00" - } - ] - } - } - }""" - .trimMargin() - JSONAssert.assertEquals(wantBody, gson.toJson(gotQuote), true) - } - - @Test - fun testGetFee() { - // Create sender customer - val senderCustomerRequest = - GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) - val senderCustomer = sep12Client.putCustomer(senderCustomerRequest) - - // Create receiver customer - val receiverCustomerRequest = - GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) - val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) - - val result = - rfiClient.getFee( - GetFeeRequest.builder() - .sendAmount("10") - .sendAsset("USDC") - .receiveAsset("USDC") - .senderId(senderCustomer!!.id) - .receiverId(receiverCustomer!!.id) - .clientId("") - .build() - ) - - assertNotNull(result) - JSONAssert.assertEquals( - gson.toJson(result), - """{ - "fee": { - "asset": "USDC", - "amount": "0.30" - } - }""", - true - ) - } - - @Test - fun testAppConfig() { - val appConfig = platformServerContext.getBean(AppConfig::class.java) - assertEquals("Test SDF Network ; September 2015", appConfig.stellarNetworkPassphrase) - assertEquals("http://localhost:8080", appConfig.hostUrl) - assertEquals(listOf("en"), appConfig.languages) - assertEquals("https://horizon-testnet.stellar.org", appConfig.horizonUrl) - } - - @Test - fun testSep1Config() { - val sep1Config = platformServerContext.getBean(Sep1Config::class.java) - assertEquals(true, sep1Config.isEnabled) - } - - @Test - fun testSep10Config() { - val sep10Config = platformServerContext.getBean(Sep10Config::class.java) - assertEquals(true, sep10Config.enabled) - assertEquals("localhost:8080", sep10Config.homeDomain) - assertEquals(false, sep10Config.isClientAttributionRequired) - assertEquals(900, sep10Config.authTimeout) - assertEquals(86400, sep10Config.jwtTimeout) - } - - @Test - fun testSep38Config() { - 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"]) - - val stellarPaymentObserverCheck = checks["stellar_payment_observer"] as Map<*, *> - assertEquals(2, stellarPaymentObserverCheck.size) - assertEquals("GREEN", stellarPaymentObserverCheck["status"]) - - val observerStreams = stellarPaymentObserverCheck["streams"] as List<*> - assertEquals(1, observerStreams.size) - - val stream1 = observerStreams[0] as Map<*, *> - assertEquals(5, stream1.size) - assertEquals(false, stream1["thread_shutdown"]) - assertEquals(false, stream1["thread_terminated"]) - assertEquals(false, stream1["stopped"]) - assertNotNull(stream1["last_event_id"]) + fun runStellarObserverTest() { + stellarObserverTestAll() } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt new file mode 100644 index 0000000000..e2a1c75adb --- /dev/null +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt @@ -0,0 +1,260 @@ +package org.stellar.anchor.platform + +import com.google.gson.Gson +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.* +import java.util.concurrent.TimeUnit +import okhttp3.OkHttpClient +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.skyscreamer.jsonassert.JSONAssert +import org.stellar.anchor.api.callback.GetFeeRequest +import org.stellar.anchor.api.callback.GetRateRequest +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.auth.AuthHelper +import org.stellar.anchor.auth.JwtService +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.util.GsonUtils + +class CallbackApiTests { + companion object { + 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 httpClient: OkHttpClient = + OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.MINUTES) + .readTimeout(10, TimeUnit.MINUTES) + .writeTimeout(10, TimeUnit.MINUTES) + .build() + + private val platformToAnchorJwtService = JwtService(PLATFORM_TO_ANCHOR_SECRET) + + private val authHelper = + AuthHelper.forJwtToken( + platformToAnchorJwtService, + JWT_EXPIRATION_MILLISECONDS, + "http://localhost:${AnchorPlatformIntegrationTest.SEP_SERVER_PORT}" + ) + + private val gson: Gson = GsonUtils.getInstance() + + private val rci = + RestCustomerIntegration( + "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", + httpClient, + authHelper, + gson + ) + private val rriClient = + RestRateIntegration( + "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", + httpClient, + authHelper, + gson + ) + private val rfiClient = + RestFeeIntegration( + "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", + httpClient, + authHelper, + gson + ) + + fun setup() {} + + @Test + @Order(21) + fun testCustomerIntegration() { + assertThrows { + rci.getCustomer(Sep12GetCustomerRequest.builder().id("1").build()) + } + } + + fun testRate_indicativePrices() { + val result = + rriClient.getRate( + GetRateRequest.builder() + .type(GetRateRequest.Type.INDICATIVE_PRICES) + .sellAsset(FIAT_USD) + .sellAmount("100") + .buyAsset(STELLAR_USD) + .build() + ) + Assertions.assertNotNull(result) + val wantBody = + """{ + "rate":{ + "price":"1.02", + "sell_amount": "100", + "buy_amount": "98.0392" + } + }""" + .trimMargin() + JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(result), true) + } + + @Test + @Order(23) + fun testRate_indicativePrice() { + val result = + rriClient.getRate( + GetRateRequest.builder() + .type(GetRateRequest.Type.INDICATIVE_PRICE) + .context(Sep38Context.SEP31) + .sellAsset(FIAT_USD) + .sellAmount("100") + .buyAsset(STELLAR_USD) + .build() + ) + Assertions.assertNotNull(result) + val wantBody = + """{ + "rate":{ + "total_price":"1.0303032801", + "price":"1.0200002473", + "sell_amount": "100", + "buy_amount": "97.0588", + "fee": { + "total": "1.00", + "asset": "$FIAT_USD", + "details": [ + { + "name": "Sell fee", + "description": "Fee related to selling the asset.", + "amount": "1.00" + } + ] + } + } + }""" + .trimMargin() + JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(result), true) + } + + @Test + @Order(24) + fun testRate_firm() { + val rate = + rriClient + .getRate( + GetRateRequest.builder() + .type(GetRateRequest.Type.FIRM) + .context(Sep38Context.SEP31) + .sellAsset(FIAT_USD) + .buyAsset(STELLAR_USD) + .buyAmount("100") + .build() + ) + .rate + Assertions.assertNotNull(rate) + + // check if id is a valid UUID + val id = rate.id + Assertions.assertDoesNotThrow { UUID.fromString(id) } + var gotExpiresAt: Instant? = null + val expiresAtStr = rate.expiresAt.toString() + Assertions.assertDoesNotThrow { + gotExpiresAt = DateTimeFormatter.ISO_INSTANT.parse(rate.expiresAt.toString(), Instant::from) + } + + val wantExpiresAt = + ZonedDateTime.now(ZoneId.of("UTC")) + .plusDays(1) + .withHour(12) + .withMinute(0) + .withSecond(0) + .withNano(0) + assertEquals(wantExpiresAt.toInstant(), gotExpiresAt) + + // check if rate was persisted by getting the rate with ID + val gotQuote = rriClient.getRate(GetRateRequest.builder().id(rate.id).build()) + assertEquals(rate.id, gotQuote.rate.id) + assertEquals("1.02", gotQuote.rate.price) + + val wantBody = + """{ + "rate":{ + "id": "$id", + "total_price":"1.03", + "price":"1.02", + "sell_amount": "103", + "buy_amount": "100", + "expires_at": "$expiresAtStr", + "fee": { + "total": "1.00", + "asset": "$FIAT_USD", + "details": [ + { + "name": "Sell fee", + "description": "Fee related to selling the asset.", + "amount": "1.00" + } + ] + } + } + }""" + .trimMargin() + JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(gotQuote), true) + } + + @Test + @Order(25) + fun testGetFee() { + // Create sender customer + val senderCustomerRequest = + GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + val senderCustomer = sep12Client.putCustomer(senderCustomerRequest) + + // Create receiver customer + val receiverCustomerRequest = + GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) + val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) + + val result = + rfiClient.getFee( + GetFeeRequest.builder() + .sendAmount("10") + .sendAsset("USDC") + .receiveAsset("USDC") + .senderId(senderCustomer!!.id) + .receiverId(receiverCustomer!!.id) + .clientId("") + .build() + ) + + Assertions.assertNotNull(result) + JSONAssert.assertEquals( + org.stellar.anchor.platform.gson.toJson(result), + """{ + "fee": { + "asset": "USDC", + "amount": "0.30" + } + }""", + true + ) + } + } +} + +fun callbackApiTestAll() { + CallbackApiTests.setup() + + println("Performing Callback API tests...") + CallbackApiTests.setup() +} diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt new file mode 100644 index 0000000000..367321baab --- /dev/null +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt @@ -0,0 +1,223 @@ +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 +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 +import org.stellar.anchor.auth.JwtService +import org.stellar.anchor.event.models.TransactionEvent +import org.stellar.anchor.reference.client.PlatformApiClient +import org.stellar.anchor.util.GsonUtils + +lateinit var platformApiClient: PlatformApiClient + +class PlatformApiTests { + companion object { + fun setup() { + if (!::platformApiClient.isInitialized) { + val platformToAnchorJwtService = JwtService("myAnchorToPlatformSecret") + val authHelper = + AuthHelper.forJwtToken(platformToAnchorJwtService, 900000, "http://localhost:8081") + platformApiClient = PlatformApiClient(authHelper, "http://localhost:8080") + } + } + + fun `test sep24 doposit, withdraw, get and patch`() { + // TODO: test with deposit/withdraw then patch then get + } + + fun `test sep31 post, get and patch`() { + // 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 = + sep38Client.postQuote( + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "10", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + ) + + // 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) + assertEquals(txnRequest.amount, getTxResponse.amountIn.amount) + 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) + .status(TransactionEvent.Status.COMPLETED.status) + .amountOut(Amount(quote.buyAmount, quote.buyAsset)) + .build() + val patchTxResponse = + platformApiClient.patchTransaction( + PatchTransactionsRequest.builder().records(listOf(patchTxRequest)).build() + ) + assertEquals(1, patchTxResponse.records.size) + val patchedTx = patchTxResponse.records[0] + assertEquals(getTxResponse.id, patchedTx.id) + assertEquals(TransactionEvent.Status.COMPLETED.status, patchedTx.status) + assertEquals(quote.buyAmount, patchedTx.amountOut.amount) + assertEquals(quote.buyAsset, patchedTx.amountOut.asset) + 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"]) + assertNotNull(response["started_at"]) + assertNotNull(response["elapsed_time_ms"]) + assertNotNull(response["number_of_checks"]) + assertNotNull(response["version"]) + } + + 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 = + sep38Client.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) + 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) + 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) + assertTrue(patchedTx.updatedAt > patchedTx.startedAt) + + // 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.truncatedTo(SECONDS), + gotSep31TxResponse.transaction.completedAt.truncatedTo(SECONDS) + ) + } + } +} + +fun platformTestAll() { + PlatformApiTests.setup() + + println("Performing Platform API tests...") + PlatformApiTests.`test sep31 post, get and patch`() + PlatformApiTests.testHealth() + PlatformApiTests.testSep31UnhappyPath() +} 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 deleted file mode 100644 index 2c23cf481c..0000000000 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformTests.kt +++ /dev/null @@ -1,212 +0,0 @@ -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 -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 -import org.stellar.anchor.auth.JwtService -import org.stellar.anchor.event.models.TransactionEvent -import org.stellar.anchor.reference.client.PlatformApiClient -import org.stellar.anchor.util.GsonUtils -import org.stellar.anchor.util.Sep1Helper - -lateinit var platformApiClient: PlatformApiClient - -fun platformTestAll(toml: Sep1Helper.TomlContent, jwt: String) { - println("Performing Platform API tests...") - sep12Client = Sep12Client(toml.getString("KYC_SERVER"), jwt) - sep31Client = Sep31Client(toml.getString("DIRECT_PAYMENT_SERVER"), jwt) - sep38 = Sep38Client(toml.getString("ANCHOR_QUOTE_SERVER"), jwt) - - val platformToAnchorJwtService = JwtService("myAnchorToPlatformSecret") - val authHelper = - AuthHelper.forJwtToken(platformToAnchorJwtService, 900000, "http://localhost:8081") - platformApiClient = PlatformApiClient(authHelper, "http://localhost:8080") - - testHappyPath() - testHealth() -} - -fun testHappyPath() { - // 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 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) - assertEquals(txnRequest.amount, getTxResponse.amountIn.amount) - 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) - .status(TransactionEvent.Status.COMPLETED.status) - .amountOut(Amount(quote.buyAmount, quote.buyAsset)) - .build() - val patchTxResponse = - platformApiClient.patchTransaction( - PatchTransactionsRequest.builder().records(listOf(patchTxRequest)).build() - ) - assertEquals(1, patchTxResponse.records.size) - val patchedTx = patchTxResponse.records[0] - assertEquals(getTxResponse.id, patchedTx.id) - assertEquals(TransactionEvent.Status.COMPLETED.status, patchedTx.status) - assertEquals(quote.buyAmount, patchedTx.amountOut.amount) - assertEquals(quote.buyAsset, patchedTx.amountOut.asset) - 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"]) - assertNotNull(response["started_at"]) - assertNotNull(response["elapsed_time_ms"]) - assertNotNull(response["number_of_checks"]) - assertNotNull(response["version"]) -} - -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) - 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) - 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) - assertTrue(patchedTx.updatedAt > patchedTx.startedAt) - - // 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.truncatedTo(SECONDS), - gotSep31TxResponse.transaction.completedAt.truncatedTo(SECONDS) - ) -} diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt index 7cc31b277b..acdc1800ae 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt @@ -3,7 +3,7 @@ package org.stellar.anchor.platform import kotlin.test.assertFailsWith import org.stellar.anchor.api.exception.SepNotAuthorizedException import org.stellar.anchor.api.sep.sep10.ValidationRequest -import org.stellar.anchor.util.Sep1Helper +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml var CLIENT_WALLET_EXTRA_SIGNER_1_SECRET = "SDUDRKCL5AX7RDZ7S6JAPBNKLV6LRZXJSHN5OJDP32TIJB42ODPQODHY" var CLIENT_WALLET_EXTRA_SIGNER_2_SECRET = "SC52GRNSIODLPNGTXUCZ5NHBII4PYUKUVQCCWWIK6OB6P4AW4M37DXZK" @@ -11,48 +11,58 @@ var CLIENT_WALLET_EXTRA_SIGNER_2_SECRET = "SC52GRNSIODLPNGTXUCZ5NHBII4PYUKUVQCCW lateinit var sep10Client: Sep10Client lateinit var sep10ClientMultiSig: Sep10Client -fun sep10TestAll(toml: Sep1Helper.TomlContent): String { - println("Performing SEP10 tests...") - sep10Client = - Sep10Client( - toml.getString("WEB_AUTH_ENDPOINT"), - toml.getString("SIGNING_KEY"), - CLIENT_WALLET_ACCOUNT, - CLIENT_WALLET_SECRET - ) - - sep10ClientMultiSig = - Sep10Client( - toml.getString("WEB_AUTH_ENDPOINT"), - toml.getString("SIGNING_KEY"), - CLIENT_WALLET_ACCOUNT, - arrayOf( - CLIENT_WALLET_SECRET, - CLIENT_WALLET_EXTRA_SIGNER_1_SECRET, - CLIENT_WALLET_EXTRA_SIGNER_2_SECRET - ) - ) +class Sep10Tests { + companion object { + internal fun setup() { + if (!::sep10Client.isInitialized) { + sep10Client = + Sep10Client( + toml.getString("WEB_AUTH_ENDPOINT"), + toml.getString("SIGNING_KEY"), + CLIENT_WALLET_ACCOUNT, + CLIENT_WALLET_SECRET + ) + } - val jwt = testOk() - testUnsignedChallenge() - testMultiSig() + if (!::sep10ClientMultiSig.isInitialized) { + sep10ClientMultiSig = + Sep10Client( + toml.getString("WEB_AUTH_ENDPOINT"), + toml.getString("SIGNING_KEY"), + CLIENT_WALLET_ACCOUNT, + arrayOf( + CLIENT_WALLET_SECRET, + CLIENT_WALLET_EXTRA_SIGNER_1_SECRET, + CLIENT_WALLET_EXTRA_SIGNER_2_SECRET + ) + ) + } + } - return jwt -} + fun testMultiSig() { + sep10ClientMultiSig.auth() + } -fun testMultiSig() { - sep10ClientMultiSig.auth() -} + fun testOk(): String { + return sep10Client.auth() + } -fun testOk(): String { - return sep10Client.auth() + fun testUnsignedChallenge() { + val challenge = sep10Client.challenge() + + assertFailsWith( + exceptionClass = SepNotAuthorizedException::class, + block = { sep10Client.validate(ValidationRequest.of(challenge.transaction)) } + ) + } + } } -fun testUnsignedChallenge() { - val challenge = sep10Client.challenge() +fun sep10TestAll() { + Sep10Tests.setup() + println("Performing SEP10 tests...") - assertFailsWith( - exceptionClass = SepNotAuthorizedException::class, - block = { sep10Client.validate(ValidationRequest.of(challenge.transaction)) } - ) + Sep10Tests.testOk() + Sep10Tests.testUnsignedChallenge() + Sep10Tests.testMultiSig() } 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 7f12122bc2..5409b0f922 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 @@ -1,12 +1,13 @@ package org.stellar.anchor.platform -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.SepNotFoundException import org.stellar.anchor.api.sep.sep12.Sep12PutCustomerRequest import org.stellar.anchor.api.sep.sep12.Sep12Status +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.jwt +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml import org.stellar.anchor.util.GsonUtils -import org.stellar.anchor.util.Sep1Helper lateinit var sep12Client: Sep12Client @@ -42,56 +43,67 @@ const val testCustomer2Json = } """ -fun sep12TestAll(toml: Sep1Helper.TomlContent, jwt: String) { - println("Performing SEP12 tests...") - sep12Client = Sep12Client(toml.getString("KYC_SERVER"), jwt) - - sep12TestHappyPath() +class Sep12Tests { + companion object { + fun setup() { + println("Performing SEP12 tests...") + if (!::sep12Client.isInitialized) { + sep12Client = Sep12Client(toml.getString("KYC_SERVER"), jwt) + } + } + + fun testHappyPath() { + val customer = + GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + customer.emailAddress = null + + // Upload a customer + printRequest("Calling PUT /customer", customer) + var pr = sep12Client.putCustomer(customer) + printResponse(pr) + + // make sure the customer was uploaded correctly. + printRequest("Calling GET /customer", customer) + var gr = sep12Client.getCustomer(pr!!.id) + printResponse(gr) + + assertEquals(Sep12Status.NEEDS_INFO, gr?.status) + assertEquals(pr.id, gr?.id) + + customer.emailAddress = "john.doe@stellar.org" + customer.type = "sep31-receiver" + + // Modify the customer + printRequest("Calling PUT /customer", customer) + pr = sep12Client.putCustomer(customer) + printResponse(pr) + + // Make sure the customer is modified correctly. + printRequest("Calling GET /customer", customer) + gr = sep12Client.getCustomer(pr!!.id) + printResponse(gr) + + assertEquals(pr.id, gr?.id) + assertEquals(Sep12Status.ACCEPTED, gr?.status) + + // Delete the customer + printRequest("Calling DELETE /customer/$CLIENT_WALLET_ACCOUNT") + val code = sep12Client.deleteCustomer(CLIENT_WALLET_ACCOUNT) + printResponse(code) + // currently, not implemented + assertEquals(200, code) + + val id = pr.id + val ex: SepNotFoundException = assertThrows { sep12Client.getCustomer(id) } + assertEquals("customer for 'id' '$id' not found", ex.message) + println(ex) + } + } } -fun sep12TestHappyPath() { - val customer = - GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) - customer.emailAddress = null - - // Upload a customer - printRequest("Calling PUT /customer", customer) - var pr = sep12Client.putCustomer(customer) - printResponse(pr) - - // make sure the customer was uploaded correctly. - printRequest("Calling GET /customer", customer) - var gr = sep12Client.getCustomer(pr!!.id) - printResponse(gr) - - assertEquals(Sep12Status.NEEDS_INFO, gr?.status) - assertEquals(pr.id, gr?.id) - - customer.emailAddress = "john.doe@stellar.org" - customer.type = "sep31-receiver" - - // Modify the customer - printRequest("Calling PUT /customer", customer) - pr = sep12Client.putCustomer(customer) - printResponse(pr) - - // Make sure the customer is modified correctly. - printRequest("Calling GET /customer", customer) - gr = sep12Client.getCustomer(pr!!.id) - printResponse(gr) - - assertEquals(pr.id, gr?.id) - assertEquals(Sep12Status.ACCEPTED, gr?.status) - - // Delete the customer - printRequest("Calling DELETE /customer/$CLIENT_WALLET_ACCOUNT") - val code = sep12Client.deleteCustomer(CLIENT_WALLET_ACCOUNT) - printResponse(code) - // currently, not implemented - assertEquals(200, code) - - val id = pr.id - val ex: SepNotFoundException = assertThrows { sep12Client.getCustomer(id) } - assertEquals("customer for 'id' '$id' not found", ex.message) - println(ex) +fun sep12TestAll() { + Sep12Tests.setup() + + println("Performing Sep12 tests...") + Sep12Tests.testHappyPath() } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt index 4d8949dc4b..5e3e42bf25 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt @@ -2,12 +2,90 @@ package org.stellar.anchor.platform -import org.junit.jupiter.api.Assertions.* -import org.stellar.anchor.util.Sep1Helper +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.assertThrows +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode +import org.skyscreamer.jsonassert.JSONCompareMode.LENIENT +import org.stellar.anchor.api.exception.SepException +import org.stellar.anchor.api.sep.sep24.GetTransactionResponse +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.jwt +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml lateinit var sep24Client: Sep24Client -const val withdrawJson = +lateinit var savedWithdrawTransaction: GetTransactionResponse +lateinit var savedDepositTransaction: GetTransactionResponse + +class Sep24Tests { + companion object { + fun setup() { + sep24Client = Sep24Client(toml.getString("TRANSFER_SERVER_SEP0024"), jwt) + } + fun `test Sep24 info endpoint`() { + printRequest("Calling GET /info") + val info = sep24Client.getInfo() + JSONAssert.assertEquals(gson.toJson(info), expectedSep24Info, JSONCompareMode.STRICT) + } + + fun `test Sep24 withdraw`() { + printRequest("POST /transactions/withdraw/interactive") + val withdrawRequest = gson.fromJson(withdrawRequest, HashMap::class.java) + val txn = sep24Client.withdraw(withdrawRequest as HashMap) + printResponse("POST /transactions/withdraw/interactive response:", txn) + savedWithdrawTransaction = sep24Client.getTransaction(txn.id, "USDC") + printResponse(savedWithdrawTransaction) + JSONAssert.assertEquals( + expectedSep24WithdrawResponse, + json(savedWithdrawTransaction), + LENIENT + ) + } + + fun `test Sep24 deposit`() { + printRequest("POST /transactions/withdraw/interactive") + val depositRequest = gson.fromJson(depositRequest, HashMap::class.java) + val txn = sep24Client.deposit(depositRequest as HashMap) + printResponse("POST /transactions/deposit/interactive response:", txn) + savedDepositTransaction = sep24Client.getTransaction(txn.id, "USDC") + printResponse(savedDepositTransaction) + JSONAssert.assertEquals(expectedSep24DepositResponse, json(savedDepositTransaction), LENIENT) + } + + fun `test PlatformAPI GET transaction for deposit and withdrawal`() { + val actualWithdrawTxn = + platformApiClient.getTransaction(savedWithdrawTransaction.transaction.id) + assertEquals(actualWithdrawTxn.id, savedWithdrawTransaction.transaction.id) + JSONAssert.assertEquals(expectedWithdrawTransactionResponse, json(actualWithdrawTxn), LENIENT) + + val actualDepositTxn = + platformApiClient.getTransaction(savedDepositTransaction.transaction.id) + printResponse(actualDepositTxn) + assertEquals(actualDepositTxn.id, savedDepositTransaction.transaction.id) + JSONAssert.assertEquals(expectedDepositTransactionResponse, json(actualDepositTxn), LENIENT) + } + + fun `test GET transactions with bad ids`() { + val badTxnIds = listOf("null", "bad id", "123", null) + for (txnId in badTxnIds) { + assertThrows { platformApiClient.getTransaction(txnId) } + } + } + } +} + +fun sep24TestAll() { + Sep24Tests.setup() + + println("Performing SEP24 tests...") + Sep24Tests.`test Sep24 info endpoint`() + Sep24Tests.`test Sep24 withdraw`() + Sep24Tests.`test Sep24 deposit`() + Sep24Tests.`test PlatformAPI GET transaction for deposit and withdrawal`() + Sep24Tests.`test GET transactions with bad ids`() +} + +const val withdrawRequest = """{ "asset_code": "USDC", "asset_issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", @@ -15,35 +93,121 @@ const val withdrawJson = "lang": "en" }""" -fun sep24TestAll(toml: Sep1Helper.TomlContent, jwt: String) { - println("Performing SEP24 tests...") - sep24Client = Sep24Client(toml.getString("TRANSFER_SERVER_SEP0024"), jwt) +const val depositRequest = + """{ + "asset_code": "USDC", + "asset_issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "account": "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG", + "lang": "en" +}""" - testSep24TestInfo() - testSep24PostInteractive() -} +val expectedSep24Info = + """ + { + "deposit": { + "JPYC": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000, + "fee_minimum": 0 + }, + "USD": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000, + "fee_minimum": 0 + }, + "USDC": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000, + "fee_minimum": 0 + } + }, + "withdraw": { + "JPYC": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "USD": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 0, + "max_amount": 10000 + }, + "USDC": { + "enabled": true, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + } + }, + "fee": { + "enabled": true + }, + "features": { + "account_creation": true, + "claimable_balances": true + } + } +""" + .trimIndent() -fun testSep24TestInfo() { - printRequest("Calling GET /info") - val info = sep24Client.getInfo() - printResponse(info) - assertTrue(info.deposit.isNotEmpty()) - assertTrue(info.withdraw.isNotEmpty()) -} +val expectedSep24WithdrawResponse = + """ + { + "transaction": { + "kind": "withdrawal", + "status": "incomplete", + "more_info_url": "http://www.stellar.org", + "refunded": false, + "from": "GAIUIZPHLIHQEMNJGSZKCEUWHAZVGUZDBDMO2JXNAJZZZVNSVHQCEWJ4" + } + } +""" + .trimIndent() -fun testSep24PostInteractive() { - printRequest("POST /transactions/withdraw/interactive") - val withdrawRequest = gson.fromJson(withdrawJson, HashMap::class.java) - val txn = sep24Client.withdraw(withdrawRequest as HashMap) - printResponse("POST /transactions/withdraw/interactive response:", txn) - val savedTxn = sep24Client.getTransaction(txn.id, "USDC") - printResponse(savedTxn) - assertEquals(txn.id, savedTxn.transaction.id) - assertEquals("withdrawal", savedTxn.transaction.kind) - assertEquals("incomplete", savedTxn.transaction.status) - assertFalse(savedTxn.transaction.refunded) - assertEquals( - "GAIUIZPHLIHQEMNJGSZKCEUWHAZVGUZDBDMO2JXNAJZZZVNSVHQCEWJ4", - savedTxn.transaction.from - ) -} +val expectedSep24DepositResponse = + """ + { + "transaction": { + "kind": "deposit", + "status": "incomplete", + "more_info_url": "http://www.stellar.org", + "refunded": false, + "to": "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG" + } + } +""" + .trimIndent() + +val expectedWithdrawTransactionResponse = + """ + { + "sep": 24, + "kind": "withdrawal", + "status": "incomplete" + } +""" + .trimIndent() + +val expectedDepositTransactionResponse = + """ + { + "sep": 24, + "kind": "deposit", + "status": "incomplete" + } +""" + .trimIndent() 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 77cdd997f9..c610464b03 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,14 +1,16 @@ package org.stellar.anchor.platform import kotlin.test.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.assertThrows +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode 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.platform.AnchorPlatformIntegrationTest.Companion.jwt +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml import org.stellar.anchor.util.GsonUtils -import org.stellar.anchor.util.Sep1Helper lateinit var sep31Client: Sep31Client @@ -28,71 +30,194 @@ const val postTxnJson = } }""" -fun sep31TestAll(toml: Sep1Helper.TomlContent, jwt: String) { - println("Performing SEP31 tests...") - sep12Client = Sep12Client(toml.getString("KYC_SERVER"), jwt) - sep31Client = Sep31Client(toml.getString("DIRECT_PAYMENT_SERVER"), jwt) - sep38 = Sep38Client(toml.getString("ANCHOR_QUOTE_SERVER"), jwt) +class Sep31Tests { + companion object { + fun setup() { + println("Performing SEP31 tests...") + if (!::sep31Client.isInitialized) + sep31Client = Sep31Client(toml.getString("DIRECT_PAYMENT_SERVER"), jwt) + } + fun `test Sep31 info endpoint`() { + printRequest("Calling GET /info") + val info = sep31Client.getInfo() + JSONAssert.assertEquals(gson.toJson(info), wantedSep31Info, JSONCompareMode.STRICT) + } - testSep31TestInfo() - testSep31PostAndGetTransaction() - testBadAsset() -} + fun testSep31PostAndGetTransaction() { + // Create sender customer + val senderCustomerRequest = + GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + val senderCustomer = sep12Client.putCustomer(senderCustomerRequest) -fun testSep31TestInfo() { - printRequest("Calling GET /info") - val info = sep31Client.getInfo() - printResponse(info) - assertTrue(info.receive.isNotEmpty()) -} + // Create receiver customer + val receiverCustomerRequest = + GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) + val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) -fun testSep31PostAndGetTransaction() { - // Create sender customer - val senderCustomerRequest = - GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) - val senderCustomer = sep12Client.putCustomer(senderCustomerRequest) + // Create asset quote + val quote = + sep38Client.postQuote( + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "10", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + ) - // Create receiver customer - val receiverCustomerRequest = - GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) - val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) + // 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) + assertEquals( + "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + postTxResponse.stellarAccountId + ) - // Create asset quote - val quote = - sep38.postQuote( - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "10", - "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - ) + // 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) + } - // 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) - assertEquals( - "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - postTxResponse.stellarAccountId - ) + fun testBadAsset() { + val customer = + GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + val pr = sep12Client.putCustomer(customer) - // 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) + // Post Sep31 transaction. + val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) + txnRequest.assetCode = "bad-asset-code" + txnRequest.receiverId = pr!!.id + assertThrows { sep31Client.postTransaction(txnRequest) } + } + } } -fun testBadAsset() { - val customer = - GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) - val pr = sep12Client.putCustomer(customer) +fun sep31TestAll() { + Sep31Tests.setup() - // Post Sep31 transaction. - val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) - txnRequest.assetCode = "bad-asset-code" - txnRequest.receiverId = pr!!.id - assertThrows { sep31Client.postTransaction(txnRequest) } + println("Performing Sep31 tests...") + Sep31Tests.`test Sep31 info endpoint`() + Sep31Tests.testSep31PostAndGetTransaction() + Sep31Tests.testBadAsset() } + +val wantedSep31Info = + """ + { + "receive": { + "JPYC": { + "enabled": true, + "quotes_supported": true, + "quotes_required": false, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000, + "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", + "optional": false + }, + "receiver_account_number": { + "description": "bank account number of the destination", + "optional": false + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ], + "optional": false + } + } + } + }, + "USDC": { + "enabled": true, + "quotes_supported": true, + "quotes_required": false, + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000, + "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", + "optional": false + }, + "receiver_account_number": { + "description": "bank account number of the destination", + "optional": false + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ], + "optional": false + } + } + } + } + } + } +""" + .trimIndent() 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 22dcff13a5..d6a410ab51 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 @@ -6,80 +6,92 @@ import kotlin.test.assertEquals import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.sep.sep38.Sep38Context.* -import org.stellar.anchor.util.Sep1Helper +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.jwt +import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml -lateinit var sep38: Sep38Client +lateinit var sep38Client: Sep38Client -fun sep38TestAll(toml: Sep1Helper.TomlContent, jwt: String) { - println("Performing SEP38 tests...") - sep38 = Sep38Client(toml.getString("ANCHOR_QUOTE_SERVER"), jwt) +class Sep38Tests { + companion object { + fun setup() { + if (!::sep38Client.isInitialized) { + sep38Client = Sep38Client(toml.getString("ANCHOR_QUOTE_SERVER"), jwt) + } + } - sep38TestHappyPath() - testSellOverAssetLimit() -} + fun sep38TestHappyPath() { + // GET {SEP38}/info + printRequest("Calling GET /info") + val info = sep38Client.getInfo() + printResponse(info) + + // GET {SEP38}/prices + printRequest("Calling GET /prices") + val prices = sep38Client.getPrices("iso4217:USD", "100") + printResponse(prices) -fun sep38TestHappyPath() { - // GET {SEP38}/info - printRequest("Calling GET /info") - val info = sep38.getInfo() - printResponse(info) + // GET {SEP38}/price + printRequest("Calling GET /price") + val price = + sep38Client.getPrice( + "iso4217:USD", + "100", + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + SEP31 + ) + printResponse(price) - // GET {SEP38}/prices - printRequest("Calling GET /prices") - val prices = sep38.getPrices("iso4217:USD", "100") - printResponse(prices) + // POST {SEP38}/quote + printRequest("Calling POST /quote") + var postQuote = + sep38Client.postQuote( + "iso4217:USD", + "100", + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + SEP31 + ) + printResponse(postQuote) - // GET {SEP38}/price - printRequest("Calling GET /price") - val price = - sep38.getPrice( - "iso4217:USD", - "100", - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - SEP31 - ) - printResponse(price) + // POST {SEP38}/quote with `expires_after` + printRequest("Calling POST /quote") + val expireAfter = + DateTimeFormatter.ISO_INSTANT.parse("2022-04-30T02:15:44.000Z", Instant::from) + postQuote = + sep38Client.postQuote( + "iso4217:USD", + "100", + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + SEP31, + expireAfter = expireAfter + ) + printResponse(postQuote) - // POST {SEP38}/quote - printRequest("Calling POST /quote") - var postQuote = - sep38.postQuote( - "iso4217:USD", - "100", - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - SEP31 - ) - printResponse(postQuote) + // GET {SEP38}/quote/{id} + printRequest("Calling GET /quote") + val getQuote = sep38Client.getQuote(postQuote.id) + printResponse(getQuote) + assertEquals(postQuote, getQuote) + } - // POST {SEP38}/quote with `expires_after` - printRequest("Calling POST /quote") - val expireAfter = DateTimeFormatter.ISO_INSTANT.parse("2022-04-30T02:15:44.000Z", Instant::from) - postQuote = - sep38.postQuote( - "iso4217:USD", - "100", - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - SEP31, - expireAfter = expireAfter - ) - printResponse(postQuote) + fun testSellOverAssetLimit() { + printRequest("Calling GET /price") - // GET {SEP38}/quote/{id} - printRequest("Calling GET /quote") - val getQuote = sep38.getQuote(postQuote.id) - printResponse(getQuote) - assertEquals(postQuote, getQuote) + assertThrows { + sep38Client.getPrice( + "iso4217:USD", + "10000000000", + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + SEP31 + ) + } + } + } } -fun testSellOverAssetLimit() { - printRequest("Calling GET /price") +fun sep38TestAll() { + Sep38Tests.setup() - assertThrows { - sep38.getPrice( - "iso4217:USD", - "10000000000", - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - SEP31 - ) - } + println("Performing SEP38 tests...") + Sep38Tests.sep38TestHappyPath() + Sep38Tests.testSellOverAssetLimit() } 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 988bdeaa27..3882a6d9f5 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 @@ -2,14 +2,10 @@ package org.stellar.anchor.platform import org.apache.commons.cli.* import org.stellar.anchor.auth.JwtService -import org.stellar.anchor.auth.JwtToken -import org.stellar.anchor.util.Sep1Helper -import org.stellar.anchor.util.Sep1Helper.TomlContent var CLIENT_WALLET_ACCOUNT = "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG" var CLIENT_WALLET_SECRET = "SBHTWEF5U7FK53FLGDMBQYGXRUJ24VBM3M6VDXCHRIGCRG3Z64PH45LW" -var jwt: String? = null val jwtService = JwtService("secret") fun main(args: Array) { @@ -58,31 +54,30 @@ fun main(args: Array) { resourceAsString("classpath:/sep1/test-stellar.toml") } - val toml = Sep1Helper.parse(tomlString) val tests = cmd.getOptionValues("p") if ("sep10" in tests) { - jwt = sep10TestAll(toml) + sep10TestAll() } if ("sep12" in tests) { - sep12TestAll(toml, getOrCreateJwt(toml)!!) + sep12TestAll() } if ("sep24" in tests) { - sep24TestAll(toml, getOrCreateJwt(toml)!!) + sep24TestAll() } if ("sep31" in tests) { - sep31TestAll(toml, getOrCreateJwt(toml)!!) + sep31TestAll() } if ("sep38" in tests) { - sep38TestAll(toml, getOrCreateJwt(toml)!!) + sep38TestAll() } if ("platform" in tests) { - platformTestAll(toml, getOrCreateJwt(toml)!!) + platformTestAll() } if ("ref" in tests) { @@ -93,24 +88,6 @@ fun main(args: Array) { } } -fun getOrCreateJwt(tomlContent: TomlContent): String? { - if (jwt == null) { - val issuedAt: Long = System.currentTimeMillis() / 1000L - val token = - JwtToken.of( - tomlContent.getString("WEB_AUTH_ENDPOINT"), - CLIENT_WALLET_ACCOUNT, - issuedAt, - issuedAt + 60, - "", - null - ) - jwt = jwtService.encode(token) - } - - return jwt -} - fun printUsage(options: Options?) { val helper = HelpFormatter() helper.optionComparator = null diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/StellarObserverTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/StellarObserverTests.kt new file mode 100644 index 0000000000..7abf360741 --- /dev/null +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/StellarObserverTests.kt @@ -0,0 +1,69 @@ +package org.stellar.anchor.platform + +import java.util.* +import java.util.concurrent.TimeUnit +import okhttp3.OkHttpClient +import okhttp3.Request +import org.junit.jupiter.api.Assertions + +class StellarObserverTests { + companion object { + private val httpClient: OkHttpClient = + OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.MINUTES) + .readTimeout(10, TimeUnit.MINUTES) + .writeTimeout(10, TimeUnit.MINUTES) + .build() + + fun setup() {} + + fun testStellarObserverHealth() { + val httpRequest = + Request.Builder() + .url( + "http://localhost:${AnchorPlatformIntegrationTest.OBSERVER_HEALTH_SERVER_PORT}/health" + ) + .header("Content-Type", "application/json") + .get() + .build() + val response = httpClient.newCall(httpRequest).execute() + Assertions.assertEquals(200, response.code) + + val responseBody = gson.fromJson(response.body!!.string(), HashMap::class.java) + Assertions.assertEquals(5, responseBody.size) + Assertions.assertNotNull(responseBody["started_at"]) + Assertions.assertNotNull(responseBody["elapsed_time_ms"]) + Assertions.assertNotNull(responseBody["number_of_checks"]) + Assertions.assertEquals(2.0, responseBody["number_of_checks"]) + Assertions.assertNotNull(responseBody["version"]) + Assertions.assertNotNull(responseBody["checks"]) + + val checks = responseBody["checks"] as Map<*, *> + + Assertions.assertEquals(2, checks.size) + Assertions.assertNotNull(checks["config"]) + Assertions.assertNotNull(checks["stellar_payment_observer"]) + + val stellarPaymentObserverCheck = checks["stellar_payment_observer"] as Map<*, *> + Assertions.assertEquals(2, stellarPaymentObserverCheck.size) + Assertions.assertEquals("GREEN", stellarPaymentObserverCheck["status"]) + + val observerStreams = stellarPaymentObserverCheck["streams"] as List<*> + Assertions.assertEquals(1, observerStreams.size) + + val stream1 = observerStreams[0] as Map<*, *> + Assertions.assertEquals(5, stream1.size) + Assertions.assertEquals(false, stream1["thread_shutdown"]) + Assertions.assertEquals(false, stream1["thread_terminated"]) + Assertions.assertEquals(false, stream1["stopped"]) + Assertions.assertNotNull(stream1["last_event_id"]) + } + } +} + +fun stellarObserverTestAll() { + StellarObserverTests.setup() + + println("Performing Stellar observer tests...") + StellarObserverTests.setup() +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index 5dc315ddbf..e06894dddd 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -4,10 +4,13 @@ import com.google.gson.annotations.SerializedName; import com.vladmihalcea.hibernate.type.json.JsonType; import java.time.Instant; +import java.util.List; import javax.persistence.*; import lombok.Getter; import lombok.Setter; +import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.stellar.anchor.api.shared.StellarTransaction; import org.stellar.anchor.sep24.Sep24Refunds; import org.stellar.anchor.sep24.Sep24Transaction; import org.stellar.anchor.util.GsonUtils; @@ -60,12 +63,19 @@ public class JdbcSep24Transaction implements Sep24Transaction, SepTransaction { @SerializedName("completed_at") Instant completedAt; + @SerializedName("updated_at") + Instant updatedAt; + @SerializedName("transaction_id") String transactionId; @SerializedName("stellar_transaction_id") String stellarTransactionId; + @Column(columnDefinition = "json") + @Type(type = "json") + List stellarTransactions; + @SerializedName("external_transaction_id") String externalTransactionId; 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 e41ffbd3c0..4b41cbddbf 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 @@ -9,9 +9,11 @@ import java.util.Map; import javax.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.springframework.beans.BeanUtils; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.shared.StellarId; import org.stellar.anchor.api.shared.StellarTransaction; @@ -26,6 +28,7 @@ @Access(AccessType.FIELD) @Table(name = "sep31_transaction") @TypeDef(name = "json", typeClass = JsonType.class) +@NoArgsConstructor public class JdbcSep31Transaction implements Sep31Transaction, SepTransaction { static Gson gson = GsonUtils.getInstance(); @@ -134,18 +137,18 @@ public void setRequiredInfoUpdatesJson(String requiredInfoUpdatesJson) { Boolean refunded; - // Ignored by JPA - @Transient Refunds refunds; + @Column(columnDefinition = "json") + @Type(type = "json") + JdbcSep31Refunds refunds; - @Access(AccessType.PROPERTY) - @Column(name = "refunds") - public String getRefundsJson() { - return gson.toJson(this.refunds); + public Refunds getRefunds() { + return refunds; } - public void setRefundsJson(String refundsJson) { - if (refundsJson != null) { - this.refunds = gson.fromJson(refundsJson, JdbcSep31Refunds.class); + public void setRefunds(Refunds refunds) { + if (refunds != null) { + this.refunds = new JdbcSep31Refunds(); + BeanUtils.copyProperties(refunds, this.refunds); } } 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 d3f01f2ae6..b00d656cab 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 @@ -7,7 +7,10 @@ import io.micrometer.core.instrument.Metrics; import java.time.Instant; -import java.util.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -20,8 +23,16 @@ import org.stellar.anchor.api.platform.PatchTransactionsRequest; import org.stellar.anchor.api.platform.PatchTransactionsResponse; import org.stellar.anchor.api.sep.AssetInfo; +import org.stellar.anchor.api.sep.SepTransactionStatus; 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.platform.data.JdbcSep24Transaction; +import org.stellar.anchor.sep24.Sep24RefundPayment; +import org.stellar.anchor.sep24.Sep24Refunds; +import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.sep31.Refunds; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.sep31.Sep31TransactionStore; @@ -34,18 +45,9 @@ @Service public class TransactionService { private final Sep38QuoteStore quoteStore; - private final Sep31TransactionStore txnStore; + private final Sep31TransactionStore txn31Store; + private final Sep24TransactionStore txn24Store; private final List assets; - static final List validStatuses = - List.of( - PENDING_STELLAR.getName(), - PENDING_CUSTOMER_INFO_UPDATE.getName(), - PENDING_RECEIVER.getName(), - PENDING_EXTERNAL.getName(), - COMPLETED.getName(), - REFUNDED.getName(), - EXPIRED.getName(), - ERROR.getName()); static boolean isStatusError(String status) { return List.of(PENDING_CUSTOMER_INFO_UPDATE.getName(), EXPIRED.getName(), ERROR.getName()) @@ -53,9 +55,13 @@ static boolean isStatusError(String status) { } TransactionService( - Sep38QuoteStore quoteStore, Sep31TransactionStore txnStore, AssetService assetService) { + Sep24TransactionStore txn24Store, + Sep31TransactionStore txn31Store, + Sep38QuoteStore quoteStore, + AssetService assetService) { + this.txn24Store = txn24Store; + this.txn31Store = txn31Store; this.quoteStore = quoteStore; - this.txnStore = txnStore; this.assets = assetService.listAllAssets(); } @@ -65,12 +71,17 @@ public GetTransactionResponse getTransaction(String txnId) throws AnchorExceptio 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)); + Sep31Transaction txn31 = txn31Store.findByTransactionId(txnId); + if (txn31 != null) { + return toGetTransactionResponse(txn31); } - return txn.toPlatformApiGetTransactionResponse(); + JdbcSep24Transaction txn24 = (JdbcSep24Transaction) txn24Store.findByTransactionId(txnId); + if (txn24 != null) { + return toGetTransactionResponse(txn24); + } + + throw new NotFoundException(String.format("transaction (id=%s) is not found", txnId)); } public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest request) @@ -78,7 +89,7 @@ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest requ List patchRequests = request.getRecords(); List ids = patchRequests.stream().map(PatchTransactionRequest::getId).collect(Collectors.toList()); - List fetchedTxns = txnStore.findByTransactionIds(ids); + List fetchedTxns = txn31Store.findByTransactionIds(ids); Map sep31Transactions = fetchedTxns.stream() .collect(Collectors.toMap(Sep31Transaction::getId, Function.identity())); @@ -98,14 +109,14 @@ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest requ if (!txnOriginalStatus.equals(txn.getStatus())) { statusUpdatedTxns.add(txn); } - responses.add(txn.toPlatformApiGetTransactionResponse()); + responses.add(toGetTransactionResponse(txn)); } else { throw new BadRequestException(String.format("transaction(id=%s) not found", patch.getId())); } } for (Sep31Transaction txn : txnsToSave) { // TODO: consider 2-phase commit DB transaction management. - txnStore.save(txn); + txn31Store.save(txn); } for (Sep31Transaction txn : statusUpdatedTxns) { Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", txn.getStatus()) @@ -190,7 +201,7 @@ void updateSep31Transaction(PatchTransactionRequest ptr, Sep31Transaction txn) } if (ptr.getRefunds() != null) { - Refunds updatedRefunds = Refunds.of(ptr.getRefunds(), txnStore); + Refunds updatedRefunds = Refunds.of(ptr.getRefunds(), txn31Store); // TODO: validate refunds if (!Objects.equals(txn.getRefunds(), updatedRefunds)) { txn.setRefunds(updatedRefunds); @@ -223,7 +234,7 @@ void updateSep31Transaction(PatchTransactionRequest ptr, Sep31Transaction txn) * @throws BadRequestException if the provided status is not supported */ void validateIfStatusIsSupported(String status) throws BadRequestException { - if (!validStatuses.contains(status)) { + if (!SepTransactionStatus.isValid(status)) { throw new BadRequestException(String.format("invalid status(%s)", status)); } } @@ -299,4 +310,81 @@ void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { } } } + + GetTransactionResponse toGetTransactionResponse(Sep31Transaction txn) { + Refund refunds = null; + if (txn.getRefunds() != null) { + refunds = txn.getRefunds().toPlatformApiRefund(txn.getAmountInAsset()); + } + + return org.stellar.anchor.api.platform.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 are meant to be the same. + .refunds(refunds) + .stellarTransactions(txn.getStellarTransactions()) + .externalTransactionId(txn.getExternalTransactionId()) + .customers(txn.getCustomers()) + .creator(txn.getCreator()) + .build(); + } + + RefundPayment toRefundPayment(Sep24RefundPayment refundPayment, String assetName) { + return org.stellar.anchor.api.shared.RefundPayment.builder() + .id(refundPayment.getId()) + .idType(org.stellar.anchor.api.shared.RefundPayment.IdType.STELLAR) + .amount(new Amount(refundPayment.getAmount(), assetName)) + .fee(new Amount(refundPayment.getFee(), assetName)) + .requestedAt(null) + .refundedAt(null) + .build(); + } + + org.stellar.anchor.api.shared.Refund toRefunds(Sep24Refunds refunds, String assetName) { + // build payments + org.stellar.anchor.api.shared.RefundPayment[] payments = + refunds.getRefundPayments().stream() + .map(refundPayment -> toRefundPayment(refundPayment, assetName)) + .toArray(org.stellar.anchor.api.shared.RefundPayment[]::new); + + return org.stellar.anchor.api.shared.Refund.builder() + .amountRefunded(new Amount(refunds.getAmountRefunded(), assetName)) + .amountFee(new Amount(refunds.getAmountFee(), assetName)) + .payments(payments) + .build(); + } + + GetTransactionResponse toGetTransactionResponse(JdbcSep24Transaction txn) { + Refund refunds = null; + if (txn.getRefunds() != null) { + refunds = toRefunds(txn.getRefunds(), txn.getAmountInAsset()); + } + + return org.stellar.anchor.api.platform.GetTransactionResponse.builder() + .id(txn.getId()) + .sep(24) + .kind(txn.getKind()) + .status(txn.getStatus()) + .amountIn(new Amount(txn.getAmountIn(), txn.getAmountInAsset())) + .amountOut(new Amount(txn.getAmountOut(), txn.getAmountOutAsset())) + .amountFee(new Amount(txn.getAmountFee(), txn.getAmountFeeAsset())) + .startedAt(txn.getStartedAt()) + .updatedAt(txn.getUpdatedAt()) + .completedAt(txn.getCompletedAt()) + .refunds(refunds) + .stellarTransactions(txn.getStellarTransactions()) + .externalTransactionId(txn.getExternalTransactionId()) + .build(); + } } 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 index 39142eb78d..f7635d9bab 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/data/JdbcSep31TransactionTest.kt @@ -1,10 +1,5 @@ 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 = """ @@ -36,28 +31,4 @@ class JdbcSep31TransactionTest { } """ .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 d65d7237c8..d223ca0c06 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 @@ -10,6 +10,8 @@ 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.junit.jupiter.params.provider.NullSource +import org.junit.jupiter.params.provider.ValueSource import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode.STRICT import org.stellar.anchor.api.exception.AnchorException @@ -17,17 +19,18 @@ 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.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.asset.ResourceJsonAssetService -import org.stellar.anchor.event.models.TransactionEvent 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.* +import org.stellar.anchor.sep24.Sep24TransactionStore +import org.stellar.anchor.sep31.Refunds +import org.stellar.anchor.sep31.Sep31TransactionStore import org.stellar.anchor.sep38.Sep38Quote import org.stellar.anchor.sep38.Sep38QuoteStore import org.stellar.anchor.util.GsonUtils @@ -40,18 +43,26 @@ class TransactionServiceTest { "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" private const val TEST_ACCOUNT = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" private const val TEST_MEMO = "test memo" + private const val TEST_TXN_ID = "a4baff5f-778c-43d6-bbef-3e9fb41d096e" private val gson = GsonUtils.getInstance() } @MockK(relaxed = true) private lateinit var sep38QuoteStore: Sep38QuoteStore @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore + @MockK(relaxed = true) private lateinit var sep24TransactionStore: Sep24TransactionStore @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) + transactionService = + TransactionService( + sep24TransactionStore, + sep31TransactionStore, + sep38QuoteStore, + assetService + ) } @AfterEach @@ -74,6 +85,7 @@ class TransactionServiceTest { // non-existent transaction is rejected with 404 every { sep31TransactionStore.findByTransactionId(any()) } returns null + every { sep24TransactionStore.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) @@ -81,150 +93,18 @@ class TransactionServiceTest { @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 { JdbcSep31RefundPayment() } - // mock time - val mockStartedAt = Instant.now().minusSeconds(180) - val mockUpdatedAt = mockStartedAt.plusSeconds(60) - 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() - 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") - .stellarTransactions(listOf(stellarTransaction)) - .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 mockSep31Transaction = gson.fromJson(jsonSep31Transaction, JdbcSep31Transaction::class.java) + val wantGetTransactionResponse = + gson.fromJson(wantedGetTransactionResponse, GetTransactionResponse::class.java) - val gotGetTransactionResponse = transactionService.getTransaction(txId) + every { sep31TransactionStore.findByTransactionId(TEST_TXN_ID) } returns mockSep31Transaction + val gotGetTransactionResponse = transactionService.getTransaction(TEST_TXN_ID) - 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 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(stellarTransaction)) - .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) } @@ -290,33 +170,24 @@ class TransactionServiceTest { @Test fun test_validateAsset() { this.assetService = ResourceJsonAssetService("test_assets.json") - transactionService = TransactionService(sep38QuoteStore, sep31TransactionStore, assetService) + transactionService = + TransactionService( + sep24TransactionStore, + sep31TransactionStore, + sep38QuoteStore, + 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", - "REFUNDED", - "EXPIRED", - "ERROR" - ] - ) - fun test_validateIfStatusIsSupported_failure(sepTxnStatus: SepTransactionStatus) { - val ex: Exception = assertThrows { - transactionService.validateIfStatusIsSupported(sepTxnStatus.getName()) - } + @NullSource + @ValueSource(strings = ["pending_anchors", "null", "bad_status"]) + fun test_validateIfStatusIsSupported_failure(status: String?) { + val ex: Exception = assertThrows { transactionService.validateIfStatusIsSupported(status) } assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("invalid status(${sepTxnStatus.getName()})", ex.message) + assertEquals("invalid status(${status})", ex.message) } @ParameterizedTest @@ -404,7 +275,13 @@ class TransactionServiceTest { every { sep38QuoteStore.findByQuoteId(quoteId) } returns mockSep38Quote this.assetService = ResourceJsonAssetService("test_assets.json") - transactionService = TransactionService(sep38QuoteStore, sep31TransactionStore, assetService) + transactionService = + TransactionService( + sep24TransactionStore, + sep31TransactionStore, + sep38QuoteStore, + assetService + ) assertEquals(mockSep31Transaction.startedAt, mockSep31Transaction.updatedAt) assertNull(mockSep31Transaction.completedAt) @@ -437,4 +314,187 @@ class TransactionServiceTest { STRICT ) } + + val jsonSep31Transaction = + """ + { + "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", + "status": "pending_receiver", + "status_eta": 120, + "amount_in": "100.0000", + "amount_in_asset": "iso4217:USD", + "amount_out": "98.0000000", + "amount_out_asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", + "amount_fee": "2.0000", + "amount_fee_asset": "iso4217:USD", + "stellar_account_id": "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR", + "stellar_memo": "test memo", + "stellar_memo_type": "text", + "started_at": "2022-12-19T02:06:44.500182800Z", + "completed_at": "2022-12-19T02:09:44.500182800Z", + "stellar_transaction_id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "stellarTransactions": [ + { + "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "memo": "my-memo", + "memo_type": "text", + "created_at": "2022-12-19T02:08:44.500182800Z", + "envelope": "here_comes_the_envelope", + "payments": [ + { + "id": "4609238642995201", + "amount": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "payment_type": "payment", + "source_account": "GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ", + "destination_account": "GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7" + } + ] + } + ], + "external_transaction_id": "external-tx-id", + "required_info_message": "Please don\u0027t forget to foo bar", + "quote_id": "quote-id", + "client_domain": "test.com", + "sender_id": "6c1770b0-0ea4-11ed-861d-0242ac120002", + "receiver_id": "31212353-f265-4dba-9eb4-0bbeda3ba7f2", + "creator": { + "id": "141ee445-f32c-4c38-9d25-f4475d6c5558" + }, + "required_info_updates": { + "transaction": { + "receiver_account_number": { + "description": "bank account number of the destination", + "optional": false + } + } + }, + "refunded": true, + "refunds": { + "amount_refunded": "90.0000", + "amount_fee": "8.0000", + "payments": [ + { + "id": "1111", + "amount": "50.0000", + "fee": "4.0000" + }, + { + "id": "2222", + "amount": "40.0000", + "fee": "4.0000" + } + ] + }, + "updatedAt": "2022-12-19T02:07:44.500182800Z", + "transferReceivedAt": "2022-12-19T02:08:44.500182800Z", + "amountExpected": "100" + } + """ + .trimIndent() + + val wantedGetTransactionResponse = + """ + { + "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", + "sep": 31, + "kind": "receive", + "status": "pending_receiver", + "amount_expected": { + "amount": "100", + "asset": "iso4217:USD" + }, + "amount_in": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "amount_out": { + "amount": "98.0000000", + "asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, + "amount_fee": { + "amount": "2.0000", + "asset": "iso4217:USD" + }, + "quote_id": "quote-id", + "started_at": "2022-12-19T02:06:44.500182800Z", + "updated_at": "2022-12-19T02:07:44.500182800Z", + "completed_at": "2022-12-19T02:09:44.500182800Z", + "transfer_received_at": "2022-12-19T02:08:44.500182800Z", + "message": "Please don\u0027t forget to foo bar", + "refunds": { + "amount_refunded": { + "amount": "90.0000", + "asset": "iso4217:USD" + }, + "amount_fee": { + "amount": "8.0000", + "asset": "iso4217:USD" + }, + "payments": [ + { + "id": "1111", + "id_type": "stellar", + "amount": { + "amount": "50.0000", + "asset": "iso4217:USD" + }, + "fee": { + "amount": "4.0000", + "asset": "iso4217:USD" + } + }, + { + "id": "2222", + "id_type": "stellar", + "amount": { + "amount": "40.0000", + "asset": "iso4217:USD" + }, + "fee": { + "amount": "4.0000", + "asset": "iso4217:USD" + } + } + ] + }, + "stellar_transactions": [ + { + "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "memo": "my-memo", + "memo_type": "text", + "created_at": "2022-12-19T02:08:44.500182800Z", + "envelope": "here_comes_the_envelope", + "payments": [ + { + "id": "4609238642995201", + "amount": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "payment_type": "payment", + "source_account": "GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ", + "destination_account": "GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7" + } + ] + } + ], + "external_transaction_id": "external-tx-id", + "customers": { + "sender": { + "id": "6c1770b0-0ea4-11ed-861d-0242ac120002" + }, + "receiver": { + "id": "31212353-f265-4dba-9eb4-0bbeda3ba7f2" + } + }, + "creator": { + "id": "141ee445-f32c-4c38-9d25-f4475d6c5558" + } + } + + """ + .trimIndent() } From c72417f94e46aef66f1f6d2ad1a780f249dd0dc8 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Sat, 24 Dec 2022 08:05:24 +0800 Subject: [PATCH 0067/1439] [ANCHOR-91]: Implement sep24 patch transaction (#688) * Sep-24 patch transaction * renamed Refunds to Sep31Refunds * Renamed Refund to Refunds and moved some functions * Fix refund payments serialization errors * Add mulsoft.org to maven repositories * Refactored sep31 integration tests * Publish event when creating sep24 transaction and when patching sep transactions --- api-schema/build.gradle.kts | 28 +- .../api/platform/GetTransactionResponse.java | 2 +- .../api/platform/PatchTransactionRequest.java | 4 +- .../api/sep/sep24/GetTransactionResponse.java | 14 - .../sep24/Sep24GetTransactionResponse.java | 14 + .../api/sep/sep24/TransactionResponse.java | 4 - .../api/shared/{Refund.java => Refunds.java} | 4 +- build.gradle.kts | 3 +- .../anchor/event/models/TransactionEvent.java | 26 +- .../org/stellar/anchor/sep24/Sep24Helper.java | 19 + .../anchor/sep24/Sep24RefundPayment.java | 15 + .../stellar/anchor/sep24/Sep24Refunds.java | 22 ++ .../stellar/anchor/sep24/Sep24Service.java | 27 +- .../anchor/sep24/Sep24TransactionStore.java | 4 + .../stellar/anchor/sep31/RefundPayment.java | 18 - .../org/stellar/anchor/sep31/Refunds.java | 87 ----- .../stellar/anchor/sep31/RefundsBuilder.java | 14 +- .../org/stellar/anchor/sep31/Sep31Helper.java | 22 ++ .../stellar/anchor/sep31/Sep31Refunds.java | 67 ++++ .../stellar/anchor/sep31/Sep31Service.java | 36 +- .../anchor/sep31/Sep31Transaction.java | 4 +- .../anchor/sep31/Sep31TransactionBuilder.java | 4 +- .../anchor/sep31/Sep31TransactionStore.java | 2 +- .../anchor/sep31/PojoSep31Refunds.java | 2 +- .../anchor/sep31/PojoSep31Transaction.java | 10 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 5 +- .../stellar/anchor/sep31/RefundPaymentTest.kt | 24 -- ...lderTest.kt => Sep31RefundsBuilderTest.kt} | 6 +- .../{RefundsTest.kt => Sep31RefundsTest.kt} | 66 +--- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 30 -- .../stellar/anchor/platform/Sep24Client.kt | 6 +- .../anchor/platform/PlatformApiTests.kt | 189 +--------- .../org/stellar/anchor/platform/Sep24Tests.kt | 234 ++++++++++-- .../org/stellar/anchor/platform/Sep31Tests.kt | 338 ++++++++++++++++-- .../stellar/anchor/platform/SepTestSuite.kt | 8 - .../org/stellar/anchor/platform/test.json | 74 ++++ .../anchor/platform/SepServiceBeans.java | 5 +- .../controller/PlatformController.java | 2 +- .../platform/controller/Sep24Controller.java | 13 +- .../platform/data/JdbcSep24RefundPayment.java | 2 + .../platform/data/JdbcSep24Refunds.java | 25 +- .../platform/data/JdbcSep24Transaction.java | 68 +--- .../data/JdbcSep24TransactionStore.java | 12 + .../platform/data/JdbcSep31Refunds.java | 19 +- .../platform/data/JdbcSep31Transaction.java | 56 +-- .../data/JdbcSep31TransactionStore.java | 4 +- .../platform/data/JdbcSepTransaction.java | 60 ++++ .../platform/service/AnchorMetrics.java | 2 + .../platform/service/TransactionService.java | 316 +++++++++------- .../resources/db/migration/V1__master.sql | 2 +- .../service/TransactionServiceTest.kt | 46 +-- 51 files changed, 1201 insertions(+), 863 deletions(-) delete mode 100644 api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/GetTransactionResponse.java create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetTransactionResponse.java rename api-schema/src/main/java/org/stellar/anchor/api/shared/{Refund.java => Refunds.java} (91%) delete mode 100644 core/src/main/java/org/stellar/anchor/sep31/Refunds.java create mode 100644 core/src/main/java/org/stellar/anchor/sep31/Sep31Refunds.java rename core/src/test/kotlin/org/stellar/anchor/sep31/{RefundsBuilderTest.kt => Sep31RefundsBuilderTest.kt} (92%) rename core/src/test/kotlin/org/stellar/anchor/sep31/{RefundsTest.kt => Sep31RefundsTest.kt} (66%) create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json create mode 100644 platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java diff --git a/api-schema/build.gradle.kts b/api-schema/build.gradle.kts index 6dfedbee0f..d0b2088447 100644 --- a/api-schema/build.gradle.kts +++ b/api-schema/build.gradle.kts @@ -1,25 +1,25 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") plugins { - `java-library` - alias(libs.plugins.kotlin.jvm) + `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()) } + processResources { + doFirst { + val existingFile = file("$buildDir/resources/main/metadata.properties") + existingFile.delete() } + filter { line -> line.replace("%APP_VERSION_TOKEN%", rootProject.version.toString()) } + } } dependencies { - api(libs.lombok) + api(libs.lombok) - implementation(libs.google.gson) - implementation(libs.reactor.core) + implementation(libs.google.gson) + implementation(libs.reactor.core) - annotationProcessor(libs.lombok) + annotationProcessor(libs.lombok) } 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 1418d468c7..a6899a97b5 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 @@ -45,7 +45,7 @@ public class GetTransactionResponse { Instant transferReceivedAt; String message; - Refund refunds; + Refunds refunds; @SerializedName("stellar_transactions") List stellarTransactions; 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 7c0a8b5c0e..1d57f96478 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,7 +5,7 @@ import lombok.Builder; import lombok.Data; import org.stellar.anchor.api.shared.Amount; -import org.stellar.anchor.api.shared.Refund; +import org.stellar.anchor.api.shared.Refunds; @Data @Builder @@ -26,7 +26,7 @@ public class PatchTransactionRequest { Instant transferReceivedAt; String message; - Refund refunds; + Refunds refunds; @SerializedName("external_transaction_id") String externalTransactionId; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/GetTransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/GetTransactionResponse.java deleted file mode 100644 index c7fdf03a3b..0000000000 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/GetTransactionResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.stellar.anchor.api.sep.sep24; - -import lombok.Data; - -@Data -public class GetTransactionResponse { - TransactionResponse transaction; - - public static GetTransactionResponse of(TransactionResponse tr) { - GetTransactionResponse gtr = new GetTransactionResponse(); - gtr.transaction = tr; - return gtr; - } -} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetTransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetTransactionResponse.java new file mode 100644 index 0000000000..71dc97f642 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetTransactionResponse.java @@ -0,0 +1,14 @@ +package org.stellar.anchor.api.sep.sep24; + +import lombok.Data; + +@Data +public class Sep24GetTransactionResponse { + TransactionResponse transaction; + + public static Sep24GetTransactionResponse of(TransactionResponse tr) { + Sep24GetTransactionResponse gtr = new Sep24GetTransactionResponse(); + gtr.transaction = tr; + return gtr; + } +} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index 22dc95bb0f..1c31f4b44e 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -49,12 +49,8 @@ public class TransactionResponse { String externalTransactionId; String message; - - @Deprecated // Deprecated in favor of refunds Boolean refunded = false; Refunds refunds; - String from = ""; - String to = ""; } diff --git a/api-schema/src/main/java/org/stellar/anchor/api/shared/Refund.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/Refunds.java similarity index 91% rename from api-schema/src/main/java/org/stellar/anchor/api/shared/Refund.java rename to api-schema/src/main/java/org/stellar/anchor/api/shared/Refunds.java index 99dc8fb8e6..d8db67a1f2 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/shared/Refund.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/Refunds.java @@ -9,7 +9,7 @@ @Data @Builder @AllArgsConstructor -public class Refund { +public class Refunds { @JsonProperty("amount_refunded") @SerializedName("amount_refunded") @@ -21,5 +21,5 @@ public class Refund { RefundPayment[] payments; - public Refund() {} + public Refunds() {} } diff --git a/build.gradle.kts b/build.gradle.kts index 00e23a5500..6b8773d61b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,8 +24,9 @@ subprojects { repositories { mavenLocal() mavenCentral() - maven { url = uri("https://jitpack.io") } maven { url = uri("https://packages.confluent.io/maven") } + maven { url = uri("https://repository.mulesoft.org/nexus/content/repositories/public/") } + maven { url = uri("https://jitpack.io") } } /** Specifies JDK-11 */ 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 d7bd47459c..2a03308770 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,15 +9,13 @@ import lombok.AllArgsConstructor; 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; -import org.stellar.anchor.api.shared.StellarTransaction; +import lombok.NoArgsConstructor; +import org.stellar.anchor.api.shared.*; @Data @Builder @AllArgsConstructor +@NoArgsConstructor public class TransactionEvent implements AnchorEvent { @JsonProperty("event_id") @SerializedName("event_id") @@ -79,7 +77,7 @@ public String getType() { String message; - Refund refunds; + Refunds refunds; @JsonProperty("stellar_transactions") @SerializedName("stellar_transactions") @@ -106,6 +104,7 @@ public String getType() { StellarId creator; public enum Status { + INCOMPLETE("incomplete"), PENDING_SENDER("pending_sender"), PENDING_STELLAR("pending_stellar"), PENDING_CUSTOMER_INFO_UPDATE("pending_customer_info_update"), @@ -178,7 +177,9 @@ public enum Type { public enum Kind { @SuppressWarnings("unused") UNDEFINED("undefined"), - RECEIVE("receive"); + RECEIVE("receive"), + DEPOSIT("deposit"), + WITHDRAW("withdrawal"); public final String kind; @@ -190,7 +191,14 @@ public enum Kind { public String getKind() { return kind; } - } - public TransactionEvent() {} + public static Kind from(String str) { + for (Kind kind : values()) { + if (kind.kind.equals(str)) { + return kind; + } + } + throw new IllegalArgumentException("No matching constant for [" + str + "]"); + } + } } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java index c7e444f52c..8c6190ac71 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java @@ -13,13 +13,17 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.UUID; import org.apache.http.client.utils.URIBuilder; import org.springframework.beans.BeanUtils; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep24.*; import org.stellar.anchor.auth.JwtService; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.Sep24Config; +import org.stellar.anchor.event.EventService; +import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.util.GsonUtils; public class Sep24Helper { @@ -169,4 +173,19 @@ public static TransactionResponse updateRefundInfo( return response; } + + public static void publishEvent( + EventService eventService, Sep24Transaction txn, TransactionEvent.Type eventType) + throws EventPublishException { + TransactionEvent event = + TransactionEvent.builder() + .eventId(UUID.randomUUID().toString()) + .type(eventType) + .id(txn.getId()) + .sep(TransactionEvent.Sep.SEP_24) + .kind(TransactionEvent.Kind.from(txn.getKind())) + .status(TransactionEvent.Status.from(txn.getStatus())) + .build(); + eventService.publish(event); + } } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java index eacee4fb6f..4ec6fa36f2 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24RefundPayment.java @@ -28,4 +28,19 @@ public interface Sep24RefundPayment { String getFee(); void setFee(String fee); + + static Sep24RefundPayment of( + org.stellar.anchor.api.shared.RefundPayment platformApiRefundPayment, + Sep24TransactionStore factory) { + if (platformApiRefundPayment == null) { + return null; + } + + Sep24RefundPayment 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/sep24/Sep24Refunds.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java index 92508e1984..cf8d457595 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Refunds.java @@ -1,6 +1,10 @@ package org.stellar.anchor.sep24; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import org.stellar.anchor.api.shared.Refunds; @SuppressWarnings("unused") public interface Sep24Refunds { @@ -15,4 +19,22 @@ public interface Sep24Refunds { List getRefundPayments(); void setRefundPayments(List refundPayments); + + static Sep24Refunds of(Refunds platformApiRefunds, Sep24TransactionStore factory) { + if (platformApiRefunds == null) { + return null; + } + + Sep24Refunds refunds = factory.newRefunds(); + refunds.setAmountRefunded(platformApiRefunds.getAmountRefunded().getAmount()); + refunds.setAmountFee(platformApiRefunds.getAmountFee().getAmount()); + + ArrayList payments = + Arrays.stream(platformApiRefunds.getPayments()) + .map(refundPayment -> Sep24RefundPayment.of(refundPayment, factory)) + .collect(Collectors.toCollection(ArrayList::new)); + refunds.setRefundPayments(payments); + + return refunds; + } } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 0fcc981752..4781c9e6cd 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -1,6 +1,8 @@ package org.stellar.anchor.sep24; import static org.stellar.anchor.api.sep.SepTransactionStatus.INCOMPLETE; +import static org.stellar.anchor.event.models.TransactionEvent.Type.TRANSACTION_CREATED; +import static org.stellar.anchor.sep24.Sep24Helper.publishEvent; import static org.stellar.anchor.sep24.Sep24Transaction.Kind.DEPOSIT; import static org.stellar.anchor.sep24.Sep24Transaction.Kind.WITHDRAWAL; import static org.stellar.anchor.sep9.Sep9Fields.extractSep9Fields; @@ -12,7 +14,6 @@ import static org.stellar.anchor.util.SepHelper.memoTypeString; import static org.stellar.anchor.util.SepLanguageHelper.validateLanguage; -import com.google.gson.Gson; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; @@ -20,10 +21,7 @@ import java.time.Instant; import java.util.*; import org.apache.http.client.utils.URIBuilder; -import org.stellar.anchor.api.exception.SepException; -import org.stellar.anchor.api.exception.SepNotAuthorizedException; -import org.stellar.anchor.api.exception.SepNotFoundException; -import org.stellar.anchor.api.exception.SepValidationException; +import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep24.*; import org.stellar.anchor.asset.AssetService; @@ -31,6 +29,7 @@ import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.Sep24Config; +import org.stellar.anchor.event.EventService; import org.stellar.sdk.KeyPair; import org.stellar.sdk.Memo; @@ -40,14 +39,15 @@ public class Sep24Service { final AssetService assetService; final JwtService jwtService; final Sep24TransactionStore txnStore; + final EventService eventService; public Sep24Service( - Gson gson, AppConfig appConfig, Sep24Config sep24Config, AssetService assetService, JwtService jwtService, - Sep24TransactionStore txnStore) { + Sep24TransactionStore txnStore, + EventService eventService) { debug("appConfig:", appConfig); debug("sep24Config:", sep24Config); this.appConfig = appConfig; @@ -55,12 +55,13 @@ public Sep24Service( this.assetService = assetService; this.jwtService = jwtService; this.txnStore = txnStore; + this.eventService = eventService; info("Sep24Service initialized."); } public InteractiveTransactionResponse withdraw( String fullRequestUrl, JwtToken token, Map withdrawRequest) - throws SepException, MalformedURLException, URISyntaxException { + throws SepException, MalformedURLException, URISyntaxException, EventPublishException { info("Creating withdrawal transaction."); if (token == null) { info("missing SEP-10 token"); @@ -141,8 +142,8 @@ public InteractiveTransactionResponse withdraw( } Sep24Transaction txn = builder.build(); - txnStore.save(txn); + publishEvent(eventService, txn, TRANSACTION_CREATED); infoF( "Saved withdraw transaction. from={}, amountIn={}, amountOut={}.", @@ -165,7 +166,7 @@ public InteractiveTransactionResponse withdraw( public InteractiveTransactionResponse deposit( String fullRequestUrl, JwtToken token, Map depositRequest) - throws SepException, MalformedURLException, URISyntaxException { + throws SepException, MalformedURLException, URISyntaxException, EventPublishException { info("Creating deposit transaction."); if (token == null) { info("missing SEP-10 token"); @@ -262,6 +263,8 @@ public InteractiveTransactionResponse deposit( Sep24Transaction txn = builder.build(); txnStore.save(txn); + publishEvent(eventService, txn, TRANSACTION_CREATED); + infoF( "Saved deposit transaction. to={}, amountIn={}, amountOut={}.", shorter(txn.getToAccount()), @@ -310,7 +313,7 @@ public GetTransactionsResponse findTransactions(JwtToken token, GetTransactionsR return result; } - public GetTransactionResponse findTransaction(JwtToken token, GetTransactionRequest txReq) + public Sep24GetTransactionResponse findTransaction(JwtToken token, GetTransactionRequest txReq) throws SepException, IOException, URISyntaxException { if (token == null) { info("missing SEP-10 token"); @@ -355,7 +358,7 @@ public GetTransactionResponse findTransaction(JwtToken token, GetTransactionRequ throw new SepNotFoundException("transaction not found"); } - return GetTransactionResponse.of(fromTxn(txn, txReq.getLang())); + return Sep24GetTransactionResponse.of(fromTxn(txn, txReq.getLang())); } public InfoResponse getInfo() { diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java index db1f867a49..54876e19e6 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24TransactionStore.java @@ -9,6 +9,10 @@ public interface Sep24TransactionStore { Sep24Transaction newInstance(); + Sep24Refunds newRefunds(); + + Sep24RefundPayment newRefundPayment(); + /** * Find the Sep24Transaction by transaction_id * 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 1b60503834..cea35b491f 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java +++ b/core/src/main/java/org/stellar/anchor/sep31/RefundPayment.java @@ -1,7 +1,6 @@ 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(); @@ -30,23 +29,6 @@ default Sep31GetTransactionResponse.Sep31RefundPayment toSep31RefundPayment() { .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. * diff --git a/core/src/main/java/org/stellar/anchor/sep31/Refunds.java b/core/src/main/java/org/stellar/anchor/sep31/Refunds.java deleted file mode 100644 index e83edc812e..0000000000 --- a/core/src/main/java/org/stellar/anchor/sep31/Refunds.java +++ /dev/null @@ -1,87 +0,0 @@ -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 { - - String getAmountRefunded(); - - void setAmountRefunded(String amountRefunded); - - String getAmountFee(); - - void setAmountFee(String amountFee); - - 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/RefundsBuilder.java b/core/src/main/java/org/stellar/anchor/sep31/RefundsBuilder.java index 2980a238b7..fec5637522 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 { - private final Refunds refunds; + private final Sep31Refunds sep31Refunds; public RefundsBuilder(Sep31TransactionStore factory) { - refunds = factory.newRefunds(); + sep31Refunds = factory.newRefunds(); } public RefundsBuilder amountRefunded(String amountRefunded) { - refunds.setAmountRefunded(amountRefunded); + sep31Refunds.setAmountRefunded(amountRefunded); return this; } public RefundsBuilder amountFee(String amountFee) { - refunds.setAmountFee(amountFee); + sep31Refunds.setAmountFee(amountFee); return this; } public RefundsBuilder payments(List payments) { - refunds.setRefundPayments(payments); + sep31Refunds.setRefundPayments(payments); return this; } - public Refunds build() { - return refunds; + public Sep31Refunds build() { + return sep31Refunds; } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java index 7bf35895d3..7ec6d0b821 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Helper.java @@ -2,7 +2,12 @@ import static org.stellar.anchor.util.SepHelper.validateTransactionStatus; +import java.util.UUID; import org.stellar.anchor.api.exception.BadRequestException; +import org.stellar.anchor.api.exception.EventPublishException; +import org.stellar.anchor.api.shared.StellarId; +import org.stellar.anchor.event.EventService; +import org.stellar.anchor.event.models.TransactionEvent; public class Sep31Helper { public static boolean allAmountAvailable(Sep31Transaction txn) { @@ -20,4 +25,21 @@ public static void validateStatus(Sep31Transaction txn) throws BadRequestExcepti String.format("'%s' is not a valid status of SEP31.", txn.getStatus())); } } + + public static void publishEvent( + EventService eventService, Sep31Transaction txn, TransactionEvent.Type eventType) + throws EventPublishException { + StellarId senderStellarId = StellarId.builder().id(txn.getSenderId()).build(); + StellarId receiverStellarId = StellarId.builder().id(txn.getReceiverId()).build(); + TransactionEvent event = + TransactionEvent.builder() + .eventId(UUID.randomUUID().toString()) + .type(eventType) + .id(txn.getId()) + .sep(TransactionEvent.Sep.SEP_31) + .kind(TransactionEvent.Kind.RECEIVE) + .status(TransactionEvent.Status.from(txn.getStatus())) + .build(); + eventService.publish(event); + } } diff --git a/core/src/main/java/org/stellar/anchor/sep31/Sep31Refunds.java b/core/src/main/java/org/stellar/anchor/sep31/Sep31Refunds.java new file mode 100644 index 0000000000..ea80bdf465 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Refunds.java @@ -0,0 +1,67 @@ +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.Refunds; + +public interface Sep31Refunds { + + String getAmountRefunded(); + + void setAmountRefunded(String amountRefunded); + + String getAmountFee(); + + void setAmountFee(String amountFee); + + List getRefundPayments(); + + void setRefundPayments(List refundPayments); + + /** + * Will create a Sep31GetTransactionResponse.Sep31Refunds object out of this SEP-31 Sep31Refunds + * object. + * + * @return a Sep31GetTransactionResponse.Sep31Refunds 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 SEP-31 Sep31Refunds object out of a PlatformApi Refunds object. + * + * @param platformApiRefunds is the platformApi's Refunds object. + * @param factory is a Sep31TransactionStore instance used to build the object. + * @return a SEP-31 Sep31Refunds object. + */ + static Sep31Refunds of(Refunds platformApiRefunds, Sep31TransactionStore factory) { + if (platformApiRefunds == null) { + return null; + } + + Sep31Refunds sep31Refunds = factory.newRefunds(); + sep31Refunds.setAmountRefunded(platformApiRefunds.getAmountRefunded().getAmount()); + sep31Refunds.setAmountFee(platformApiRefunds.getAmountFee().getAmount()); + + ArrayList payments = + Arrays.stream(platformApiRefunds.getPayments()) + .map(refundPayment -> RefundPayment.of(refundPayment, factory)) + .collect(Collectors.toCollection(ArrayList::new)); + sep31Refunds.setRefundPayments(payments); + + return sep31Refunds; + } +} 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 5e863d50d9..b2ce45a222 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Service.java @@ -2,6 +2,7 @@ import static org.stellar.anchor.api.sep.sep31.Sep31InfoResponse.AssetResponse; import static org.stellar.anchor.config.Sep31Config.PaymentType.STRICT_SEND; +import static org.stellar.anchor.event.models.TransactionEvent.Type.TRANSACTION_CREATED; import static org.stellar.anchor.util.Log.*; import static org.stellar.anchor.util.MathHelper.decimal; import static org.stellar.anchor.util.MathHelper.formatAmount; @@ -30,14 +31,12 @@ import org.stellar.anchor.api.sep.sep12.Sep12Status; import org.stellar.anchor.api.sep.sep31.*; 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.EventService; -import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.sep38.Sep38Quote; import org.stellar.anchor.sep38.Sep38QuoteStore; import org.stellar.anchor.util.Log; @@ -183,38 +182,7 @@ public Sep31PostTransactionResponse postTransaction( updateDepositInfo(); - StellarId senderStellarId = StellarId.builder().id(txn.getSenderId()).build(); - StellarId receiverStellarId = StellarId.builder().id(txn.getReceiverId()).build(); - TransactionEvent event = - TransactionEvent.builder() - .eventId(UUID.randomUUID().toString()) - .type(TransactionEvent.Type.TRANSACTION_CREATED) - .id(txn.getId()) - .sep(TransactionEvent.Sep.SEP_31) - .kind(TransactionEvent.Kind.RECEIVE) - .status(TransactionEvent.Status.PENDING_SENDER) - .statusChange( - new TransactionEvent.StatusChange(null, TransactionEvent.Status.PENDING_SENDER)) - .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.getStartedAt()) - .completedAt(null) - .transferReceivedAt(null) - .message(null) - .refunds(null) - .stellarTransactions(null) - .externalTransactionId(null) - .custodialTransactionId(null) - .sourceAccount(null) - .destinationAccount(null) - .customers(new Customers(senderStellarId, receiverStellarId)) - .creator(creatorStellarId) - .build(); - eventService.publish(event); + Sep31Helper.publishEvent(eventService, txn, TRANSACTION_CREATED); return Sep31PostTransactionResponse.builder() .id(txn.getId()) 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 3b466a0bdb..963168b087 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31Transaction.java @@ -93,9 +93,9 @@ public interface Sep31Transaction { void setRefunded(Boolean refunded); - Refunds getRefunds(); + Sep31Refunds getRefunds(); - void setRefunds(Refunds refunds); + void setRefunds(Sep31Refunds sep31Refunds); String getRequiredInfoMessage(); 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 0dcc4f4dd6..78aed5100b 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionBuilder.java @@ -119,8 +119,8 @@ public Sep31TransactionBuilder refunded(Boolean refunded) { return this; } - public Sep31TransactionBuilder refunds(Refunds refunds) { - txn.setRefunds(refunds); + public Sep31TransactionBuilder refunds(Sep31Refunds sep31Refunds) { + txn.setRefunds(sep31Refunds); 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 8543bf4d1c..07470e7495 100644 --- a/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java +++ b/core/src/main/java/org/stellar/anchor/sep31/Sep31TransactionStore.java @@ -10,7 +10,7 @@ public interface Sep31TransactionStore { Sep31Transaction newTransaction(); - Refunds newRefunds(); + Sep31Refunds newRefunds(); RefundPayment newRefundPayment(); 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 8fa77e06bd..23e315522d 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Refunds.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Refunds.java @@ -5,7 +5,7 @@ import lombok.Data; @Data -public class PojoSep31Refunds implements Refunds { +public class PojoSep31Refunds implements Sep31Refunds { String amountRefunded; String amountFee; List 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 76b7afc16e..108210027e 100644 --- a/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java +++ b/core/src/test/java/org/stellar/anchor/sep31/PojoSep31Transaction.java @@ -42,16 +42,16 @@ public class PojoSep31Transaction implements Sep31Transaction { StellarId creator; @Override - public void setRefunds(Refunds refunds) { - if (refunds == null) { + public void setRefunds(Sep31Refunds sep31Refunds) { + if (sep31Refunds == null) { this.refunds = null; return; } PojoSep31Refunds newRefunds = new PojoSep31Refunds(); - newRefunds.setAmountRefunded(refunds.getAmountRefunded()); - newRefunds.setAmountFee(refunds.getAmountFee()); - newRefunds.setRefundPayments(refunds.getRefundPayments()); + newRefunds.setAmountRefunded(sep31Refunds.getAmountRefunded()); + newRefunds.setAmountFee(sep31Refunds.getAmountFee()); + newRefunds.setRefundPayments(sep31Refunds.getRefundPayments()); this.refunds = newRefunds; } } 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 16dba7905d..ef23d1f6cb 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -37,6 +37,7 @@ 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.event.EventService import org.stellar.anchor.util.GsonUtils import org.stellar.anchor.util.MemoHelper.makeMemo import org.stellar.sdk.MemoHash @@ -53,6 +54,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 + @MockK(relaxed = true) lateinit var eventService: EventService @MockK(relaxed = true) private lateinit var txnStore: Sep24TransactionStore private val assetService: AssetService = ResourceJsonAssetService("test_assets.json") @@ -76,7 +78,8 @@ internal class Sep24ServiceTest { jwtService = spyk(JwtService(secretConfig)) - sep24Service = Sep24Service(gson, appConfig, sep24Config, assetService, jwtService, txnStore) + sep24Service = + Sep24Service(appConfig, sep24Config, assetService, jwtService, txnStore, eventService) } @AfterEach 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 4f2dda27d7..70ff428013 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/RefundPaymentTest.kt @@ -52,30 +52,6 @@ class RefundPaymentTest { assertEquals(wantSep31RefundPayment, gotSep31RefundPayment) } - @Test - fun `test to platform api refund payment`() { - // 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 PlatformApi Refund object creation`() { // mock the PlatformApi RefundPayment object diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31RefundsBuilderTest.kt similarity index 92% rename from core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt rename to core/src/test/kotlin/org/stellar/anchor/sep31/Sep31RefundsBuilderTest.kt index c6504550de..0c7d1a30b1 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsBuilderTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31RefundsBuilderTest.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -class RefundsBuilderTest { +class Sep31RefundsBuilderTest { @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore @BeforeEach @@ -40,13 +40,13 @@ class RefundsBuilderTest { refundPayment2.fee = "1" val refundPaymentList = listOf(refundPayment1, refundPayment2) - // mock the Refunds object we want + // mock the Sep31Refunds object we want val wantRefunds = PojoSep31Refunds() wantRefunds.amountRefunded = "10" wantRefunds.amountFee = "2" wantRefunds.refundPayments = refundPaymentList - // build the Refunds object. + // build the Sep31Refunds object. val gotRefunds = RefundsBuilder(sep31TransactionStore) .amountRefunded("10") diff --git a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31RefundsTest.kt similarity index 66% rename from core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt rename to core/src/test/kotlin/org/stellar/anchor/sep31/Sep31RefundsTest.kt index 92aefaa817..27883f5eec 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/RefundsTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31RefundsTest.kt @@ -15,8 +15,9 @@ 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 +import org.stellar.anchor.api.shared.Refunds -class RefundsTest { +class Sep31RefundsTest { companion object { private const val fiatUSD = "iso4217:USD" private const val stellarUSDC = @@ -40,7 +41,7 @@ class RefundsTest { @Test fun `test conversion to sep31 transaction response refunds`() { - // mock the SEP-31 Refunds object + // mock the SEP-31 Sep31Refunds object val mockRefundPayment1 = PojoSep31RefundPayment() mockRefundPayment1.id = "A" mockRefundPayment1.amount = "50" @@ -81,60 +82,11 @@ class RefundsTest { assertEquals(wantSep31GetTransactionResponseRefunds, gotSep31GetTransactionResponseRefunds) } - @Test - fun `test conversion to CallbackApi Refund`() { - // 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 CallbackApi Refund creation`() { - // mock the CallbackApi Refund - val mockPlatformApiRefund = - org.stellar.anchor.api.shared.Refund.builder() + // mock the CallbackApi Refunds + val mockPlatformApiRefunds = + Refunds.builder() .amountRefunded(Amount("100", fiatUSD)) .amountFee(Amount("8", stellarUSDC)) .payments( @@ -159,7 +111,7 @@ class RefundsTest { ) .build() - // mock the SEP-31 Refunds object we want + // mock the SEP-31 Sep31Refunds object we want val wantRefundPayment1 = PojoSep31RefundPayment() wantRefundPayment1.id = "A" wantRefundPayment1.amount = "50" @@ -174,8 +126,8 @@ class RefundsTest { wantSep31Refunds.amountFee = "8" wantSep31Refunds.refundPayments = wantRefundPaymentList - // build the SEP-31 Refunds object - val gotSep31Refunds = Refunds.of(mockPlatformApiRefund, sep31TransactionStore) + // build the SEP-31 Sep31Refunds object + val gotSep31Refunds = Sep31Refunds.of(mockPlatformApiRefunds, sep31TransactionStore) assertEquals(wantSep31Refunds, gotSep31Refunds) } } 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 b1cd867604..72bdcf3577 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -30,8 +30,6 @@ 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 @@ -825,34 +823,6 @@ class Sep31ServiceTest { .sep(TransactionEvent.Sep.SEP_31) .kind(TransactionEvent.Kind.RECEIVE) .status(TransactionEvent.Status.PENDING_SENDER) - .statusChange(TransactionEvent.StatusChange(null, TransactionEvent.Status.PENDING_SENDER)) - .amountExpected(Amount("100", stellarUSDC)) - .amountIn(Amount("100", stellarUSDC)) - .amountOut(Amount("12500", stellarJPYC)) - .amountFee(Amount("10", stellarUSDC)) - .quoteId("my_quote_id") - .startedAt(txStartedAt) - .updatedAt(txStartedAt) - .completedAt(null) - .transferReceivedAt(null) - .message(null) - .refunds(null) - .stellarTransactions(null) - .externalTransactionId(null) - .custodialTransactionId(null) - .sourceAccount(null) - .destinationAccount(null) - .customers( - Customers( - StellarId.builder().id(senderId).build(), - StellarId.builder().id(receiverId).build() - ) - ) - .creator( - StellarId.builder() - .account("GBJDSMTMG4YBP27ZILV665XBISBBNRP62YB7WZA2IQX2HIPK7ABLF4C2") - .build(), - ) .build() assertEquals(wantEvent, txEventSlot.captured) diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt index e5fa4aa4df..bd5a1bbc9b 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt @@ -1,8 +1,8 @@ package org.stellar.anchor.platform -import org.stellar.anchor.api.sep.sep24.GetTransactionResponse import org.stellar.anchor.api.sep.sep24.InfoResponse import org.stellar.anchor.api.sep.sep24.InteractiveTransactionResponse +import org.stellar.anchor.api.sep.sep24.Sep24GetTransactionResponse class Sep24Client(private val endpoint: String, private val jwt: String) : SepClient() { @@ -26,9 +26,9 @@ class Sep24Client(private val endpoint: String, private val jwt: String) : SepCl return gson.fromJson(responseBody, InteractiveTransactionResponse::class.java) } - fun getTransaction(id: String, assetCode: String): GetTransactionResponse { + fun getTransaction(id: String, assetCode: String): Sep24GetTransactionResponse { println("SEP24 $endpoint/transactions") val responseBody = httpGet("$endpoint/transaction?id=$id&asset_code=$assetCode", jwt) - return gson.fromJson(responseBody, GetTransactionResponse::class.java) + return gson.fromJson(responseBody, Sep24GetTransactionResponse::class.java) } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt index 367321baab..abc0c88076 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt @@ -1,18 +1,10 @@ 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 -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.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull import org.stellar.anchor.auth.AuthHelper import org.stellar.anchor.auth.JwtService -import org.stellar.anchor.event.models.TransactionEvent import org.stellar.anchor.reference.client.PlatformApiClient -import org.stellar.anchor.util.GsonUtils lateinit var platformApiClient: PlatformApiClient @@ -27,63 +19,6 @@ class PlatformApiTests { } } - fun `test sep24 doposit, withdraw, get and patch`() { - // TODO: test with deposit/withdraw then patch then get - } - - fun `test sep31 post, get and patch`() { - // 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 = - sep38Client.postQuote( - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "10", - "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - ) - - // 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) - assertEquals(txnRequest.amount, getTxResponse.amountIn.amount) - 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) - .status(TransactionEvent.Status.COMPLETED.status) - .amountOut(Amount(quote.buyAmount, quote.buyAsset)) - .build() - val patchTxResponse = - platformApiClient.patchTransaction( - PatchTransactionsRequest.builder().records(listOf(patchTxRequest)).build() - ) - assertEquals(1, patchTxResponse.records.size) - val patchedTx = patchTxResponse.records[0] - assertEquals(getTxResponse.id, patchedTx.id) - assertEquals(TransactionEvent.Status.COMPLETED.status, patchedTx.status) - assertEquals(quote.buyAmount, patchedTx.amountOut.amount) - assertEquals(quote.buyAsset, patchedTx.amountOut.asset) - assertEquals(31, getTxResponse.sep) - } - - @Suppress("UNCHECKED_CAST") fun testHealth() { val response = platformApiClient.health(listOf("all")) assertEquals(5, response.size) @@ -94,130 +29,12 @@ class PlatformApiTests { assertNotNull(response["number_of_checks"]) assertNotNull(response["version"]) } - - 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 = - sep38Client.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) - 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) - 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) - assertTrue(patchedTx.updatedAt > patchedTx.startedAt) - - // 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.truncatedTo(SECONDS), - gotSep31TxResponse.transaction.completedAt.truncatedTo(SECONDS) - ) - } } } fun platformTestAll() { PlatformApiTests.setup() - println("Performing Platform API tests...") - PlatformApiTests.`test sep31 post, get and patch`() + PlatformApiTests.testHealth() - PlatformApiTests.testSep31UnhappyPath() } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt index 5e3e42bf25..6ccaeb94fc 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt @@ -8,14 +8,14 @@ import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode import org.skyscreamer.jsonassert.JSONCompareMode.LENIENT import org.stellar.anchor.api.exception.SepException -import org.stellar.anchor.api.sep.sep24.GetTransactionResponse +import org.stellar.anchor.api.platform.PatchTransactionsRequest +import org.stellar.anchor.api.sep.sep24.Sep24GetTransactionResponse import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.jwt import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml lateinit var sep24Client: Sep24Client - -lateinit var savedWithdrawTransaction: GetTransactionResponse -lateinit var savedDepositTransaction: GetTransactionResponse +lateinit var savedWithdrawTxn: Sep24GetTransactionResponse +lateinit var savedDepositTxn: Sep24GetTransactionResponse class Sep24Tests { companion object { @@ -33,13 +33,9 @@ class Sep24Tests { val withdrawRequest = gson.fromJson(withdrawRequest, HashMap::class.java) val txn = sep24Client.withdraw(withdrawRequest as HashMap) printResponse("POST /transactions/withdraw/interactive response:", txn) - savedWithdrawTransaction = sep24Client.getTransaction(txn.id, "USDC") - printResponse(savedWithdrawTransaction) - JSONAssert.assertEquals( - expectedSep24WithdrawResponse, - json(savedWithdrawTransaction), - LENIENT - ) + savedWithdrawTxn = sep24Client.getTransaction(txn.id, "USDC") + printResponse(savedWithdrawTxn) + JSONAssert.assertEquals(expectedSep24WithdrawResponse, json(savedWithdrawTxn), LENIENT) } fun `test Sep24 deposit`() { @@ -47,24 +43,49 @@ class Sep24Tests { val depositRequest = gson.fromJson(depositRequest, HashMap::class.java) val txn = sep24Client.deposit(depositRequest as HashMap) printResponse("POST /transactions/deposit/interactive response:", txn) - savedDepositTransaction = sep24Client.getTransaction(txn.id, "USDC") - printResponse(savedDepositTransaction) - JSONAssert.assertEquals(expectedSep24DepositResponse, json(savedDepositTransaction), LENIENT) + savedDepositTxn = sep24Client.getTransaction(txn.id, "USDC") + printResponse(savedDepositTxn) + JSONAssert.assertEquals(expectedSep24DepositResponse, json(savedDepositTxn), LENIENT) } fun `test PlatformAPI GET transaction for deposit and withdrawal`() { - val actualWithdrawTxn = - platformApiClient.getTransaction(savedWithdrawTransaction.transaction.id) - assertEquals(actualWithdrawTxn.id, savedWithdrawTransaction.transaction.id) + val actualWithdrawTxn = platformApiClient.getTransaction(savedWithdrawTxn.transaction.id) + assertEquals(actualWithdrawTxn.id, savedWithdrawTxn.transaction.id) JSONAssert.assertEquals(expectedWithdrawTransactionResponse, json(actualWithdrawTxn), LENIENT) - val actualDepositTxn = - platformApiClient.getTransaction(savedDepositTransaction.transaction.id) + val actualDepositTxn = platformApiClient.getTransaction(savedDepositTxn.transaction.id) printResponse(actualDepositTxn) - assertEquals(actualDepositTxn.id, savedDepositTransaction.transaction.id) + assertEquals(actualDepositTxn.id, savedDepositTxn.transaction.id) JSONAssert.assertEquals(expectedDepositTransactionResponse, json(actualDepositTxn), LENIENT) } + fun `test patch, get and compare`() { + val patch = + gson.fromJson(patchWithdrawTransactionRequest, PatchTransactionsRequest::class.java) + // create patch request and patch + patch.records[0].id = savedWithdrawTxn.transaction.id + patch.records[1].id = savedDepositTxn.transaction.id + platformApiClient.patchTransaction(patch) + + // check if the patched transactions are as expected + var afterPatchWithdraw = platformApiClient.getTransaction(savedWithdrawTxn.transaction.id) + assertEquals(afterPatchWithdraw.id, savedWithdrawTxn.transaction.id) + JSONAssert.assertEquals(expectedAfterPatchWithdraw, json(afterPatchWithdraw), LENIENT) + + var afterPatchDeposit = platformApiClient.getTransaction(savedDepositTxn.transaction.id) + assertEquals(afterPatchDeposit.id, savedDepositTxn.transaction.id) + JSONAssert.assertEquals(expectedAfterPatchDeposit, json(afterPatchDeposit), LENIENT) + + // Test patch idempotency + afterPatchWithdraw = platformApiClient.getTransaction(savedWithdrawTxn.transaction.id) + assertEquals(afterPatchWithdraw.id, savedWithdrawTxn.transaction.id) + JSONAssert.assertEquals(expectedAfterPatchWithdraw, json(afterPatchWithdraw), LENIENT) + + afterPatchDeposit = platformApiClient.getTransaction(savedDepositTxn.transaction.id) + assertEquals(afterPatchDeposit.id, savedDepositTxn.transaction.id) + JSONAssert.assertEquals(expectedAfterPatchDeposit, json(afterPatchDeposit), LENIENT) + } + fun `test GET transactions with bad ids`() { val badTxnIds = listOf("null", "bad id", "123", null) for (txnId in badTxnIds) { @@ -82,10 +103,11 @@ fun sep24TestAll() { Sep24Tests.`test Sep24 withdraw`() Sep24Tests.`test Sep24 deposit`() Sep24Tests.`test PlatformAPI GET transaction for deposit and withdrawal`() + Sep24Tests.`test patch, get and compare`() Sep24Tests.`test GET transactions with bad ids`() } -const val withdrawRequest = +private const val withdrawRequest = """{ "asset_code": "USDC", "asset_issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", @@ -93,7 +115,7 @@ const val withdrawRequest = "lang": "en" }""" -const val depositRequest = +private const val depositRequest = """{ "asset_code": "USDC", "asset_issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", @@ -101,7 +123,160 @@ const val depositRequest = "lang": "en" }""" -val expectedSep24Info = +private const val patchWithdrawTransactionRequest = + """ +{ + "records": [ + { + "id": "", + "status": "completed", + "amount_in": { + "amount": "10", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_out": { + "amount": "10", + "asset": "iso4217:USD" + }, + "amount_fee": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "message": "this is the message", + "refunds": { + "amount_refunded": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "payments": [ + { + "id": 1, + "amount": { + "amount": "0.6", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + }, + { + "id": 2, + "amount": { + "amount": "0.4", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + } + ] + } + }, + { + "id": "", + "status": "completed", + "amount_in": { + "amount": "100", + "asset": "iso4217:USD" + }, + "amount_out": { + "amount": "100", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "1", + "asset": "iso4217:USD" + }, + "message": "this is the message" + } + ] +} +""" + +private const val expectedAfterPatchWithdraw = + """ +{ + "sep": 24, + "kind": "withdrawal", + "status": "completed", + "amount_in": { + "amount": "10", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_out": { + "amount": "10", + "asset": "iso4217:USD" + }, + "amount_fee": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "refunds": { + "amount_refunded": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "payments": [ + { + "id": "1", + "id_type": "stellar", + "amount": { + "amount": "0.6", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + }, + { + "id": "2", + "id_type": "stellar", + "amount": { + "amount": "0.4", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + } + ] + } +}""" + +private const val expectedAfterPatchDeposit = + """ + { + "sep": 24, + "kind": "deposit", + "status": "completed", + "amount_in": { + "amount": "100", + "asset": "iso4217:USD" + }, + "amount_out": { + "amount": "100", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "1", + "asset": "iso4217:USD" + } + } +""" + +private const val expectedSep24Info = """ { "deposit": { @@ -162,9 +337,8 @@ val expectedSep24Info = } } """ - .trimIndent() -val expectedSep24WithdrawResponse = +private const val expectedSep24WithdrawResponse = """ { "transaction": { @@ -176,9 +350,8 @@ val expectedSep24WithdrawResponse = } } """ - .trimIndent() -val expectedSep24DepositResponse = +private const val expectedSep24DepositResponse = """ { "transaction": { @@ -190,9 +363,8 @@ val expectedSep24DepositResponse = } } """ - .trimIndent() -val expectedWithdrawTransactionResponse = +private const val expectedWithdrawTransactionResponse = """ { "sep": 24, @@ -200,9 +372,8 @@ val expectedWithdrawTransactionResponse = "status": "incomplete" } """ - .trimIndent() -val expectedDepositTransactionResponse = +private const val expectedDepositTransactionResponse = """ { "sep": 24, @@ -210,4 +381,3 @@ val expectedDepositTransactionResponse = "status": "incomplete" } """ - .trimIndent() 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 c610464b03..1a4874013c 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,11 +1,17 @@ package org.stellar.anchor.platform -import kotlin.test.assertEquals +import java.time.temporal.ChronoUnit.SECONDS +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.assertThrows import org.skyscreamer.jsonassert.JSONAssert import org.skyscreamer.jsonassert.JSONCompareMode +import org.skyscreamer.jsonassert.JSONCompareMode.LENIENT import org.stellar.anchor.api.exception.SepException +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.Sep31GetTransactionResponse import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest import org.stellar.anchor.event.models.TransactionEvent import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.jwt @@ -13,22 +19,7 @@ import org.stellar.anchor.platform.AnchorPlatformIntegrationTest.Companion.toml import org.stellar.anchor.util.GsonUtils lateinit var sep31Client: Sep31Client - -const val postTxnJson = - """{ - "amount": "10", - "asset_code": "USDC", - "asset_issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "receiver_id": "MOCK_RECEIVER_ID", - "sender_id": "MOCK_SENDER_ID", - "fields": { - "transaction": { - "receiver_routing_number": "r0123", - "receiver_account_number": "a0456", - "type": "SWIFT" - } - } -}""" +lateinit var savedTxn: Sep31GetTransactionResponse class Sep31Tests { companion object { @@ -37,13 +28,13 @@ class Sep31Tests { if (!::sep31Client.isInitialized) sep31Client = Sep31Client(toml.getString("DIRECT_PAYMENT_SERVER"), jwt) } - fun `test Sep31 info endpoint`() { + fun `test info endpoint`() { printRequest("Calling GET /info") val info = sep31Client.getInfo() - JSONAssert.assertEquals(gson.toJson(info), wantedSep31Info, JSONCompareMode.STRICT) + JSONAssert.assertEquals(gson.toJson(info), expectedSep31Info, JSONCompareMode.STRICT) } - fun testSep31PostAndGetTransaction() { + fun `test post and get transactions`() { // Create sender customer val senderCustomerRequest = GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) @@ -63,7 +54,7 @@ class Sep31Tests { ) // POST Sep31 transaction - val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) + val txnRequest = gson.fromJson(postTxnRequest, Sep31PostTransactionRequest::class.java) txnRequest.senderId = senderCustomer!!.id txnRequest.receiverId = receiverCustomer!!.id txnRequest.quoteId = quote.id @@ -74,12 +65,11 @@ class Sep31Tests { ) // 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) + savedTxn = sep31Client.getTransaction(postTxResponse.id) + JSONAssert.assertEquals(expectedTxn, json(savedTxn), LENIENT) + assertEquals(postTxResponse.id, savedTxn.transaction.id) + assertEquals(postTxResponse.stellarMemo, savedTxn.transaction.stellarMemo) + assertEquals(TransactionEvent.Status.PENDING_SENDER.status, savedTxn.transaction.status) } fun testBadAsset() { @@ -88,11 +78,144 @@ class Sep31Tests { val pr = sep12Client.putCustomer(customer) // Post Sep31 transaction. - val txnRequest = gson.fromJson(postTxnJson, Sep31PostTransactionRequest::class.java) + val txnRequest = gson.fromJson(postTxnRequest, Sep31PostTransactionRequest::class.java) txnRequest.assetCode = "bad-asset-code" txnRequest.receiverId = pr!!.id assertThrows { sep31Client.postTransaction(txnRequest) } } + + fun `test patch, get and compare`() { + val patch = gson.fromJson(patchRequest, PatchTransactionsRequest::class.java) + // create patch request and patch + patch.records[0].id = savedTxn.transaction.id + platformApiClient.patchTransaction(patch) + + // check if the patched transactions are as expected + var afterPatch = platformApiClient.getTransaction(savedTxn.transaction.id) + assertEquals(afterPatch.id, savedTxn.transaction.id) + JSONAssert.assertEquals(expectedAfterPatch, json(afterPatch), LENIENT) + + // Test patch idempotency + afterPatch = platformApiClient.getTransaction(savedTxn.transaction.id) + assertEquals(afterPatch.id, savedTxn.transaction.id) + JSONAssert.assertEquals(expectedAfterPatch, json(afterPatch), LENIENT) + } + + fun `test bad requests`() { + // 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 = + sep38Client.postQuote( + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "10", + "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + ) + + // POST SEP-31 transaction + val txnRequest = gson.fromJson(postTxnRequest, 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) + 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) + 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) + assertTrue(patchedTx.updatedAt > patchedTx.startedAt) + + // 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.truncatedTo(SECONDS), + gotSep31TxResponse.transaction.completedAt.truncatedTo(SECONDS) + ) + } } } @@ -100,12 +223,46 @@ fun sep31TestAll() { Sep31Tests.setup() println("Performing Sep31 tests...") - Sep31Tests.`test Sep31 info endpoint`() - Sep31Tests.testSep31PostAndGetTransaction() + Sep31Tests.`test info endpoint`() + Sep31Tests.`test post and get transactions`() + Sep31Tests.`test patch, get and compare`() Sep31Tests.testBadAsset() } -val wantedSep31Info = +private const val postTxnRequest = + """{ + "amount": "10", + "asset_code": "USDC", + "asset_issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "receiver_id": "MOCK_RECEIVER_ID", + "sender_id": "MOCK_SENDER_ID", + "fields": { + "transaction": { + "receiver_routing_number": "r0123", + "receiver_account_number": "a0456", + "type": "SWIFT" + } + } +}""" + +private const val expectedTxn = + """ + { + "transaction": { + "status": "pending_sender", + "amount_in": "10", + "amount_in_asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "amount_out": "1071.4285982", + "amount_out_asset": "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "amount_fee": "1.00", + "amount_fee_asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "stellar_account_id": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "stellar_memo_type": "hash" + } +} +""" + +private const val expectedSep31Info = """ { "receive": { @@ -220,4 +377,121 @@ val wantedSep31Info = } } """ - .trimIndent() + +private const val patchRequest = + """ +{ + "records": [ + { + "id": "", + "status": "completed", + "message": "this is the message", + "refunds": { + "amount_refunded": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "payments": [ + { + "id": 1, + "amount": { + "amount": "0.6", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + }, + { + "id": 2, + "amount": { + "amount": "0.4", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + } + ] + } + } + ] +} +""" + +private const val expectedAfterPatch = + """ + { + "sep": 31, + "kind": "receive", + "status": "completed", + "amount_expected": { + "amount": "10", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_in": { + "amount": "10", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_out": { + "amount": "1071.4285982", + "asset": "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "1.00", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "message": "this is the message", + "refunds": { + "amount_refunded": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "payments": [ + { + "id": "1", + "id_type": "stellar", + "amount": { + "amount": "0.6", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + }, + { + "id": "2", + "id_type": "stellar", + "amount": { + "amount": "0.4", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + } + ] + }, + "customers": { + "sender": { + }, + "receiver": { + } + }, + "creator": { + "account": "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG" + } +} +""" 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 3882a6d9f5..203f20c797 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 @@ -46,14 +46,6 @@ fun main(args: Array) { ServiceRunner.startAnchorReferenceServer() } - // Read TOML file - val tomlString = - if (cmd.hasOption("sep1-toml")) { - resourceAsString(cmd.getOptionValue("t")) - } else { - resourceAsString("classpath:/sep1/test-stellar.toml") - } - val tests = cmd.getOptionValues("p") if ("sep10" in tests) { diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json new file mode 100644 index 0000000000..a17760e029 --- /dev/null +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json @@ -0,0 +1,74 @@ +{ + "id": "ecf4fe8d-b61e-4edf-a20f-47c995246e5c", + "sep": 31, + "kind": "receive", + "status": "completed", + "amount_expected": { + "amount": "10", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_in": { + "amount": "10", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_out": { + "amount": "1071.4285982", + "asset": "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "1.00", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "quote_id": "53055040-592f-48fb-8c26-7586064ccc65", + "started_at": "2022-12-23T07:54:49.713Z", + "updated_at": "2022-12-23T07:54:51.038Z", + "completed_at": "2022-12-23T07:54:51.038Z", + "message": "this is the message", + "refunds": { + "amount_refunded": { + "amount": "1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "amount_fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "payments": [ + { + "id": "1", + "id_type": "stellar", + "amount": { + "amount": "0.6", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0.1", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + }, + { + "id": "2", + "id_type": "stellar", + "amount": { + "amount": "0.4", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + }, + "fee": { + "amount": "0", + "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + } + } + ] + }, + "customers": { + "sender": { + "id": "fa699c8b-16d7-40b3-9159-50fd4df5d439" + }, + "receiver": { + "id": "fa699c8b-16d7-40b3-9159-50fd4df5d439" + } + }, + "creator": { + "account": "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG" + } +} \ No newline at end of file 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 a6a9fd95c7..582468e0e4 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java @@ -105,9 +105,10 @@ Sep24Service sep24Service( Sep24Config sep24Config, AssetService assetService, JwtService jwtService, - Sep24TransactionStore sep24TransactionStore) { + Sep24TransactionStore sep24TransactionStore, + EventService eventService) { return new Sep24Service( - gson, appConfig, sep24Config, assetService, jwtService, sep24TransactionStore); + appConfig, sep24Config, assetService, jwtService, sep24TransactionStore, eventService); } @Bean 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 7d8b26ee5a..15daf721c5 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 @@ -28,7 +28,7 @@ public class PlatformController { method = {RequestMethod.GET}) public GetTransactionResponse getTransaction(@PathVariable(name = "id") String txnId) throws AnchorException { - return transactionService.getTransaction(txnId); + return transactionService.getTransactionResponse(txnId); } @CrossOrigin(origins = "*") 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 f8e9b7867f..4f515a4017 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 @@ -14,6 +14,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; +import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.exception.SepNotFoundException; import org.stellar.anchor.api.exception.SepValidationException; @@ -51,7 +52,7 @@ public InfoResponse getInfo() { method = {RequestMethod.POST}) public InteractiveTransactionResponse deposit( HttpServletRequest request, @RequestBody HashMap requestData) - throws SepException, MalformedURLException, URISyntaxException { + throws SepException, MalformedURLException, URISyntaxException, EventPublishException { debug("/deposit", requestData); JwtToken token = getSep10Token(request); String fullUrl = getFullRequestUrl(request); @@ -66,7 +67,7 @@ public InteractiveTransactionResponse deposit( method = {RequestMethod.POST}, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public InteractiveTransactionResponse depositAllType(HttpServletRequest request) - throws SepException, MalformedURLException, URISyntaxException { + throws SepException, MalformedURLException, URISyntaxException, EventPublishException { HashMap requestData = new HashMap<>(); for (Map.Entry entry : request.getParameterMap().entrySet()) { requestData.put(entry.getKey(), entry.getValue()[0]); @@ -82,7 +83,7 @@ public InteractiveTransactionResponse depositAllType(HttpServletRequest request) method = {RequestMethod.POST}) public InteractiveTransactionResponse withdraw( HttpServletRequest request, @RequestBody HashMap requestData) - throws SepException, MalformedURLException, URISyntaxException { + throws SepException, MalformedURLException, URISyntaxException, EventPublishException { debug("/withdraw", requestData); JwtToken token = getSep10Token(request); String fullUrl = getFullRequestUrl(request); @@ -97,7 +98,7 @@ public InteractiveTransactionResponse withdraw( method = {RequestMethod.POST}, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) public InteractiveTransactionResponse withdrawAllType(HttpServletRequest request) - throws SepException, MalformedURLException, URISyntaxException { + throws SepException, MalformedURLException, URISyntaxException, EventPublishException { HashMap requestData = new HashMap<>(); for (Map.Entry entry : request.getParameterMap().entrySet()) { requestData.put(entry.getKey(), entry.getValue()[0]); @@ -150,7 +151,7 @@ public GetTransactionsResponse getTransactions( value = "/transaction", consumes = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.GET}) - public GetTransactionResponse getTransaction( + public Sep24GetTransactionResponse getTransaction( HttpServletRequest request, @RequestBody(required = false) GetTransactionRequest tr) throws SepException, IOException, URISyntaxException { debug("/transaction", tr); @@ -164,7 +165,7 @@ public GetTransactionResponse getTransaction( value = "/transaction", consumes = {MediaType.ALL_VALUE}, method = {RequestMethod.GET}) - public GetTransactionResponse getTransaction( + public Sep24GetTransactionResponse getTransaction( HttpServletRequest request, @RequestParam(required = false, value = "id") String id, @RequestParam(required = false, value = "external_transaction_id") diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java index 3d60b84f74..cf499800e8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24RefundPayment.java @@ -2,11 +2,13 @@ import javax.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.stellar.anchor.sep24.Sep24RefundPayment; @Getter @Setter +@NoArgsConstructor public class JdbcSep24RefundPayment implements Sep24RefundPayment { String id; String amount; diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java index d3a376c837..65fa749657 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Refunds.java @@ -1,13 +1,18 @@ package org.stellar.anchor.platform.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.gson.annotations.SerializedName; import java.util.ArrayList; import java.util.List; -import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.stellar.anchor.sep24.Sep24RefundPayment; import org.stellar.anchor.sep24.Sep24Refunds; -@Data +@Getter +@Setter +@NoArgsConstructor public class JdbcSep24Refunds implements Sep24Refunds { @SerializedName("amount_refunded") String amountRefunded; @@ -16,23 +21,21 @@ public class JdbcSep24Refunds implements Sep24Refunds { String amountFee; @SerializedName("payments") - List refundPayments; + List payments; - @Override + @JsonIgnore public List getRefundPayments() { - if (refundPayments == null) return null; + if (payments == null) return null; // getPayments() is made for Gson serialization. - List payments = new ArrayList<>(refundPayments.size()); - payments.addAll(refundPayments); + List payments = new ArrayList<>(this.payments.size()); + payments.addAll(this.payments); return payments; } - @Override public void setRefundPayments(List refundPayments) { - this.refundPayments = new ArrayList<>(refundPayments.size()); + this.payments = new ArrayList<>(refundPayments.size()); for (Sep24RefundPayment rp : refundPayments) { - if (rp instanceof JdbcSep24RefundPayment) - this.refundPayments.add((JdbcSep24RefundPayment) rp); + if (rp instanceof JdbcSep24RefundPayment) this.payments.add((JdbcSep24RefundPayment) rp); else throw new ClassCastException( String.format("Error casting %s to JdbcSep24RefundPayment", rp.getClass())); diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index e06894dddd..910a556422 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -1,19 +1,18 @@ package org.stellar.anchor.platform.data; -import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import com.vladmihalcea.hibernate.type.json.JsonType; -import java.time.Instant; import java.util.List; import javax.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.springframework.beans.BeanUtils; import org.stellar.anchor.api.shared.StellarTransaction; import org.stellar.anchor.sep24.Sep24Refunds; import org.stellar.anchor.sep24.Sep24Transaction; -import org.stellar.anchor.util.GsonUtils; @Getter @Setter @@ -21,15 +20,17 @@ @Access(AccessType.FIELD) @Table(name = "sep24_transaction") @TypeDef(name = "json", typeClass = JsonType.class) -public class JdbcSep24Transaction implements Sep24Transaction, SepTransaction { - static Gson gson = GsonUtils.getInstance(); +@NoArgsConstructor +public class JdbcSep24Transaction extends JdbcSepTransaction + implements Sep24Transaction, SepTransaction { + public String getProtocol() { + return "24"; + } @Id String id; String kind; - String status; - @SerializedName("status_eta") String statusEta; @@ -39,62 +40,31 @@ public class JdbcSep24Transaction implements Sep24Transaction, SepTransaction { @SerializedName("more_info_url") String moreInfoUrl; - @SerializedName("amount_in") - String amountIn; - - @SerializedName("amount_in_asset") - String amountInAsset; - - @SerializedName("amount_out") - String amountOut; - - @SerializedName("amount_out_asset") - String amountOutAsset; - - @SerializedName("amount_fee") - String amountFee; - - @SerializedName("amount_fee_asset") - String amountFeeAsset; - - @SerializedName("started_at") - Instant startedAt; - - @SerializedName("completed_at") - Instant completedAt; - - @SerializedName("updated_at") - Instant updatedAt; - @SerializedName("transaction_id") String transactionId; - @SerializedName("stellar_transaction_id") - String stellarTransactionId; - @Column(columnDefinition = "json") @Type(type = "json") List stellarTransactions; - @SerializedName("external_transaction_id") - String externalTransactionId; - String message; Boolean refunded; - // Ignored by JPA - @Transient Sep24Refunds refunds; + @Column(columnDefinition = "json") + @Type(type = "json") + JdbcSep24Refunds refunds; - @Access(AccessType.PROPERTY) - @Column(name = "refunds") - public String getRefundsJson() { - return gson.toJson(this.refunds); + @Override + public Sep24Refunds getRefunds() { + return refunds; } - public void setRefundsJson(String refundsJson) { - if (refundsJson != null) { - this.refunds = gson.fromJson(refundsJson, JdbcSep24Refunds.class); + @Override + public void setRefunds(Sep24Refunds refunds) { + if (refunds != null) { + this.refunds = new JdbcSep24Refunds(); + BeanUtils.copyProperties(refunds, this.refunds); } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java index 5b415415f3..9c8680e1b7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java @@ -7,6 +7,8 @@ import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.api.exception.SepValidationException; import org.stellar.anchor.api.sep.sep24.GetTransactionsRequest; +import org.stellar.anchor.sep24.Sep24RefundPayment; +import org.stellar.anchor.sep24.Sep24Refunds; import org.stellar.anchor.sep24.Sep24Transaction; import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.util.DateUtil; @@ -24,6 +26,16 @@ public Sep24Transaction newInstance() { return new JdbcSep24Transaction(); } + @Override + public Sep24Refunds newRefunds() { + return new JdbcSep24Refunds(); + } + + @Override + public Sep24RefundPayment newRefundPayment() { + return new JdbcSep24RefundPayment(); + } + @Override public Sep24Transaction findByTransactionId(String transactionId) { return txnRepo.findOneByTransactionId(transactionId); 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 e817b73065..32658d8cd1 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,14 +1,15 @@ package org.stellar.anchor.platform.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.gson.annotations.SerializedName; import java.util.ArrayList; import java.util.List; import lombok.Data; import org.stellar.anchor.sep31.RefundPayment; -import org.stellar.anchor.sep31.Refunds; +import org.stellar.anchor.sep31.Sep31Refunds; @Data -public class JdbcSep31Refunds implements Refunds { +public class JdbcSep31Refunds implements Sep31Refunds { @SerializedName("amount_refunded") String amountRefunded; @@ -16,23 +17,23 @@ public class JdbcSep31Refunds implements Refunds { String amountFee; @SerializedName("payments") - List refundPayments; + List payments; @Override + @JsonIgnore public List getRefundPayments() { - if (refundPayments == null) return null; + if (payments == null) return null; // getPayments() is made for Gson serialization. - List payments = new ArrayList<>(refundPayments.size()); - payments.addAll(refundPayments); + List payments = new ArrayList<>(this.payments.size()); + payments.addAll(this.payments); return payments; } @Override public void setRefundPayments(List refundPayments) { - this.refundPayments = new ArrayList<>(refundPayments.size()); + this.payments = new ArrayList<>(refundPayments.size()); for (RefundPayment rp : refundPayments) { - if (rp instanceof JdbcSep31RefundPayment) - this.refundPayments.add((JdbcSep31RefundPayment) rp); + if (rp instanceof JdbcSep31RefundPayment) this.payments.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 4b41cbddbf..46c6000f1e 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 @@ -1,10 +1,8 @@ package org.stellar.anchor.platform.data; -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 javax.persistence.*; @@ -18,9 +16,8 @@ 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.Sep31Refunds; import org.stellar.anchor.sep31.Sep31Transaction; -import org.stellar.anchor.util.GsonUtils; @Getter @Setter @@ -29,33 +26,17 @@ @Table(name = "sep31_transaction") @TypeDef(name = "json", typeClass = JsonType.class) @NoArgsConstructor -public class JdbcSep31Transaction implements Sep31Transaction, SepTransaction { - static Gson gson = GsonUtils.getInstance(); +public class JdbcSep31Transaction extends JdbcSepTransaction + implements Sep31Transaction, SepTransaction { + public String getProtocol() { + return "31"; + } @Id String id; - String status; @SerializedName("status_eta") Long statusEta; - @SerializedName("amount_in") - String amountIn; - - @SerializedName("amount_in_asset") - String amountInAsset; - - @SerializedName("amount_out") - String amountOut; - - @SerializedName("amount_out_asset") - String amountOutAsset; - - @SerializedName("amount_fee") - String amountFee; - - @SerializedName("amount_fee_asset") - String amountFeeAsset; - @SerializedName("stellar_account_id") String stellarAccountId; @@ -65,25 +46,10 @@ public class JdbcSep31Transaction implements Sep31Transaction, SepTransaction { @SerializedName("stellar_memo_type") String stellarMemoType; - @SerializedName("started_at") - Instant startedAt; - - @SerializedName("completed_at") - Instant completedAt; - - @SerializedName("stellar_transaction_id") - String stellarTransactionId; - @Column(columnDefinition = "json") @Type(type = "json") List stellarTransactions; - @SerializedName("external_transaction_id") - String externalTransactionId; - - @SerializedName("required_info_message") - String requiredInfoMessage; - @SerializedName("quote_id") String quoteId; @@ -141,18 +107,16 @@ public void setRequiredInfoUpdatesJson(String requiredInfoUpdatesJson) { @Type(type = "json") JdbcSep31Refunds refunds; - public Refunds getRefunds() { + public Sep31Refunds getRefunds() { return refunds; } - public void setRefunds(Refunds refunds) { - if (refunds != null) { + public void setRefunds(Sep31Refunds sep31Refunds) { + if (sep31Refunds != null) { this.refunds = new JdbcSep31Refunds(); - BeanUtils.copyProperties(refunds, this.refunds); + BeanUtils.copyProperties(sep31Refunds, this.refunds); } } - Instant updatedAt; - Instant transferReceivedAt; String amountExpected; } 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 6a7c45868b..ff30703724 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,7 +7,7 @@ import lombok.NonNull; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.sep31.RefundPayment; -import org.stellar.anchor.sep31.Refunds; +import org.stellar.anchor.sep31.Sep31Refunds; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.sep31.Sep31TransactionStore; @@ -24,7 +24,7 @@ public Sep31Transaction newTransaction() { } @Override - public Refunds newRefunds() { + public Sep31Refunds newRefunds() { return new JdbcSep31Refunds(); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java new file mode 100644 index 0000000000..789e39d5f4 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java @@ -0,0 +1,60 @@ +package org.stellar.anchor.platform.data; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import java.time.Instant; +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; +import lombok.Getter; +import lombok.Setter; +import org.stellar.anchor.util.GsonUtils; + +@Getter +@Setter +@MappedSuperclass +public abstract class JdbcSepTransaction { + @Transient static Gson gson = GsonUtils.getInstance(); + + String status; + + @SerializedName("updated_at") + Instant updatedAt; + + @SerializedName("amount_in") + String amountIn; + + @SerializedName("amount_in_asset") + String amountInAsset; + + @SerializedName("amount_out") + String amountOut; + + @SerializedName("amount_out_asset") + String amountOutAsset; + + @SerializedName("amount_fee") + String amountFee; + + @SerializedName("amount_fee_asset") + String amountFeeAsset; + + @SerializedName("started_at") + Instant startedAt; + + @SerializedName("completed_at") + Instant completedAt; + + @SerializedName("transfer_received_at") + Instant transferReceivedAt; + + @SerializedName("stellar_transaction_id") + String stellarTransactionId; + + @SerializedName("external_transaction_id") + String externalTransactionId; + + @SerializedName("required_info_message") + String requiredInfoMessage; + + public abstract String getProtocol(); +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/AnchorMetrics.java b/platform/src/main/java/org/stellar/anchor/platform/service/AnchorMetrics.java index 890a4a9a4d..5d6fb455b5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/AnchorMetrics.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/AnchorMetrics.java @@ -1,8 +1,10 @@ package org.stellar.anchor.platform.service; public enum AnchorMetrics { + SEP24_TRANSACTION("sep24.transaction"), SEP31_TRANSACTION("sep31.transaction"), SEP31_TRANSACTION_DB("sep31.transaction.db"), + SEP24_TRANSACTION_DB("sep24.transaction.db"), PAYMENT_RECEIVED("payment.received"), LOGGER("logger"), 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 b00d656cab..895757d31e 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 @@ -1,6 +1,7 @@ package org.stellar.anchor.platform.service; import static org.stellar.anchor.api.sep.SepTransactionStatus.*; +import static org.stellar.anchor.event.models.TransactionEvent.Type.TRANSACTION_STATUS_CHANGED; import static org.stellar.anchor.sep31.Sep31Helper.allAmountAvailable; import static org.stellar.anchor.util.MathHelper.decimal; import static org.stellar.anchor.util.MathHelper.equalsAsDecimals; @@ -9,15 +10,9 @@ import java.time.Instant; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.function.Function; -import java.util.stream.Collectors; import org.springframework.stereotype.Service; -import org.stellar.anchor.api.exception.AnchorException; -import org.stellar.anchor.api.exception.BadRequestException; -import org.stellar.anchor.api.exception.InternalServerErrorException; -import org.stellar.anchor.api.exception.NotFoundException; +import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.platform.PatchTransactionRequest; import org.stellar.anchor.api.platform.PatchTransactionsRequest; @@ -25,15 +20,17 @@ import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.SepTransactionStatus; 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.api.shared.Refunds; import org.stellar.anchor.asset.AssetService; +import org.stellar.anchor.event.EventService; import org.stellar.anchor.event.models.TransactionEvent; import org.stellar.anchor.platform.data.JdbcSep24Transaction; -import org.stellar.anchor.sep24.Sep24RefundPayment; -import org.stellar.anchor.sep24.Sep24Refunds; -import org.stellar.anchor.sep24.Sep24TransactionStore; -import org.stellar.anchor.sep31.Refunds; +import org.stellar.anchor.platform.data.JdbcSep31Transaction; +import org.stellar.anchor.platform.data.JdbcSepTransaction; +import org.stellar.anchor.sep24.*; +import org.stellar.anchor.sep31.Sep31Helper; +import org.stellar.anchor.sep31.Sep31Refunds; import org.stellar.anchor.sep31.Sep31Transaction; import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep38.Sep38Quote; @@ -48,6 +45,7 @@ public class TransactionService { private final Sep31TransactionStore txn31Store; private final Sep24TransactionStore txn24Store; private final List assets; + private final EventService eventService; static boolean isStatusError(String status) { return List.of(PENDING_CUSTOMER_INFO_UPDATE.getName(), EXPIRED.getName(), ERROR.getName()) @@ -58,164 +56,201 @@ static boolean isStatusError(String status) { Sep24TransactionStore txn24Store, Sep31TransactionStore txn31Store, Sep38QuoteStore quoteStore, - AssetService assetService) { + AssetService assetService, + EventService eventService) { this.txn24Store = txn24Store; this.txn31Store = txn31Store; this.quoteStore = quoteStore; this.assets = assetService.listAllAssets(); + this.eventService = eventService; } - public GetTransactionResponse getTransaction(String txnId) throws AnchorException { + /** + * Fetch the transaction and convert the transaction to an object of Sep24GetTransactionResponse + * class. + * + * @param txnId the transaction ID + * @return the result + */ + public GetTransactionResponse getTransactionResponse(String txnId) throws AnchorException { 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 txn31 = txn31Store.findByTransactionId(txnId); - if (txn31 != null) { - return toGetTransactionResponse(txn31); + JdbcSepTransaction txn = findTransaction(txnId); + if (txn != null) { + return toGetTransactionResponse(txn); + } else { + throw new NotFoundException(String.format("transaction (id=%s) is not found", txnId)); } + } - JdbcSep24Transaction txn24 = (JdbcSep24Transaction) txn24Store.findByTransactionId(txnId); - if (txn24 != null) { - return toGetTransactionResponse(txn24); + /** + * Fetch the transaction. + * + * @param txnId the transaction ID + * @return an object of JdbcSepTransaction + */ + public JdbcSepTransaction findTransaction(String txnId) throws AnchorException { + JdbcSep31Transaction txn31 = (JdbcSep31Transaction) txn31Store.findByTransactionId(txnId); + if (txn31 != null) { + return txn31; } - throw new NotFoundException(String.format("transaction (id=%s) is not found", txnId)); + return (JdbcSep24Transaction) txn24Store.findByTransactionId(txnId); } + /** + * Patch transactions. + * + * @param request the request + * @return the response + */ public PatchTransactionsResponse patchTransactions(PatchTransactionsRequest request) throws AnchorException { List patchRequests = request.getRecords(); - List ids = - patchRequests.stream().map(PatchTransactionRequest::getId).collect(Collectors.toList()); - List fetchedTxns = txn31Store.findByTransactionIds(ids); - Map sep31Transactions = - fetchedTxns.stream() - .collect(Collectors.toMap(Sep31Transaction::getId, Function.identity())); - - List txnsToSave = new LinkedList<>(); - List responses = new LinkedList<>(); - List statusUpdatedTxns = new LinkedList<>(); - - for (PatchTransactionRequest patch : patchRequests) { - Sep31Transaction txn = sep31Transactions.get(patch.getId()); - if (txn != null) { - String txnOriginalStatus = txn.getStatus(); - // validate and update the transaction. - updateSep31Transaction(patch, txn); - // Add them to the to-be-updated lists. - txnsToSave.add(txn); - if (!txnOriginalStatus.equals(txn.getStatus())) { - statusUpdatedTxns.add(txn); - } - responses.add(toGetTransactionResponse(txn)); - } else { - throw new BadRequestException(String.format("transaction(id=%s) not found", patch.getId())); - } + + // PatchTransactionsResponse patchTransactionsResponse = new PatchTransactionsResponse(); + List txnResponses = new LinkedList<>(); + for (PatchTransactionRequest patchRequest : patchRequests) { + txnResponses.add(patchTransaction(patchRequest)); } - for (Sep31Transaction txn : txnsToSave) { - // TODO: consider 2-phase commit DB transaction management. - txn31Store.save(txn); + return new PatchTransactionsResponse(txnResponses); + } + + private GetTransactionResponse patchTransaction(PatchTransactionRequest patch) + throws AnchorException { + JdbcSepTransaction txn = findTransaction(patch.getId()); + if (txn == null) + throw new BadRequestException(String.format("transaction(id=%s) not found", patch.getId())); + + String lastStatus = txn.getStatus(); + updateSepTransaction(patch, txn); + switch (txn.getProtocol()) { + case "24": + txn24Store.save((JdbcSep24Transaction) txn); + Sep24Helper.publishEvent(eventService, (Sep24Transaction) txn, TRANSACTION_STATUS_CHANGED); + break; + case "31": + txn31Store.save((JdbcSep31Transaction) txn); + Sep31Helper.publishEvent(eventService, (Sep31Transaction) txn, TRANSACTION_STATUS_CHANGED); + break; } - for (Sep31Transaction txn : statusUpdatedTxns) { - Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", txn.getStatus()) - .increment(); + if (!lastStatus.equals(txn.getStatus())) updateMetrics(txn); + return toGetTransactionResponse(txn); + } + + private void updateMetrics(JdbcSepTransaction txn) { + switch (txn.getProtocol()) { + case "24": + Metrics.counter(AnchorMetrics.SEP24_TRANSACTION.toString(), "status", txn.getStatus()) + .increment(); + break; + case "31": + Metrics.counter(AnchorMetrics.SEP31_TRANSACTION.toString(), "status", txn.getStatus()) + .increment(); + break; } - return new PatchTransactionsResponse(responses); } - /** - * 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) + void updateSepTransaction(PatchTransactionRequest patch, JdbcSepTransaction txn) throws AnchorException { boolean txWasUpdated = false; boolean txWasCompleted = false; boolean shouldClearMessageStatus = - !StringHelper.isEmpty(ptr.getStatus()) - && !isStatusError(ptr.getStatus()) + !StringHelper.isEmpty(patch.getStatus()) + && !isStatusError(patch.getStatus()) && !StringHelper.isEmpty(txn.getStatus()) && isStatusError(txn.getStatus()); - if (ptr.getStatus() != null && !Objects.equals(txn.getStatus(), ptr.getStatus())) { - validateIfStatusIsSupported(ptr.getStatus()); + if (patch.getStatus() != null && !Objects.equals(txn.getStatus(), patch.getStatus())) { + validateIfStatusIsSupported(patch.getStatus()); txWasCompleted = !Objects.equals(txn.getStatus(), COMPLETED.getName()) - && Objects.equals(ptr.getStatus(), COMPLETED.getName()); - txn.setStatus(ptr.getStatus()); + && Objects.equals(patch.getStatus(), COMPLETED.getName()); + txn.setStatus(patch.getStatus()); txWasUpdated = true; } - 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()); + if (patch.getAmountIn() != null + && (!Objects.equals(txn.getAmountIn(), patch.getAmountIn().getAmount()) + || !Objects.equals(txn.getAmountInAsset(), patch.getAmountIn().getAsset()))) { + validateAsset("amount_in", patch.getAmountIn()); + txn.setAmountIn(patch.getAmountIn().getAmount()); + txn.setAmountInAsset(patch.getAmountIn().getAsset()); txWasUpdated = true; } - 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()); + if (patch.getAmountOut() != null + && (!Objects.equals(txn.getAmountOut(), patch.getAmountOut().getAmount()) + || !Objects.equals(txn.getAmountOutAsset(), patch.getAmountOut().getAsset()))) { + validateAsset("amount_out", patch.getAmountOut()); + txn.setAmountOut(patch.getAmountOut().getAmount()); + txn.setAmountOutAsset(patch.getAmountOut().getAsset()); txWasUpdated = true; } - 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()); + if (patch.getAmountFee() != null + && (!Objects.equals(txn.getAmountFee(), patch.getAmountFee().getAmount()) + || !Objects.equals(txn.getAmountFeeAsset(), patch.getAmountFee().getAsset()))) { + validateAsset("amount_fee", patch.getAmountFee()); + txn.setAmountFee(patch.getAmountFee().getAmount()); + txn.setAmountFeeAsset(patch.getAmountFee().getAsset()); txWasUpdated = true; } - if (ptr.getTransferReceivedAt() != null - && ptr.getTransferReceivedAt().compareTo(txn.getStartedAt()) != 0) { - if (ptr.getTransferReceivedAt().compareTo(txn.getStartedAt()) < 0) { + if (patch.getTransferReceivedAt() != null + && patch.getTransferReceivedAt().compareTo(txn.getStartedAt()) != 0) { + if (patch.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())); + patch.getTransferReceivedAt().toString(), txn.getStartedAt().toString())); } - txn.setTransferReceivedAt(ptr.getTransferReceivedAt()); + txn.setTransferReceivedAt(patch.getTransferReceivedAt()); txWasUpdated = true; } - if (ptr.getMessage() != null) { - if (!Objects.equals(txn.getRequiredInfoMessage(), ptr.getMessage())) { - txn.setRequiredInfoMessage(ptr.getMessage()); + if (patch.getMessage() != null) { + if (!Objects.equals(txn.getRequiredInfoMessage(), patch.getMessage())) { + txn.setRequiredInfoMessage(patch.getMessage()); txWasUpdated = true; } } else if (shouldClearMessageStatus) { txn.setRequiredInfoMessage(null); } - if (ptr.getRefunds() != null) { - Refunds updatedRefunds = Refunds.of(ptr.getRefunds(), txn31Store); - // TODO: validate refunds - if (!Objects.equals(txn.getRefunds(), updatedRefunds)) { - txn.setRefunds(updatedRefunds); - txWasUpdated = true; - } - } - - if (ptr.getExternalTransactionId() != null - && !Objects.equals(txn.getExternalTransactionId(), ptr.getExternalTransactionId())) { - txn.setExternalTransactionId(ptr.getExternalTransactionId()); + if (patch.getExternalTransactionId() != null + && !Objects.equals(txn.getExternalTransactionId(), patch.getExternalTransactionId())) { + txn.setExternalTransactionId(patch.getExternalTransactionId()); txWasUpdated = true; } - validateQuoteAndAmounts(txn); + switch (txn.getProtocol()) { + case "24": + JdbcSep24Transaction sep24Txn = (JdbcSep24Transaction) txn; + if (patch.getRefunds() != null) { + Sep24Refunds updatedRefunds = Sep24Refunds.of(patch.getRefunds(), txn24Store); + // TODO: validate refunds + if (!Objects.equals(sep24Txn.getRefunds(), updatedRefunds)) { + sep24Txn.setRefunds(updatedRefunds); + txWasUpdated = true; + } + } + break; + case "31": + JdbcSep31Transaction sep31Txn = (JdbcSep31Transaction) txn; + if (patch.getRefunds() != null) { + Sep31Refunds updatedSep31Refunds = Sep31Refunds.of(patch.getRefunds(), txn31Store); + // TODO: validate refunds + if (!Objects.equals(sep31Txn.getRefunds(), updatedSep31Refunds)) { + sep31Txn.setRefunds(updatedSep31Refunds); + txWasUpdated = true; + } + } + validateQuoteAndAmounts(sep31Txn); + break; + } Instant now = Instant.now(); if (txWasUpdated) { @@ -311,13 +346,50 @@ void validateQuoteAndAmounts(Sep31Transaction txn) throws AnchorException { } } - GetTransactionResponse toGetTransactionResponse(Sep31Transaction txn) { - Refund refunds = null; + GetTransactionResponse toGetTransactionResponse(JdbcSepTransaction txn) throws SepException { + switch (txn.getProtocol()) { + case "24": + return toGetTransactionResponse((JdbcSep24Transaction) txn); + case "31": + return toGetTransactionResponse((JdbcSep31Transaction) txn); + default: + throw new SepException(String.format("Unsupported protocol:%s", txn.getProtocol())); + } + } + + RefundPayment toRefundPayment( + org.stellar.anchor.sep31.RefundPayment refundPayment, String assetName) { + return RefundPayment.builder() + .id(refundPayment.getId()) + .idType(RefundPayment.IdType.STELLAR) + .amount(new Amount(refundPayment.getAmount(), assetName)) + .fee(new Amount(refundPayment.getFee(), assetName)) + .requestedAt(null) + .refundedAt(null) + .build(); + } + + Refunds toRefunds(Sep31Refunds refunds, String assetName) { + // build payments + RefundPayment[] payments = + refunds.getRefundPayments().stream() + .map(refundPayment -> toRefundPayment(refundPayment, assetName)) + .toArray(RefundPayment[]::new); + + return Refunds.builder() + .amountRefunded(new Amount(refunds.getAmountRefunded(), assetName)) + .amountFee(new Amount(refunds.getAmountFee(), assetName)) + .payments(payments) + .build(); + } + + private GetTransactionResponse toGetTransactionResponse(JdbcSep31Transaction txn) { + Refunds refunds = null; if (txn.getRefunds() != null) { - refunds = txn.getRefunds().toPlatformApiRefund(txn.getAmountInAsset()); + refunds = toRefunds(txn.getRefunds(), txn.getAmountInAsset()); } - return org.stellar.anchor.api.platform.GetTransactionResponse.builder() + return GetTransactionResponse.builder() .id(txn.getId()) .sep(31) .kind(TransactionEvent.Kind.RECEIVE.getKind()) @@ -341,9 +413,9 @@ GetTransactionResponse toGetTransactionResponse(Sep31Transaction txn) { } RefundPayment toRefundPayment(Sep24RefundPayment refundPayment, String assetName) { - return org.stellar.anchor.api.shared.RefundPayment.builder() + return RefundPayment.builder() .id(refundPayment.getId()) - .idType(org.stellar.anchor.api.shared.RefundPayment.IdType.STELLAR) + .idType(RefundPayment.IdType.STELLAR) .amount(new Amount(refundPayment.getAmount(), assetName)) .fee(new Amount(refundPayment.getFee(), assetName)) .requestedAt(null) @@ -351,27 +423,27 @@ RefundPayment toRefundPayment(Sep24RefundPayment refundPayment, String assetName .build(); } - org.stellar.anchor.api.shared.Refund toRefunds(Sep24Refunds refunds, String assetName) { + Refunds toRefunds(Sep24Refunds refunds, String assetName) { // build payments - org.stellar.anchor.api.shared.RefundPayment[] payments = + RefundPayment[] payments = refunds.getRefundPayments().stream() .map(refundPayment -> toRefundPayment(refundPayment, assetName)) - .toArray(org.stellar.anchor.api.shared.RefundPayment[]::new); + .toArray(RefundPayment[]::new); - return org.stellar.anchor.api.shared.Refund.builder() + return Refunds.builder() .amountRefunded(new Amount(refunds.getAmountRefunded(), assetName)) .amountFee(new Amount(refunds.getAmountFee(), assetName)) .payments(payments) .build(); } - GetTransactionResponse toGetTransactionResponse(JdbcSep24Transaction txn) { - Refund refunds = null; + private GetTransactionResponse toGetTransactionResponse(JdbcSep24Transaction txn) { + Refunds refunds = null; if (txn.getRefunds() != null) { refunds = toRefunds(txn.getRefunds(), txn.getAmountInAsset()); } - return org.stellar.anchor.api.platform.GetTransactionResponse.builder() + return GetTransactionResponse.builder() .id(txn.getId()) .sep(24) .kind(txn.getKind()) diff --git a/platform/src/main/resources/db/migration/V1__master.sql b/platform/src/main/resources/db/migration/V1__master.sql index 5318595968..76bd8fd7c6 100644 --- a/platform/src/main/resources/db/migration/V1__master.sql +++ b/platform/src/main/resources/db/migration/V1__master.sql @@ -88,7 +88,7 @@ CREATE TABLE sep31_transaction ( amount_expected VARCHAR(255), fields VARCHAR(255), required_info_updates VARCHAR(255), - refunds VARCHAR(255), + sep31Refunds VARCHAR(255), CONSTRAINT pk_sep31_transaction PRIMARY KEY (id) ); 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 d223ca0c06..50dc393bd1 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 @@ -13,23 +13,24 @@ import org.junit.jupiter.params.provider.EnumSource import org.junit.jupiter.params.provider.NullSource import org.junit.jupiter.params.provider.ValueSource import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode.LENIENT import org.skyscreamer.jsonassert.JSONCompareMode.STRICT 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.SepTransactionStatus 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.api.shared.Refunds import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.ResourceJsonAssetService +import org.stellar.anchor.event.EventService 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.sep24.Sep24TransactionStore -import org.stellar.anchor.sep31.Refunds +import org.stellar.anchor.sep31.Sep31Refunds import org.stellar.anchor.sep31.Sep31TransactionStore import org.stellar.anchor.sep38.Sep38Quote import org.stellar.anchor.sep38.Sep38QuoteStore @@ -51,6 +52,7 @@ class TransactionServiceTest { @MockK(relaxed = true) private lateinit var sep31TransactionStore: Sep31TransactionStore @MockK(relaxed = true) private lateinit var sep24TransactionStore: Sep24TransactionStore @MockK(relaxed = true) private lateinit var assetService: AssetService + @MockK(relaxed = true) private lateinit var eventService: EventService private lateinit var transactionService: TransactionService @BeforeEach @@ -61,7 +63,8 @@ class TransactionServiceTest { sep24TransactionStore, sep31TransactionStore, sep38QuoteStore, - assetService + assetService, + eventService ) } @@ -74,19 +77,19 @@ class TransactionServiceTest { @Test fun test_getTransaction_failure() { // null tx id is rejected with 400 - var ex: AnchorException = assertThrows { transactionService.getTransaction(null) } + var ex: AnchorException = assertThrows { transactionService.getTransactionResponse(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("") } + ex = assertThrows { transactionService.getTransactionResponse("") } 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 every { sep24TransactionStore.findByTransactionId(any()) } returns null - ex = assertThrows { transactionService.getTransaction("not-found-tx-id") } + ex = assertThrows { transactionService.getTransactionResponse("not-found-tx-id") } assertInstanceOf(NotFoundException::class.java, ex) assertEquals("transaction (id=not-found-tx-id) is not found", ex.message) } @@ -99,13 +102,15 @@ class TransactionServiceTest { every { sep31TransactionStore.newRefundPayment() } answers { JdbcSep31RefundPayment() } val mockSep31Transaction = gson.fromJson(jsonSep31Transaction, JdbcSep31Transaction::class.java) - val wantGetTransactionResponse = - gson.fromJson(wantedGetTransactionResponse, GetTransactionResponse::class.java) every { sep31TransactionStore.findByTransactionId(TEST_TXN_ID) } returns mockSep31Transaction - val gotGetTransactionResponse = transactionService.getTransaction(TEST_TXN_ID) + val gotGetTransactionResponse = transactionService.getTransactionResponse(TEST_TXN_ID) - assertEquals(wantGetTransactionResponse, gotGetTransactionResponse) + JSONAssert.assertEquals( + wantedGetTransactionResponse, + gson.toJson(gotGetTransactionResponse), + LENIENT + ) } @Test @@ -175,7 +180,8 @@ class TransactionServiceTest { sep24TransactionStore, sep31TransactionStore, sep38QuoteStore, - assetService + assetService, + eventService ) val mockAsset = Amount("10", fiatUSD) assertDoesNotThrow { transactionService.validateAsset("amount_in", mockAsset) } @@ -219,8 +225,8 @@ class TransactionServiceTest { val mockStartedAt = Instant.now().minusSeconds(180) val mockTransferReceivedAt = mockStartedAt.plusSeconds(60) - val mockRefunds: Refund = - Refund.builder() + val mockRefunds: Refunds = + Refunds.builder() .amountRefunded(Amount("90.0000", fiatUSD)) .amountFee(Amount("8.0000", fiatUSD)) .payments( @@ -280,13 +286,14 @@ class TransactionServiceTest { sep24TransactionStore, sep31TransactionStore, sep38QuoteStore, - assetService + assetService, + eventService ) assertEquals(mockSep31Transaction.startedAt, mockSep31Transaction.updatedAt) assertNull(mockSep31Transaction.completedAt) assertDoesNotThrow { - transactionService.updateSep31Transaction(mockPatchTransactionRequest, mockSep31Transaction) + transactionService.updateSepTransaction(mockPatchTransactionRequest, mockSep31Transaction) } assertTrue(mockSep31Transaction.updatedAt > mockSep31Transaction.startedAt) assertTrue(mockSep31Transaction.updatedAt == mockSep31Transaction.completedAt) @@ -307,7 +314,7 @@ class TransactionServiceTest { wantSep31TransactionUpdated.requiredInfoMessage = "Remittance was successfully completed." wantSep31TransactionUpdated.externalTransactionId = "external-id" wantSep31TransactionUpdated.transferReceivedAt = mockTransferReceivedAt - wantSep31TransactionUpdated.refunds = Refunds.of(mockRefunds, sep31TransactionStore) + wantSep31TransactionUpdated.refunds = Sep31Refunds.of(mockRefunds, sep31TransactionStore) JSONAssert.assertEquals( gson.toJson(wantSep31TransactionUpdated), gson.toJson(mockSep31Transaction), @@ -419,10 +426,6 @@ class TransactionServiceTest { "asset": "iso4217:USD" }, "quote_id": "quote-id", - "started_at": "2022-12-19T02:06:44.500182800Z", - "updated_at": "2022-12-19T02:07:44.500182800Z", - "completed_at": "2022-12-19T02:09:44.500182800Z", - "transfer_received_at": "2022-12-19T02:08:44.500182800Z", "message": "Please don\u0027t forget to foo bar", "refunds": { "amount_refunded": { @@ -465,7 +468,6 @@ class TransactionServiceTest { "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", "memo": "my-memo", "memo_type": "text", - "created_at": "2022-12-19T02:08:44.500182800Z", "envelope": "here_comes_the_envelope", "payments": [ { From e1d1d149fa49b18cdbb973aac5332bd5d655835a Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Sun, 25 Dec 2022 10:24:01 +0800 Subject: [PATCH 0068/1439] all: Restructure Spring configurations and remove @Profiles (#690) * Remove Spring profiles and restructured Spring configuration classes * Rename component beans * Simplified GsonUtils usage --- .../reference/AnchorReferenceConfig.java | 2 +- .../reference/controller/FeeController.java | 2 +- .../reference/controller/RateController.java | 2 +- build.gradle.kts | 2 +- .../org/stellar/anchor/platform/TestHelper.kt | 3 +- .../anchor/platform/AnchorPlatformServer.java | 9 +- .../platform/ConfigManagementBeans.java | 120 ------------------ .../stellar/anchor/platform/HelperBeans.java | 14 -- .../platform/StellarObservingServer.java | 20 +-- .../component/observer/ConfigBeans.java | 26 ++++ .../observer}/PaymentObserverBeans.java | 17 +-- .../{ => component/sep}/CallbackApiBeans.java | 10 +- .../platform/component/sep/ConfigBeans.java | 16 +++ .../{ => component/sep}/PlatformApiBeans.java | 26 +++- .../sep/SepBeans.java} | 69 ++++++---- .../platform/component/share/AssetBeans.java | 24 ++++ .../platform/component/share/DataBeans.java | 38 ++++++ .../share/EventBeans.java} | 27 +++- .../{ => component/share}/MetricsBeans.java | 14 +- .../share/ObservingAccountsBeans.java | 15 +++ .../share}/RequestLoggerBeans.java | 2 +- .../component/share/UtilityBeans.java | 42 ++++++ .../configurator/ObserverConfigManager.java | 2 - .../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 | 4 +- .../platform/event/KafkaEventPublisher.java | 12 +- ...JdbcStellarPaymentStreamerCursorStore.java | 4 +- .../stellar/StellarPaymentObserver.java | 30 ++++- .../platform/service/HealthCheckService.java | 4 - .../PaymentOperationToEventListener.java | 6 +- .../platform/service/TransactionService.java | 4 +- ...t => PaymentObservingAccountsBeansTest.kt} | 3 +- .../stellar/StellarPaymentObserverTest.kt | 17 ++- 38 files changed, 350 insertions(+), 248 deletions(-) delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java delete mode 100644 platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/observer/ConfigBeans.java rename platform/src/main/java/org/stellar/anchor/platform/{ => component/observer}/PaymentObserverBeans.java (83%) rename platform/src/main/java/org/stellar/anchor/platform/{ => component/sep}/CallbackApiBeans.java (92%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/sep/ConfigBeans.java rename platform/src/main/java/org/stellar/anchor/platform/{ => component/sep}/PlatformApiBeans.java (61%) rename platform/src/main/java/org/stellar/anchor/platform/{SepServiceBeans.java => component/sep/SepBeans.java} (83%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/share/DataBeans.java rename platform/src/main/java/org/stellar/anchor/platform/{EventsBeans.java => component/share/EventBeans.java} (62%) rename platform/src/main/java/org/stellar/anchor/platform/{ => component/share}/MetricsBeans.java (58%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/share/ObservingAccountsBeans.java rename platform/src/main/java/org/stellar/anchor/platform/{ => component/share}/RequestLoggerBeans.java (92%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/share/UtilityBeans.java rename platform/src/test/kotlin/org/stellar/anchor/platform/{PaymentObserverBeansTest.kt => PaymentObservingAccountsBeansTest.kt} (97%) diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceConfig.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceConfig.java index ce0786c055..be2535e974 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceConfig.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceConfig.java @@ -21,7 +21,7 @@ public class AnchorReferenceConfig { @Bean public Gson gson() { - return GsonUtils.builder().create(); + return GsonUtils.getInstance(); } @Bean(name = "eventListener") diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/FeeController.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/FeeController.java index 27486f2ada..4703a7e6b6 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/FeeController.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/controller/FeeController.java @@ -15,7 +15,7 @@ @RestController public class FeeController { final FeeService feeService; - static final Gson gson = GsonUtils.builder().create(); + static final Gson gson = GsonUtils.getInstance(); public FeeController(FeeService feeService) { this.feeService = feeService; 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 29d0a4e2fc..1674d153b8 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 @@ -12,7 +12,7 @@ @RestController public class RateController { private final RateService rateService; - private static final Gson gson = GsonUtils.builder().create(); + private static final Gson gson = GsonUtils.getInstance(); public RateController(RateService rateService) { this.rateService = rateService; diff --git a/build.gradle.kts b/build.gradle.kts index 6b8773d61b..8e9c945765 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ subprojects { mavenLocal() mavenCentral() maven { url = uri("https://packages.confluent.io/maven") } - maven { url = uri("https://repository.mulesoft.org/nexus/content/repositories/public/") } + maven { url = uri("https://reposdeitory.mulesoft.org/nexus/content/repositories/public/") } maven { url = uri("https://jitpack.io") } } diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/TestHelper.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/TestHelper.kt index 3ca9fd44f1..e43846b98e 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/TestHelper.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/TestHelper.kt @@ -7,7 +7,8 @@ import org.springframework.core.io.DefaultResourceLoader import org.springframework.util.FileCopyUtils import org.stellar.anchor.util.GsonUtils -val gson: Gson = GsonUtils.builder().setPrettyPrinting().create() +// val gson: Gson = GsonUtils.builder().setPrettyPrinting().create() +val gson: Gson = GsonUtils.getInstance() fun json(value: Any?): String { if (value != null) return gson.toJson(value) 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 202e05a4ec..2ca2e075e8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -9,17 +9,22 @@ 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.context.annotation.ComponentScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.stellar.anchor.platform.configurator.ConfigEnvironment; import org.stellar.anchor.platform.configurator.SecretManager; import org.stellar.anchor.platform.configurator.SepConfigManager; -@Profile("default") @SpringBootApplication @EnableJpaRepositories(basePackages = {"org.stellar.anchor.platform.data"}) @EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) +@ComponentScan( + basePackages = { + "org.stellar.anchor.platform.controller", + "org.stellar.anchor.platform.component.sep", + "org.stellar.anchor.platform.component.share" + }) @EnableConfigurationProperties public class AnchorPlatformServer implements WebMvcConfigurer { public static ConfigurableApplicationContext start(Map environment) { diff --git a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java b/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java deleted file mode 100644 index 5820d2e205..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/ConfigManagementBeans.java +++ /dev/null @@ -1,120 +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.springframework.context.annotation.Profile; -import org.stellar.anchor.config.*; -import org.stellar.anchor.platform.config.*; -import org.stellar.anchor.platform.configurator.ConfigManager; -import org.stellar.anchor.platform.configurator.ObserverConfigManager; -import org.stellar.anchor.platform.configurator.SepConfigManager; - -@Configuration -public class ConfigManagementBeans { - @Bean(name = "configManager") - @Profile("stellar-observer") - ConfigManager observerConfigManager() { - return ObserverConfigManager.getInstance(); - } - - @Bean(name = "configManager") - @Profile("default") - ConfigManager configManager() { - return SepConfigManager.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); - } - - /********************************** - * SEP configurations - */ - @Bean - @ConfigurationProperties(prefix = "sep1") - Sep1Config sep1Config() { - return new PropertySep1Config(); - } - - @Bean - @ConfigurationProperties(prefix = "sep10") - Sep10Config sep10Config(SecretConfig secretConfig) { - return new PropertySep10Config(secretConfig); - } - - @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() { - return new PropertySep31Config(); - } - - @Bean - @ConfigurationProperties(prefix = "sep38") - Sep38Config sep38Config() { - return new PropertySep38Config(); - } - - /********************************** - * Payment observer configurations - */ - - @Bean - @ConfigurationProperties(prefix = "payment-observer") - public PaymentObserverConfig paymentObserverConfig() { - return new PaymentObserverConfig(); - } - - /********************************** - * Event configurations - */ - @Bean - @ConfigurationProperties(prefix = "events") - PropertyEventConfig eventConfig() { - return new PropertyEventConfig(); - } - - @Bean - @ConfigurationProperties(prefix = "metrics") - MetricConfig metricConfig() { - return new PropertyMetricConfig(); - } - - @Bean - @ConfigurationProperties - PropertySecretConfig secretConfig() { - return new PropertySecretConfig(); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java b/platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java deleted file mode 100644 index e6babd1105..0000000000 --- a/platform/src/main/java/org/stellar/anchor/platform/HelperBeans.java +++ /dev/null @@ -1,14 +0,0 @@ -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 HelperBeans { - @Bean - public Gson gson() { - return GsonUtils.builder().create(); - } -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java index b3f8717ec7..71350dd9a7 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java @@ -9,28 +9,32 @@ 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.context.annotation.ComponentScan; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.stellar.anchor.platform.configurator.ConfigEnvironment; import org.stellar.anchor.platform.configurator.ObserverConfigManager; import org.stellar.anchor.platform.configurator.SecretManager; -@Profile("stellar-observer") @SpringBootApplication @EnableJpaRepositories(basePackages = {"org.stellar.anchor.platform.data"}) @EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) +@ComponentScan( + basePackages = { + "org.stellar.anchor.platform.component.observer", + "org.stellar.anchor.platform.component.share" + }) @EnableConfigurationProperties public class StellarObservingServer implements WebMvcConfigurer { public static ConfigurableApplicationContext start(Map environment) { SpringApplicationBuilder builder = - new SpringApplicationBuilder(StellarObservingServer.class) - .bannerMode(OFF) - .properties( - // this allows a developer to use a .env file for local development - "spring.profiles.active=stellar-observer"); + new SpringApplicationBuilder(StellarObservingServer.class).bannerMode(OFF); 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(); diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/observer/ConfigBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/observer/ConfigBeans.java new file mode 100644 index 0000000000..9cfc43da04 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/component/observer/ConfigBeans.java @@ -0,0 +1,26 @@ +package org.stellar.anchor.platform.component.observer; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.platform.config.PaymentObserverConfig; +import org.stellar.anchor.platform.configurator.ConfigManager; +import org.stellar.anchor.platform.configurator.ObserverConfigManager; + +@Configuration +public class ConfigBeans { + @Bean(name = "configManager") + ConfigManager observerConfigManager() { + return ObserverConfigManager.getInstance(); + } + + /********************************** + * Payment observer configurations + */ + + @Bean + @ConfigurationProperties(prefix = "payment-observer") + public PaymentObserverConfig paymentObserverConfig() { + return new PaymentObserverConfig(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/PaymentObserverBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java similarity index 83% rename from platform/src/main/java/org/stellar/anchor/platform/PaymentObserverBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java index edecf68439..1c1a7abada 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PaymentObserverBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java @@ -1,19 +1,16 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.observer; import java.util.List; import java.util.stream.Collectors; import lombok.SneakyThrows; 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; import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.platform.config.PaymentObserverConfig; -import org.stellar.anchor.platform.data.PaymentObservingAccountRepo; import org.stellar.anchor.platform.observer.PaymentListener; -import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountStore; import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.platform.observer.stellar.StellarPaymentObserver; import org.stellar.anchor.platform.observer.stellar.StellarPaymentStreamerCursorStore; @@ -21,7 +18,6 @@ @Configuration public class PaymentObserverBeans { @Bean - @Profile("stellar-observer") @SneakyThrows public StellarPaymentObserver stellarPaymentObserver( AssetService assetService, @@ -82,15 +78,4 @@ public StellarPaymentObserver stellarPaymentObserver( stellarPaymentObserver.start(); return stellarPaymentObserver; } - - @Bean - public PaymentObservingAccountsManager observingAccounts( - PaymentObservingAccountStore paymentObservingAccountStore) { - return new PaymentObservingAccountsManager(paymentObservingAccountStore); - } - - @Bean - public PaymentObservingAccountStore observingAccountStore(PaymentObservingAccountRepo repo) { - return new PaymentObservingAccountStore(repo); - } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/CallbackApiBeans.java similarity index 92% rename from platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/sep/CallbackApiBeans.java index 9654967859..f3511fb64e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/CallbackApiBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/CallbackApiBeans.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.sep; import com.google.gson.Gson; import java.security.KeyManagementException; @@ -9,6 +9,7 @@ import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.stellar.anchor.api.callback.CustomerIntegration; @@ -23,6 +24,7 @@ import org.stellar.anchor.platform.callback.RestRateIntegration; import org.stellar.anchor.platform.callback.RestUniqueAddressIntegration; import org.stellar.anchor.platform.config.CallbackApiConfig; +import org.stellar.anchor.platform.config.PropertySecretConfig; @Configuration public class CallbackApiBeans { @@ -44,6 +46,12 @@ public java.security.cert.X509Certificate[] getAcceptedIssuers() { } }; + @Bean + @ConfigurationProperties(prefix = "callback-api") + CallbackApiConfig callbackApiConfig(PropertySecretConfig secretConfig) { + return new CallbackApiConfig(secretConfig); + } + @Bean OkHttpClient httpClient(CallbackApiConfig callbackApiConfig) throws NoSuchAlgorithmException, KeyManagementException { diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/sep/ConfigBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/ConfigBeans.java new file mode 100644 index 0000000000..159d7ba233 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/ConfigBeans.java @@ -0,0 +1,16 @@ +package org.stellar.anchor.platform.component.sep; + +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.configurator.ConfigManager; +import org.stellar.anchor.platform.configurator.SepConfigManager; + +@Configuration +public class ConfigBeans { + @Bean(name = "configManager") + ConfigManager configManager() { + return SepConfigManager.getInstance(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/PlatformApiBeans.java similarity index 61% rename from platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/sep/PlatformApiBeans.java index 78968c3430..073fa773f1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/PlatformApiBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/PlatformApiBeans.java @@ -1,17 +1,31 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.sep; import javax.servlet.Filter; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.asset.AssetService; import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.event.EventService; 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; +import org.stellar.anchor.platform.config.PropertySecretConfig; +import org.stellar.anchor.platform.service.TransactionService; +import org.stellar.anchor.sep24.Sep24TransactionStore; +import org.stellar.anchor.sep31.Sep31TransactionStore; +import org.stellar.anchor.sep38.Sep38QuoteStore; @Configuration public class PlatformApiBeans { + @Bean + @ConfigurationProperties(prefix = "platform-api") + PlatformApiConfig platformApiConfig(PropertySecretConfig secretConfig) { + return new PlatformApiConfig(secretConfig); + } + /** * Register anchor-to-platform token filter. * @@ -45,4 +59,14 @@ public FilterRegistrationBean anchorToPlatformTokenFilter( registrationBean.addUrlPatterns("/exchange/quotes"); return registrationBean; } + + @Bean + TransactionService transactionService( + Sep24TransactionStore txn24Store, + Sep31TransactionStore txn31Store, + Sep38QuoteStore quoteStore, + AssetService assetService, + EventService eventService) { + return new TransactionService(txn24Store, txn31Store, quoteStore, assetService, eventService); + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java similarity index 83% rename from platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java index 582468e0e4..464370fbd4 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/SepServiceBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java @@ -1,8 +1,8 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.sep; -import com.google.gson.Gson; import java.io.IOException; import javax.servlet.Filter; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,9 +17,8 @@ import org.stellar.anchor.event.EventService; import org.stellar.anchor.filter.JwtTokenFilter; import org.stellar.anchor.horizon.Horizon; -import org.stellar.anchor.platform.data.*; +import org.stellar.anchor.platform.config.*; import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager; -import org.stellar.anchor.platform.service.PropertyAssetsService; import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorApi; import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf; import org.stellar.anchor.sep1.Sep1Service; @@ -35,8 +34,45 @@ /** SEP configurations */ @Configuration -public class SepServiceBeans { - public SepServiceBeans() {} +public class SepBeans { + /********************************** + * SEP configurations + */ + @Bean + @ConfigurationProperties(prefix = "sep1") + Sep1Config sep1Config() { + return new PropertySep1Config(); + } + + @Bean + @ConfigurationProperties(prefix = "sep10") + Sep10Config sep10Config(SecretConfig secretConfig) { + return new PropertySep10Config(secretConfig); + } + + @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() { + return new PropertySep31Config(); + } + + @Bean + @ConfigurationProperties(prefix = "sep38") + Sep38Config sep38Config() { + return new PropertySep38Config(); + } /** * Used by SEP-10 authentication service. @@ -68,11 +104,6 @@ public FilterRegistrationBean sep10TokenFilter(JwtService jwtService) { return registrationBean; } - @Bean - AssetService assetService(AssetsConfig assetsConfig) throws InvalidConfigException { - return new PropertyAssetsService(assetsConfig); - } - @Bean public Horizon horizon(AppConfig appConfig) { return new Horizon(appConfig); @@ -100,7 +131,6 @@ Sep12Service sep12Service(CustomerIntegration customerIntegration) { @Bean Sep24Service sep24Service( - Gson gson, AppConfig appConfig, Sep24Config sep24Config, AssetService assetService, @@ -111,11 +141,6 @@ Sep24Service sep24Service( appConfig, sep24Config, assetService, jwtService, sep24TransactionStore, eventService); } - @Bean - Sep24TransactionStore sep24TransactionStore(JdbcSep24TransactionRepo sep24TransactionRepo) { - return new JdbcSep24TransactionStore(sep24TransactionRepo); - } - @Bean Sep31DepositInfoGenerator sep31DepositInfoGenerator( Sep31Config sep31Config, @@ -155,16 +180,6 @@ Sep31Service sep31Service( eventService); } - @Bean - JdbcSep31TransactionStore sep31TransactionStore(JdbcSep31TransactionRepo txnRepo) { - return new JdbcSep31TransactionStore(txnRepo); - } - - @Bean - Sep38QuoteStore sep38QuoteStore(JdbcSep38QuoteRepo quoteRepo) { - return new JdbcSep38QuoteStore(quoteRepo); - } - @Bean Sep38Service sep38Service( Sep38Config sep38Config, diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java new file mode 100644 index 0000000000..b0970599e3 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java @@ -0,0 +1,24 @@ +package org.stellar.anchor.platform.component.share; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.asset.AssetService; +import org.stellar.anchor.config.AssetsConfig; +import org.stellar.anchor.platform.config.PropertyAssetsConfig; +import org.stellar.anchor.platform.service.PropertyAssetsService; + +@Configuration +public class AssetBeans { + @Bean + @ConfigurationProperties(prefix = "assets") + AssetsConfig assetsConfig() { + return new PropertyAssetsConfig(); + } + + @Bean + AssetService assetService(AssetsConfig assetsConfig) throws InvalidConfigException { + return new PropertyAssetsService(assetsConfig); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/DataBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/DataBeans.java new file mode 100644 index 0000000000..e0220ec9e6 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/DataBeans.java @@ -0,0 +1,38 @@ +package org.stellar.anchor.platform.component.share; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.platform.data.*; +import org.stellar.anchor.platform.observer.stellar.JdbcStellarPaymentStreamerCursorStore; +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountStore; +import org.stellar.anchor.sep24.Sep24TransactionStore; +import org.stellar.anchor.sep38.Sep38QuoteStore; + +@Configuration +public class DataBeans { + @Bean + Sep24TransactionStore sep24TransactionStore(JdbcSep24TransactionRepo sep24TransactionRepo) { + return new JdbcSep24TransactionStore(sep24TransactionRepo); + } + + @Bean + JdbcSep31TransactionStore sep31TransactionStore(JdbcSep31TransactionRepo txnRepo) { + return new JdbcSep31TransactionStore(txnRepo); + } + + @Bean + Sep38QuoteStore sep38QuoteStore(JdbcSep38QuoteRepo quoteRepo) { + return new JdbcSep38QuoteStore(quoteRepo); + } + + @Bean + JdbcStellarPaymentStreamerCursorStore stellarPaymentStreamerCursorStore( + PaymentStreamerCursorRepo paymentStreamerCursorRepo) { + return new JdbcStellarPaymentStreamerCursorStore(paymentStreamerCursorRepo); + } + + @Bean + public PaymentObservingAccountStore observingAccountStore(PaymentObservingAccountRepo repo) { + return new PaymentObservingAccountStore(repo); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java similarity index 62% rename from platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java index 42c30efd99..168b4d5873 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/EventsBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java @@ -1,19 +1,31 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.share; import static org.stellar.anchor.util.Log.errorF; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.stellar.anchor.api.exception.InvalidConfigException; -import org.stellar.anchor.event.*; +import org.stellar.anchor.event.EventService; 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; +import org.stellar.anchor.platform.service.PaymentOperationToEventListener; +import org.stellar.anchor.sep31.Sep31TransactionStore; @Configuration -public class EventsBeans { +public class EventBeans { + /********************************** + * Event configurations + */ + @Bean + @ConfigurationProperties(prefix = "events") + PropertyEventConfig eventConfig() { + return new PropertyEventConfig(); + } + @Bean public EventService eventService(PropertyEventConfig eventConfig) throws InvalidConfigException { EventService eventService = new EventService(eventConfig); @@ -24,7 +36,7 @@ public EventService eventService(PropertyEventConfig eventConfig) throws Invalid switch (publisherType) { case "kafka": eventService.setEventPublisher( - new KafkaEventPublisher(eventConfig.getPublisher().getKafka())); + KafkaEventPublisher.getInstance(eventConfig.getPublisher().getKafka())); break; case "sqs": eventService.setEventPublisher(new SqsEventPublisher(eventConfig.getPublisher().getSqs())); @@ -40,4 +52,11 @@ public EventService eventService(PropertyEventConfig eventConfig) throws Invalid return eventService; } + + @Bean + public PaymentOperationToEventListener paymentOperationToEventListener( + Sep31TransactionStore transactionStore, EventService eventService) { + // TODO: Remove this bean + return new PaymentOperationToEventListener(transactionStore, eventService); + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/MetricsBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java similarity index 58% rename from platform/src/main/java/org/stellar/anchor/platform/MetricsBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java index 3f60e90302..15e2b6d68c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/MetricsBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java @@ -1,13 +1,25 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.share; +import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.stellar.anchor.config.MetricConfig; +import org.stellar.anchor.platform.config.PropertyMetricConfig; import org.stellar.anchor.platform.data.JdbcSep31TransactionRepo; import org.stellar.anchor.platform.service.MetricEmitterService; @Configuration public class MetricsBeans { + + /********************************** + * Metrics configurations + */ + @Bean + @ConfigurationProperties(prefix = "metrics") + MetricConfig metricConfig() { + return new PropertyMetricConfig(); + } + @Bean public MetricEmitterService metricService( MetricConfig metricConfig, JdbcSep31TransactionRepo sep31TransactionRepo) { diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/ObservingAccountsBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/ObservingAccountsBeans.java new file mode 100644 index 0000000000..31e90c5ac6 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/ObservingAccountsBeans.java @@ -0,0 +1,15 @@ +package org.stellar.anchor.platform.component.share; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountStore; +import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager; + +@Configuration +public class ObservingAccountsBeans { + @Bean + public PaymentObservingAccountsManager paymentObservingAccountsManager( + PaymentObservingAccountStore paymentObservingAccountStore) { + return new PaymentObservingAccountsManager(paymentObservingAccountStore); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/RequestLoggerBeans.java similarity index 92% rename from platform/src/main/java/org/stellar/anchor/platform/RequestLoggerBeans.java rename to platform/src/main/java/org/stellar/anchor/platform/component/share/RequestLoggerBeans.java index fc67d1ac4a..6e9511cbea 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/RequestLoggerBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/RequestLoggerBeans.java @@ -1,4 +1,4 @@ -package org.stellar.anchor.platform; +package org.stellar.anchor.platform.component.share; import javax.servlet.Filter; import org.springframework.boot.web.servlet.FilterRegistrationBean; diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/UtilityBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/UtilityBeans.java new file mode 100644 index 0000000000..f8623be715 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/UtilityBeans.java @@ -0,0 +1,42 @@ +package org.stellar.anchor.platform.component.share; + +import com.google.gson.Gson; +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.stellar.anchor.config.AppConfig; +import org.stellar.anchor.healthcheck.HealthCheckable; +import org.stellar.anchor.platform.config.PropertyAppConfig; +import org.stellar.anchor.platform.config.PropertySecretConfig; +import org.stellar.anchor.platform.service.HealthCheckService; +import org.stellar.anchor.util.GsonUtils; + +@Configuration +public class UtilityBeans { + @Bean + public Gson gson() { + return GsonUtils.builder().create(); + } + + @Bean + @DependsOn("configManager") + public HealthCheckService healthCheckService(List checkables) { + return new HealthCheckService(checkables); + } + + @Bean + @ConfigurationProperties(prefix = "") + AppConfig appConfig() { + return new PropertyAppConfig(); + } + + /********************************** + * Secret configurations + */ + @Bean + PropertySecretConfig secretConfig() { + return new PropertySecretConfig(); + } +} diff --git a/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java index 68f5f854ba..35c19c94a5 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ObserverConfigManager.java @@ -41,7 +41,5 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { copy(config, "payment_observer.context_path", "server.servlet.context-path"); copy(config, "payment_observer.port", "server.port"); set("spring.mvc.converters.preferred-json-mapper", "gson"); - // set("spring.config.import", "optional:classpath:example.env[.properties]"); - set("spring.profiles.active", "stellar-observer"); } } 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 15daf721c5..f0ed1d5e15 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,5 @@ 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.*; @@ -12,7 +11,6 @@ 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 105452734e..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 @@ -4,7 +4,6 @@ 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.*; @@ -25,7 +24,6 @@ @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 13f5926777..23aa9b741b 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,7 +8,6 @@ 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.*; @@ -22,7 +21,6 @@ @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 e1d21b4294..1aafa233a2 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,5 @@ package org.stellar.anchor.platform.controller; -import org.springframework.context.annotation.Profile; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -14,7 +13,6 @@ @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 4f515a4017..385a94b4dc 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,7 +9,6 @@ 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; @@ -28,7 +27,6 @@ @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 c627365ad8..1484694e2a 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 @@ -6,7 +6,6 @@ import javax.servlet.http.HttpServletRequest; import lombok.Data; -import org.springframework.context.annotation.Profile; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; @@ -23,7 +22,6 @@ @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 83d0328004..948baec9b3 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,7 +8,6 @@ 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.*; @@ -24,10 +23,9 @@ @CrossOrigin(origins = "*") @RequestMapping("/sep38") @ConditionalOnAllSepsEnabled(seps = {"sep38"}) -@Profile("default") public class Sep38Controller { private final Sep38Service sep38Service; - private static final Gson gson = GsonUtils.builder().create(); + private static final Gson gson = GsonUtils.getInstance(); public Sep38Controller(Sep38Service sep38Service) { this.sep38Service = sep38Service; 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 4969e5884b..a18c125f7c 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 @@ -16,11 +16,13 @@ import org.stellar.anchor.platform.config.KafkaConfig; import org.stellar.anchor.util.Log; +/** To avoid jmx conflict, this class uses the singleton design pattern. */ @NoArgsConstructor public class KafkaEventPublisher implements EventPublisher { + private static KafkaEventPublisher kafkaEventPublisher; Producer producer; - public KafkaEventPublisher(KafkaConfig kafkaConfig) { + private KafkaEventPublisher(KafkaConfig kafkaConfig) { Log.debugF("kafkaConfig: {}", kafkaConfig); Properties props = new Properties(); props.put(BOOTSTRAP_SERVERS_CONFIG, kafkaConfig.getBootstrapServer()); @@ -34,6 +36,14 @@ public KafkaEventPublisher(KafkaConfig kafkaConfig) { createPublisher(props); } + public static KafkaEventPublisher getInstance(KafkaConfig kafkaConfig) { + if (kafkaEventPublisher == null) { + kafkaEventPublisher = new KafkaEventPublisher(kafkaConfig); + } + + return kafkaEventPublisher; + } + protected void createPublisher(Properties props) { this.producer = new KafkaProducer<>(props); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java index 96cecd39fe..b43b518eaa 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/JdbcStellarPaymentStreamerCursorStore.java @@ -3,15 +3,13 @@ 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; import org.stellar.anchor.platform.data.PaymentStreamerCursorRepo; -@Component public class JdbcStellarPaymentStreamerCursorStore implements StellarPaymentStreamerCursorStore { private final PaymentStreamerCursorRepo repo; - JdbcStellarPaymentStreamerCursorStore(PaymentStreamerCursorRepo repo) { + public JdbcStellarPaymentStreamerCursorStore(PaymentStreamerCursorRepo repo) { this.repo = repo; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java index 9055af4d20..387df3495e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserver.java @@ -35,6 +35,7 @@ import org.stellar.anchor.platform.observer.PaymentListener; import org.stellar.anchor.util.ExponentialBackoffTimer; import org.stellar.anchor.util.Log; +import org.stellar.anchor.util.StringHelper; import org.stellar.sdk.Server; import org.stellar.sdk.requests.EventListener; import org.stellar.sdk.requests.PaymentsRequestBuilder; @@ -250,20 +251,35 @@ void checkStatus() { } /** - * 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. + * fetchStreamingCursor will gather a starting cursor for the streamer. If there is no cursor + * stored in the database, the method will fetch the most recent cursor from the Stellar network + * and return the most recent result. + * + *

Otherwise, returns max(most recent cursor - MAX_RESULTS, last stored cursor) * * @return the starting point to start streaming from. */ String fetchStreamingCursor() { // Use database value, if any. - String lastToken = paymentStreamerCursorStore.load(); - if (lastToken != null) { - return lastToken; + String strLastStored = paymentStreamerCursorStore.load(); + Log.debug("Fetching latest cursor from Stellar network"); + String strLatest = fetchLatestCursor(); + Log.infoF("The latest cursor fetched from Stellar network is: {}", strLatest); + if (StringHelper.isEmpty(strLastStored)) { + return strLatest; + } else { + long lastStored = Long.parseLong(strLastStored); + long latest = Long.parseLong(strLatest); + if (lastStored >= latest) { + // lastStoredCursor is stale because it is greater than the latest cursor + return String.valueOf(latest); + } else { + return String.valueOf(Math.max(lastStored, latest - MAX_RESULTS)); + } } + } - // Otherwise, fetch the latest value from the network. + String fetchLatestCursor() { Page pageOpResponse; try { pageOpResponse = 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 0a63dbeaa5..abff830e8e 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 @@ -4,14 +4,10 @@ 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 { final HealthCheckProcessor processor; 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 2a7422efb4..cd6c0c9175 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 @@ -12,8 +12,6 @@ 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.EventPublishException; import org.stellar.anchor.api.exception.SepException; @@ -31,13 +29,11 @@ import org.stellar.anchor.util.MemoHelper; import org.stellar.sdk.xdr.MemoType; -@Component -@Profile("stellar-observer") public class PaymentOperationToEventListener implements PaymentListener { final Sep31TransactionStore transactionStore; final EventService eventService; - PaymentOperationToEventListener( + public PaymentOperationToEventListener( Sep31TransactionStore transactionStore, EventService eventService) { this.transactionStore = transactionStore; this.eventService = eventService; 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 895757d31e..96ce3550cb 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 @@ -11,7 +11,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import org.springframework.stereotype.Service; import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.platform.GetTransactionResponse; import org.stellar.anchor.api.platform.PatchTransactionRequest; @@ -39,7 +38,6 @@ import org.stellar.anchor.util.SepHelper; import org.stellar.anchor.util.StringHelper; -@Service public class TransactionService { private final Sep38QuoteStore quoteStore; private final Sep31TransactionStore txn31Store; @@ -52,7 +50,7 @@ static boolean isStatusError(String status) { .contains(status); } - TransactionService( + public TransactionService( Sep24TransactionStore txn24Store, Sep31TransactionStore txn31Store, Sep38QuoteStore quoteStore, diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt similarity index 97% rename from platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt rename to platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt index 7b48407ee2..f197bf2646 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObserverBeansTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt @@ -13,6 +13,7 @@ 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.component.observer.PaymentObserverBeans import org.stellar.anchor.platform.config.PaymentObserverConfig import org.stellar.anchor.platform.config.PaymentObserverConfig.StellarPaymentObserverConfig import org.stellar.anchor.platform.observer.PaymentListener @@ -20,7 +21,7 @@ import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountStore import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager import org.stellar.anchor.platform.observer.stellar.StellarPaymentStreamerCursorStore -class PaymentObserverBeansTest { +class PaymentObservingAccountsBeansTest { @MockK private lateinit var paymentStreamerCursorStore: StellarPaymentStreamerCursorStore @MockK private lateinit var paymentObservingAccountStore: PaymentObservingAccountStore diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt index a40bb34072..e985c04266 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/observer/stellar/StellarPaymentObserverTest.kt @@ -48,16 +48,19 @@ class StellarPaymentObserverTest { // 1 - If there is a stored cursor, we'll use that. every { paymentStreamerCursorStore.load() } returns "123" var stellarObserver = - StellarPaymentObserver( - TEST_HORIZON_URI, - stellarPaymentObserverConfig, - null, - paymentObservingAccountsManager, - paymentStreamerCursorStore + spyk( + StellarPaymentObserver( + TEST_HORIZON_URI, + stellarPaymentObserverConfig, + null, + paymentObservingAccountsManager, + paymentStreamerCursorStore + ) ) + every { stellarObserver.fetchLatestCursor() } returns "1000" var gotCursor = stellarObserver.fetchStreamingCursor() - assertEquals("123", gotCursor) + assertEquals("800", gotCursor) verify(exactly = 1) { paymentStreamerCursorStore.load() } // 2 - If there is no stored constructor, we will fall back to fetching a result from the From 4bc916e0b4fdc8ca033474827b8c7e3093c2c49b Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Sun, 25 Dec 2022 22:21:59 +0800 Subject: [PATCH 0069/1439] Disable Kafka client if events.publisher.type is not KAFKA or event service is not enabled (#691) --- .../platform/component/share/EventBeans.java | 37 ++++++++++--------- .../platform/event/KafkaEventPublisher.java | 4 ++ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java index 168b4d5873..12e3dae67f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/EventBeans.java @@ -31,23 +31,26 @@ public EventService eventService(PropertyEventConfig eventConfig) throws Invalid EventService eventService = new EventService(eventConfig); if (!eventConfig.isEnabled()) { eventService.setEventPublisher(new NoopEventPublisher()); - } - String publisherType = eventConfig.getPublisher().getType(); - switch (publisherType) { - case "kafka": - eventService.setEventPublisher( - KafkaEventPublisher.getInstance(eventConfig.getPublisher().getKafka())); - break; - case "sqs": - eventService.setEventPublisher(new SqsEventPublisher(eventConfig.getPublisher().getSqs())); - break; - case "msk": - eventService.setEventPublisher(new MskEventPublisher(eventConfig.getPublisher().getMsk())); - break; - default: - errorF("Invalid event publisher: {}", publisherType); - throw new InvalidConfigException( - String.format("Invalid event publisher: %s", publisherType)); + } else { + String publisherType = eventConfig.getPublisher().getType(); + switch (publisherType) { + case "kafka": + eventService.setEventPublisher( + KafkaEventPublisher.getInstance(eventConfig.getPublisher().getKafka())); + break; + case "sqs": + eventService.setEventPublisher( + new SqsEventPublisher(eventConfig.getPublisher().getSqs())); + break; + case "msk": + eventService.setEventPublisher( + new MskEventPublisher(eventConfig.getPublisher().getMsk())); + break; + default: + 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/event/KafkaEventPublisher.java b/platform/src/main/java/org/stellar/anchor/platform/event/KafkaEventPublisher.java index a18c125f7c..da23852e16 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 @@ -32,6 +32,10 @@ private KafkaEventPublisher(KafkaConfig kafkaConfig) { props.put(RETRIES_CONFIG, kafkaConfig.getRetries()); props.put(LINGER_MS_CONFIG, kafkaConfig.getLingerMs()); props.put(BATCH_SIZE_CONFIG, kafkaConfig.getBatchSize()); + // reconnect back-off is 1 second + props.put(RECONNECT_BACKOFF_MS_CONFIG, "1000"); + // maximum reconnect back-off is 10 seconds + props.put(RECONNECT_BACKOFF_MAX_MS_CONFIG, "10000"); createPublisher(props); } From be0f16f16bb22f4cd99374eb1338b93468d3c2bc Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Sun, 25 Dec 2022 22:43:14 +0800 Subject: [PATCH 0070/1439] platform: [ANCHOR-108] Add interactive URL configuration and InteractiveUrlConstructor classes (#692) * Add interactive URL configuration and InteractiveUrlConstructor classes --- .... Platform Server - ServiceRunner.run.xml} | 0 core/build.gradle.kts | 1 + .../stellar/anchor/config/Sep24Config.java | 2 - .../sep24/InteractiveUrlConstructor.java | 12 +++ .../org/stellar/anchor/sep24/Sep24Helper.java | 3 +- .../stellar/anchor/sep24/Sep24Service.java | 66 ++------------- .../org/stellar/anchor/util/StringHelper.java | 17 ++++ .../stellar/anchor/sep24/Sep24ServiceTest.kt | 75 +++++++++++------ gradle/libs.versions.toml | 2 + platform/build.gradle.kts | 2 + .../platform/component/sep/SepBeans.java | 22 ++++- .../platform/config/PropertySep24Config.java | 65 +++++++++++---- .../SimpleInteractiveUrlConstructor.java | 67 +++++++++++++++ .../config/anchor-config-default-values.yaml | 19 +++-- .../config/anchor-config-schema-v1.yaml | 4 +- .../anchor/platform/config/Sep24ConfigTest.kt | 31 ++++--- .../SimpleInteractiveUrlConstructorTest.kt | 81 +++++++++++++++++++ .../stellar/anchor/platform/service/test.json | 0 18 files changed, 349 insertions(+), 120 deletions(-) rename .run/{1. Platform Server.run.xml => 1. Platform Server - ServiceRunner.run.xml} (100%) create mode 100644 core/src/main/java/org/stellar/anchor/sep24/InteractiveUrlConstructor.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/test.json diff --git a/.run/1. Platform Server.run.xml b/.run/1. Platform Server - ServiceRunner.run.xml similarity index 100% rename from .run/1. Platform Server.run.xml rename to .run/1. Platform Server - ServiceRunner.run.xml diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 6a0f545034..059db7a4d2 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(libs.javax.transaction.api) implementation(libs.commons.beanutils) + implementation(libs.commons.text) implementation(libs.apache.commons.lang3) implementation(libs.log4j2.core) implementation(libs.httpclient) diff --git a/core/src/main/java/org/stellar/anchor/config/Sep24Config.java b/core/src/main/java/org/stellar/anchor/config/Sep24Config.java index 6262c29e18..b396ad8768 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep24Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep24Config.java @@ -5,6 +5,4 @@ public interface Sep24Config { boolean isEnabled(); int getInteractiveJwtExpiration(); - - String getInteractiveUrl(); } diff --git a/core/src/main/java/org/stellar/anchor/sep24/InteractiveUrlConstructor.java b/core/src/main/java/org/stellar/anchor/sep24/InteractiveUrlConstructor.java new file mode 100644 index 0000000000..9c584db604 --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/sep24/InteractiveUrlConstructor.java @@ -0,0 +1,12 @@ +package org.stellar.anchor.sep24; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.HashMap; +import org.stellar.anchor.auth.JwtToken; + +public abstract class InteractiveUrlConstructor { + public abstract String construct( + JwtToken token, Sep24Transaction txn, String lang, HashMap sep9Fields) + throws URISyntaxException, MalformedURLException; +} diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java index 8c6190ac71..91c7597bd9 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java @@ -61,7 +61,8 @@ public static String constructMoreInfoUrl( txn.getTransactionId(), txn.getClientDomain()); - URI uri = new URI(sep24Config.getInteractiveUrl()); + // TODO: Fix the more_info_url + URI uri = new URI("https://www.stellar.org"); URIBuilder builder = new URIBuilder() diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 4781c9e6cd..54abf76475 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -16,11 +16,9 @@ import java.io.IOException; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; import java.util.*; -import org.apache.http.client.utils.URIBuilder; import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep24.*; @@ -40,6 +38,7 @@ public class Sep24Service { final JwtService jwtService; final Sep24TransactionStore txnStore; final EventService eventService; + final InteractiveUrlConstructor interactiveUrlConstructor; public Sep24Service( AppConfig appConfig, @@ -47,7 +46,8 @@ public Sep24Service( AssetService assetService, JwtService jwtService, Sep24TransactionStore txnStore, - EventService eventService) { + EventService eventService, + InteractiveUrlConstructor interactiveUrlConstructor) { debug("appConfig:", appConfig); debug("sep24Config:", sep24Config); this.appConfig = appConfig; @@ -56,6 +56,7 @@ public Sep24Service( this.jwtService = jwtService; this.txnStore = txnStore; this.eventService = eventService; + this.interactiveUrlConstructor = interactiveUrlConstructor; info("Sep24Service initialized."); } @@ -153,14 +154,8 @@ public InteractiveTransactionResponse withdraw( debug("Transaction details:", txn); return new InteractiveTransactionResponse( "interactive_customer_info_needed", - constructInteractiveUrl( - "withdraw", - buildRedirectJwtToken(fullRequestUrl, token, txn), - sep9Fields, - lang, - assetCode, - strAmount, - txn.getTransactionId()), + interactiveUrlConstructor.construct( + buildRedirectJwtToken(fullRequestUrl, token, txn), txn, lang, sep9Fields), txn.getTransactionId()); } @@ -274,14 +269,8 @@ public InteractiveTransactionResponse deposit( return new InteractiveTransactionResponse( "interactive_customer_info_needed", - constructInteractiveUrl( - "deposit", - buildRedirectJwtToken(fullRequestUrl, token, txn), - sep9Fields, - lang, - assetCode, - strAmount, - txn.getTransactionId()), + interactiveUrlConstructor.construct( + buildRedirectJwtToken(fullRequestUrl, token, txn), txn, lang, sep9Fields), txn.getTransactionId()); } @@ -415,43 +404,4 @@ JwtToken buildRedirectJwtToken(String fullRequestUrl, JwtToken token, Sep24Trans List listAllAssets() { return this.assetService.listAllAssets(); } - - String constructInteractiveUrl( - String op, - JwtToken token, - HashMap sep9Fields, - String lang, - String assetCode, - String amount, - String txnId) - throws URISyntaxException, MalformedURLException { - - String interactiveUrlHostname = sep24Config.getInteractiveUrl(); - - URI uri = new URI(interactiveUrlHostname); - - URIBuilder builder = - new URIBuilder() - .setScheme(uri.getScheme()) - .setHost(uri.getHost()) - .setPort(uri.getPort()) - .setPath(uri.getPath()) - .addParameter("operation", op) - .addParameter("asset_code", assetCode) - .addParameter("transaction_id", txnId) - .addParameter("token", jwtService.encode(token)); - - if (amount != null) { - builder.addParameter("amount", amount); - } - - if (lang != null) { - builder.addParameter("lang", lang); - } - - // Add Sep9 fields to url - sep9Fields.forEach(builder::addParameter); - - return builder.build().toURL().toString(); - } } 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 231a3b5052..0f361992b9 100644 --- a/core/src/main/java/org/stellar/anchor/util/StringHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/StringHelper.java @@ -1,6 +1,7 @@ package org.stellar.anchor.util; import java.util.Objects; +import org.apache.commons.text.WordUtils; public class StringHelper { public static boolean isEmpty(String value) { @@ -25,6 +26,22 @@ public static String camelToSnake(String camel) { .toLowerCase(); } + /** + * Convert snake_case string to camelCase string. + * + * @param snakeWord the camel case string. + * @return under-scored snake-case string + */ + public static String snakeToCamelCase(String snakeWord) { + String[] camelWords = snakeWord.replaceAll("_", " ").split(" "); + if (camelWords.length == 0) return ""; + StringBuffer sb = new StringBuffer(camelWords[0]); + for (int i = 1; i < camelWords.length; i++) { + sb.append(WordUtils.capitalize(camelWords[i])); + } + return sb.toString(); + } + public static String toPosixForm(String camel) { return camel .replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2") 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 ef23d1f6cb..5293d0563c 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -55,12 +55,14 @@ internal class Sep24ServiceTest { @MockK(relaxed = true) lateinit var secretConfig: SecretConfig @MockK(relaxed = true) lateinit var sep24Config: Sep24Config @MockK(relaxed = true) lateinit var eventService: EventService - @MockK(relaxed = true) private lateinit var txnStore: Sep24TransactionStore + @MockK(relaxed = true) lateinit var txnStore: Sep24TransactionStore + @MockK(relaxed = true) lateinit var interactiveUrlConstructor: InteractiveUrlConstructor private val assetService: AssetService = ResourceJsonAssetService("test_assets.json") private lateinit var jwtService: JwtService private lateinit var sep24Service: Sep24Service + private lateinit var createdJwt: JwtToken private val gson = GsonUtils.getInstance() @@ -70,16 +72,25 @@ internal class Sep24ServiceTest { every { appConfig.stellarNetworkPassphrase } returns TestConstants.TEST_NETWORK_PASS_PHRASE every { appConfig.hostUrl } returns TestConstants.TEST_HOST_URL every { secretConfig.sep10JwtSecretKey } returns TestConstants.TEST_JWT_SECRET - - every { sep24Config.interactiveUrl } returns TEST_SEP24_INTERACTIVE_URL every { sep24Config.interactiveJwtExpiration } returns 1000 - every { txnStore.newInstance() } returns PojoSep24Transaction() jwtService = spyk(JwtService(secretConfig)) + createdJwt = createJwtToken() + val strToken = jwtService.encode(createdJwt) + every { interactiveUrlConstructor.construct(any(), any(), any(), any()) } returns + "${TEST_SEP24_INTERACTIVE_URL}?lang=en&token=$strToken" sep24Service = - Sep24Service(appConfig, sep24Config, assetService, jwtService, txnStore, eventService) + Sep24Service( + appConfig, + sep24Config, + assetService, + jwtService, + txnStore, + eventService, + interactiveUrlConstructor + ) } @AfterEach @@ -94,8 +105,8 @@ internal class Sep24ServiceTest { every { txnStore.save(capture(slotTxn)) } returns null - var response = - sep24Service.withdraw("/sep24/withdraw", createJwtToken(), createTestTransactionRequest()) + val response = + sep24Service.withdraw("/sep24/withdraw", createdJwt, createTestTransactionRequest()) verify(exactly = 1) { txnStore.save(any()) } @@ -116,23 +127,30 @@ internal class Sep24ServiceTest { assertEquals(slotTxn.captured.amountIn, "123.4") assertEquals(slotTxn.captured.amountOut, "123.4") - var params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) - var tokenStrings = params.filter { pair -> pair.name.equals("token") } + val params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) + val tokenStrings = params.filter { pair -> pair.name.equals("token") } assertEquals(tokenStrings.size, 1) - var tokenString = tokenStrings[0].value - var decodedToken = jwtService.decode(tokenString) + val tokenString = tokenStrings[0].value + val decodedToken = jwtService.decode(tokenString) assertEquals(decodedToken.sub, TEST_ACCOUNT) assertEquals(decodedToken.clientDomain, TEST_CLIENT_DOMAIN) + } - // Now test with a memo - response = - sep24Service.withdraw("/sep24/withdraw", createJwtWithMemo(), createTestTransactionRequest()) + @Test + fun `test withdraw with token memo`() { + createdJwt = createJwtWithMemo() + val strToken = jwtService.encode(createdJwt) + every { interactiveUrlConstructor.construct(any(), any(), any(), any()) } returns + "${TEST_SEP24_INTERACTIVE_URL}?lang=en&token=$strToken" + + val response = + sep24Service.withdraw("/sep24/withdraw", createdJwt, createTestTransactionRequest()) - params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) - tokenStrings = params.filter { pair -> pair.name.equals("token") } + val params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) + val tokenStrings = params.filter { pair -> pair.name.equals("token") } assertEquals(tokenStrings.size, 1) - tokenString = tokenStrings[0].value - decodedToken = jwtService.decode(tokenString) + val tokenString = tokenStrings[0].value + val decodedToken = jwtService.decode(tokenString) assertEquals( "$TEST_ACCOUNT:$TEST_MEMO", decodedToken.sub, @@ -207,7 +225,7 @@ internal class Sep24ServiceTest { val request = createTestTransactionRequest() request["claimable_balance_supported"] = claimable_balance_supported - var response = sep24Service.deposit("/sep24/deposit", createJwtToken(), request) + var response = sep24Service.deposit("/sep24/deposit", createdJwt, request) verify(exactly = 1) { txnStore.save(any()) } @@ -226,8 +244,18 @@ internal class Sep24ServiceTest { assertEquals(slotTxn.captured.amountOut, "123.4") // Now test with a memo - response = - sep24Service.withdraw("/sep24/withdraw", createJwtWithMemo(), createTestTransactionRequest()) + + } + + @Test + fun `test deposit with token memo`() { + createdJwt = createJwtWithMemo() + val strToken = jwtService.encode(createdJwt) + every { interactiveUrlConstructor.construct(any(), any(), any(), any()) } returns + "${TEST_SEP24_INTERACTIVE_URL}?lang=en&token=$strToken" + + val response = + sep24Service.withdraw("/sep24/withdraw", createdJwt, createTestTransactionRequest()) val params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) val tokenStrings = params.filter { pair -> pair.name.equals("token") } @@ -435,11 +463,8 @@ internal class Sep24ServiceTest { val request = createTestTransactionRequest() request["lang"] = "en" - var response = sep24Service.withdraw("/sep24/withdraw", createJwtToken(), request) + val response = sep24Service.withdraw("/sep24/withdraw", createJwtToken(), request) assertTrue(response.url.indexOf("lang=en") != -1) - request.remove("lang") - response = sep24Service.withdraw("/sep24/withdraw", createJwtToken(), request) - assertTrue(response.url.indexOf("lang=en-US") != -1) } private fun createJwtToken(): JwtToken { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4984f74a85..9036bf1147 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ commons-cli = "1.5.0" commons-codec = "1.15" commons-io = "2.11.0" commons-validator = "1.7" +commons-text = "1.10.0" findbugs-jsr305 = "3.0.2" flyway-core = "8.5.13" google-gson = "2.8.9" @@ -58,6 +59,7 @@ 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-text = { module = "org.apache.commons:commons-text", version.ref = "commons-text" } 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" } diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index 7828dddf0b..9fb3efd4a8 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -29,8 +29,10 @@ dependencies { implementation(libs.abdera) implementation(libs.aws.rds) implementation(libs.aws.iam.auth) + implementation(libs.commons.beanutils) implementation(libs.commons.cli) implementation(libs.commons.io) + implementation(libs.commons.text) implementation(libs.flyway.core) implementation(libs.hibernate.types) implementation(libs.google.gson) diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java index 464370fbd4..fccbfb8342 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java @@ -21,9 +21,11 @@ import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorApi; import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf; +import org.stellar.anchor.platform.service.SimpleInteractiveUrlConstructor; import org.stellar.anchor.sep1.Sep1Service; import org.stellar.anchor.sep10.Sep10Service; import org.stellar.anchor.sep12.Sep12Service; +import org.stellar.anchor.sep24.InteractiveUrlConstructor; import org.stellar.anchor.sep24.Sep24Service; import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.sep31.Sep31DepositInfoGenerator; @@ -58,7 +60,7 @@ Sep12Config sep12Config(CallbackApiConfig callbackApiConfig) { @Bean @ConfigurationProperties(prefix = "sep24") - Sep24Config sep24Config() { + PropertySep24Config sep24Config() { return new PropertySep24Config(); } @@ -136,9 +138,23 @@ Sep24Service sep24Service( AssetService assetService, JwtService jwtService, Sep24TransactionStore sep24TransactionStore, - EventService eventService) { + EventService eventService, + InteractiveUrlConstructor interactiveUrlConstructor) { return new Sep24Service( - appConfig, sep24Config, assetService, jwtService, sep24TransactionStore, eventService); + appConfig, + sep24Config, + assetService, + jwtService, + sep24TransactionStore, + eventService, + interactiveUrlConstructor); + } + + @Bean + InteractiveUrlConstructor interactiveUrlConstructor( + PropertySep24Config sep24Config, JwtService jwtService) { + return new SimpleInteractiveUrlConstructor( + sep24Config.getInteractiveUrl().getSimple(), jwtService); } @Bean 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 4372c16e2f..6fbad4d88c 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,8 +1,9 @@ package org.stellar.anchor.platform.config; -import static org.stellar.anchor.util.StringHelper.isEmpty; - +import java.util.List; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -13,7 +14,23 @@ public class PropertySep24Config implements Sep24Config, Validator { boolean enabled; int interactiveJwtExpiration; - String interactiveUrl; + InteractiveUrlConfig interactiveUrl; + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class InteractiveUrlConfig { + String type; + SimpleInteractiveUrlConfig simple; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class SimpleInteractiveUrlConfig { + String baseUrl; + List txnFields; + } @Override public boolean supports(@NotNull Class clazz) { @@ -23,17 +40,6 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { PropertySep24Config config = (PropertySep24Config) target; - if (isEmpty(config.getInteractiveUrl())) { - errors.rejectValue( - "interactiveUrl", - "sep24-interactive-url-invalid", - "sep24.interactive_url is not defined."); - } else if (!NetUtil.isUrlValid(config.getInteractiveUrl())) { - errors.rejectValue( - "interactiveUrl", - "sep24-interactive-url-invalid", - String.format("sep24.interactive_url:%s is not valid", config.getInteractiveUrl())); - } if (config.getInteractiveJwtExpiration() <= 0) { errors.rejectValue( @@ -43,5 +49,36 @@ public void validate(@NotNull Object target, @NotNull Errors errors) { "sep24.interactive_jwt_expiration:%s is not valid", config.getInteractiveJwtExpiration())); } + + if (config.interactiveUrl == null) { + errors.rejectValue( + "interactiveUrl", + "sep24-interactive-url-invalid", + "sep24.interactive_url is not defined."); + } else { + if ("simple".equals(config.interactiveUrl.getType())) { + if (config.interactiveUrl.getSimple() == null) { + errors.rejectValue( + "interactiveUrl", + "sep24-interactive-url-simple-not-defined", + "sep24.interactive_url.simple is not defined."); + } + if (!NetUtil.isUrlValid(config.interactiveUrl.simple.baseUrl)) { + errors.rejectValue( + "interactiveUrl", + "sep24-interactive-url-simple-base-url-not-valid", + String.format( + "sep24.interactive_url.simple.base_url:[%s] is not a valid URL.", + config.interactiveUrl.simple.baseUrl)); + } + } else { + errors.rejectValue( + "interactiveUrl", + "sep24-interactive-url-invalid-type", + String.format( + "sep24.interactive_url.type:[%s] is not supported.", + config.interactiveUrl.getType())); + } + } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java new file mode 100644 index 0000000000..65cfd9b9b1 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java @@ -0,0 +1,67 @@ +package org.stellar.anchor.platform.service; + +import static org.stellar.anchor.platform.config.PropertySep24Config.SimpleInteractiveUrlConfig; +import static org.stellar.anchor.util.StringHelper.snakeToCamelCase; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.http.client.utils.URIBuilder; +import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.auth.JwtToken; +import org.stellar.anchor.sep24.InteractiveUrlConstructor; +import org.stellar.anchor.sep24.Sep24Transaction; +import org.stellar.anchor.util.StringHelper; + +public class SimpleInteractiveUrlConstructor extends InteractiveUrlConstructor { + private SimpleInteractiveUrlConfig config; + private JwtService jwtService; + + public SimpleInteractiveUrlConstructor(SimpleInteractiveUrlConfig config, JwtService jwtService) { + this.config = config; + this.jwtService = jwtService; + } + + public String construct( + JwtToken token, Sep24Transaction txn, String lang, HashMap sep9Fields) + throws URISyntaxException, MalformedURLException { + + String baseUrl = config.getBaseUrl(); + + URI uri = new URI(baseUrl); + + URIBuilder builder = + new URIBuilder() + .setScheme(uri.getScheme()) + .setHost(uri.getHost()) + .setPort(uri.getPort()) + .setPath(uri.getPath()) + .addParameter("transaction_id", txn.getTransactionId()) + .addParameter("token", jwtService.encode(token)); + + // Add lang field + if (lang != null) { + builder.addParameter("lang", lang); + } + + // Add Sep9 fields + sep9Fields.forEach(builder::addParameter); + + // Add fields defined in txnFields + for (String field : config.getTxnFields()) { + try { + field = snakeToCamelCase(field); + String value = BeanUtils.getProperty(txn, snakeToCamelCase(field)); + if (!StringHelper.isEmpty((value))) { + builder.addParameter(field, value); + } + } catch (Exception e) { + // give up + } + } + + return builder.build().toURL().toString(); + } +} 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 f5a4043a8c..06efefdb65 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -278,7 +278,6 @@ sep12: enabled: false sep24: - ## SEP24 in experimental. ## @param: enabled @@ -298,10 +297,20 @@ sep24: 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 + # Configures the interactive URL where the platform server will redirect to start the SEP-24 interactive flow. + # If the type is `simple`, sep24.interactive_url.simple will be read to construct the url. + # base_uri: is the base_uri used to construct the interactive url + # txn_fields: is the list of transaction fields that will be read to the interactive url as query parameters + # In addition to the txn_fields, the following fields are also added to the query parameters. + # `transaction_id`: the transaction ID + # `token`: the JWT token + # `lang`: the lang request parameter and + # any defined SEP-9 fields fromt he request parameters. + interactive_url: + type: simple + simple: + base_url: http://localhost:8080/sep24/interactive + txn_fields: status, kind, amount_in, amount_in_asset sep31: 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 676b7bfb2e..29dad214fe 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -70,7 +70,9 @@ sep10.require_known_omnibus_account: sep12.enabled: sep24.enabled: sep24.interactive_jwt_expiration: -sep24.interactive_url: +sep24.interactive_url.simple.base_url: +sep24.interactive_url.simple.txn_fields: +sep24.interactive_url.type: sep31.deposit_info_generator_type: sep31.enabled: sep31.payment_type: diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt index c12b14e856..3f55430d17 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt @@ -8,6 +8,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import org.springframework.validation.BindException import org.springframework.validation.Errors +import org.stellar.anchor.platform.config.PropertySep24Config.InteractiveUrlConfig class Sep24ConfigTest { lateinit var config: PropertySep24Config @@ -18,32 +19,40 @@ class Sep24ConfigTest { config = PropertySep24Config() config.enabled = true errors = BindException(config, "config") + config.interactiveJwtExpiration = 1200 + config.interactiveUrl = + InteractiveUrlConfig( + "simple", + PropertySep24Config.SimpleInteractiveUrlConfig("https://www.stellar.org", listOf("")) + ) } @Test fun `test valid sep24 configuration`() { - config.interactiveUrl = "https://www.stellar.org" - config.interactiveJwtExpiration = 1200 - config.validate(config, errors) assertFalse(errors.hasErrors()) } @ParameterizedTest - @ValueSource(strings = ["", "123", "http://abc .com"]) - fun `test bad interactive url`(url: String) { - config.interactiveUrl = url + @ValueSource(ints = [-1, Integer.MIN_VALUE, 0]) + fun `test bad interactive jwt expiration`(expiration: Int) { + config.interactiveJwtExpiration = expiration + config.validate(config, errors) assertTrue(errors.hasErrors()) - assertErrorCode(errors, "sep24-interactive-url-invalid") + assertErrorCode(errors, "sep24-interactive-jwt-expiration-invalid") } @ParameterizedTest - @ValueSource(ints = [-1, Integer.MIN_VALUE, 0]) - fun `test bad interactive jwt expiration`(expiration: Int) { - config.setInteractiveJwtExpiration(expiration) + @ValueSource(strings = ["httpss://www.stellar.org"]) + fun `test bad url`(url: String) { + config.interactiveUrl = + InteractiveUrlConfig( + "simple", + PropertySep24Config.SimpleInteractiveUrlConfig(url, listOf("")) + ) + config.validate(config, errors) assertTrue(errors.hasErrors()) - assertErrorCode(errors, "sep24-interactive-url-invalid") } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt new file mode 100644 index 0000000000..fd8160d6ca --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt @@ -0,0 +1,81 @@ +package org.stellar.anchor.platform.service + +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.anchor.auth.JwtService +import org.stellar.anchor.auth.JwtToken +import org.stellar.anchor.platform.config.PropertySep24Config +import org.stellar.anchor.platform.data.JdbcSep24Transaction +import org.stellar.anchor.util.GsonUtils + +@Suppress("UNCHECKED_CAST") +class SimpleInteractiveUrlConstructorTest { + companion object { + private val gson = GsonUtils.getInstance() + } + + @MockK(relaxed = true) private lateinit var jwtService: JwtService + lateinit var jwtToken: JwtToken + lateinit var sep9Fields: HashMap<*, *> + lateinit var txn: JdbcSep24Transaction + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + + every { jwtService.encode(any()) } returns "mock_token" + + jwtToken = JwtToken() + sep9Fields = gson.fromJson(sep9FieldsJson, HashMap::class.java) + txn = gson.fromJson(txnJson, JdbcSep24Transaction::class.java) + } + + @Test + fun `test correct config`() { + val config = + gson.fromJson(simpleConfig, PropertySep24Config.SimpleInteractiveUrlConfig::class.java) + val constructor = SimpleInteractiveUrlConstructor(config, jwtService) + val url = constructor.construct(jwtToken, txn, "en", sep9Fields as HashMap?) + assertEquals(wantedTestUrl, url) + } +} + +private const val simpleConfig = + """ +{ + "baseUrl": "http://localhost:8080/sep24/interactive", + "txnFields": [ + "kind", + "amount_in", + "amount_in_asset", + "asset_code" + ] +} +""" + +private const val sep9FieldsJson = + """ +{ + "name": "John Doe", + "email": "john_doe@stellar.org" +} +""" + +private const val txnJson = + """ +{ + "id": "123", + "transaction_id": "txn_123", + "status": "incomplete", + "kind" : "deposit", + "amount_in": "100", + "amount_in_asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" +} +""" + +private const val wantedTestUrl = + """http://localhost:8080/sep24/interactive?transaction_id=txn_123&token=mock_token&lang=en&name=John+Doe&email=john_doe%40stellar.org&kind=deposit&amountIn=100&amountInAsset=stellar%3AUSDC%3AGDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP""" diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/test.json b/platform/src/test/kotlin/org/stellar/anchor/platform/service/test.json new file mode 100644 index 0000000000..e69de29bb2 From 0114dee3d245edb07990c55afa31ed9ed9f42726 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 26 Dec 2022 10:12:54 +0800 Subject: [PATCH 0071/1439] Merge JsonAssetService, ResourceJsonAssetService to DefaultAssetsService (#693) --- .../anchor/asset/DefaultAssetService.java | 41 +++++++++++++------ .../anchor/asset/JsonAssetService.java | 41 ------------------- .../asset/ResourceJsonAssetService.java | 11 ----- .../asset/ResourceJsonAssetServiceTest.kt | 10 +++-- .../anchor/dto/sep38/InfoResponseTest.kt | 4 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 4 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 8 ++-- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 4 +- .../platform/component/share/AssetBeans.java | 4 +- .../PaymentObservingAccountsBeansTest.kt | 6 +-- .../service/TransactionServiceTest.kt | 8 ++-- .../sep31/Sep31DepositInfoGeneratorTest.kt | 4 +- 12 files changed, 55 insertions(+), 90 deletions(-) rename platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java => core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java (51%) delete mode 100644 core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java delete mode 100644 core/src/main/java/org/stellar/anchor/asset/ResourceJsonAssetService.java diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java b/core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java similarity index 51% rename from platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java rename to core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java index fd975c380d..3dea3c1969 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PropertyAssetsService.java +++ b/core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java @@ -1,33 +1,30 @@ -package org.stellar.anchor.platform.service; +package org.stellar.anchor.asset; import static org.stellar.anchor.util.Log.error; import static org.stellar.anchor.util.Log.infoF; import com.google.gson.Gson; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import lombok.NoArgsConstructor; import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.api.exception.SepNotFoundException; 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.FileUtil; import org.stellar.anchor.util.GsonUtils; -public class PropertyAssetsService implements AssetService { +@NoArgsConstructor +public class DefaultAssetService implements AssetService { static final Gson gson = GsonUtils.getInstance(); Assets assets; - public PropertyAssetsService(AssetsConfig assetsConfig) throws InvalidConfigException { + public static DefaultAssetService fromAssetConfig(AssetsConfig assetsConfig) + throws InvalidConfigException { switch (assetsConfig.getType()) { case JSON: - String assetsJson = assetsConfig.getValue(); - assets = gson.fromJson(assetsJson, Assets.class); - if (assets == null || assets.getAssets() == null || assets.getAssets().size() == 0) { - error("Invalid asset defined. assets JSON=", assetsJson); - throw new InvalidConfigException( - "Invalid assets defined in configuration. Please check the logs for details."); - } - break; + return fromJson(assetsConfig.getValue()); case YAML: default: infoF("assets type {} is not supported", assetsConfig.getType()); @@ -36,6 +33,24 @@ public PropertyAssetsService(AssetsConfig assetsConfig) throws InvalidConfigExce } } + public static DefaultAssetService fromJson(String assetsJson) throws InvalidConfigException { + DefaultAssetService assetService = new DefaultAssetService(); + assetService.assets = gson.fromJson(assetsJson, Assets.class); + if (assetService.assets == null + || assetService.assets.getAssets() == null + || assetService.assets.getAssets().size() == 0) { + error("Invalid asset defined. assets JSON=", assetsJson); + throw new InvalidConfigException( + "Invalid assets defined in configuration. Please check the logs for details."); + } + return assetService; + } + + public static DefaultAssetService fromResource(String assetPath) + throws IOException, SepNotFoundException, InvalidConfigException { + return fromJson(FileUtil.getResourceFileAsString(assetPath)); + } + public List listAllAssets() { return new ArrayList<>(assets.getAssets()); } diff --git a/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java b/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java deleted file mode 100644 index 472357886d..0000000000 --- a/core/src/main/java/org/stellar/anchor/asset/JsonAssetService.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.stellar.anchor.asset; - -import com.google.gson.Gson; -import java.util.ArrayList; -import java.util.List; -import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.util.GsonUtils; - -public class JsonAssetService implements AssetService { - static final Gson gson = GsonUtils.getInstance(); - final Assets assets; - - public JsonAssetService(String assetJson) { - this.assets = gson.fromJson(assetJson, Assets.class); - } - - public List listAllAssets() { - return new ArrayList<>(assets.assets); - } - - public AssetInfo getAsset(String code) { - for (AssetInfo asset : assets.assets) { - 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.assets) { - if (asset.getCode().equals(code) && issuer.equals(asset.getIssuer())) { - return asset; - } - } - return null; - } -} diff --git a/core/src/main/java/org/stellar/anchor/asset/ResourceJsonAssetService.java b/core/src/main/java/org/stellar/anchor/asset/ResourceJsonAssetService.java deleted file mode 100644 index a1e86dca3c..0000000000 --- a/core/src/main/java/org/stellar/anchor/asset/ResourceJsonAssetService.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.stellar.anchor.asset; - -import java.io.IOException; -import org.stellar.anchor.api.exception.SepNotFoundException; -import org.stellar.anchor.util.FileUtil; - -public class ResourceJsonAssetService extends JsonAssetService { - public ResourceJsonAssetService(String assetPath) throws IOException, SepNotFoundException { - super(FileUtil.getResourceFileAsString(assetPath)); - } -} 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 dfa80e2036..338c45fd9c 100644 --- a/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt @@ -12,7 +12,7 @@ import org.stellar.anchor.api.exception.SepNotFoundException internal class ResourceJsonAssetServiceTest { @Test fun `test assets listing`() { - val rjas = ResourceJsonAssetService("test_assets.json") + val rjas = DefaultAssetService.fromResource("test_assets.json") assertEquals(3, rjas.assets.getAssets().size) val assets = rjas.listAllAssets() @@ -29,10 +29,12 @@ internal class ResourceJsonAssetServiceTest { @Test fun `test asset JSON file not found`() { - assertThrows { ResourceJsonAssetService("test_assets.json.bad") } + assertThrows { DefaultAssetService.fromResource("test_assets.json.bad") } - assertThrows { ResourceJsonAssetService("not_found.json") } + assertThrows { DefaultAssetService.fromResource("not_found.json") } - assertThrows { ResourceJsonAssetService("classpath:/test_assets.json") } + assertThrows { + DefaultAssetService.fromResource("classpath:/test_assets.json") + } } } 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 61a58ccb63..0f8092af5d 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 @@ -8,14 +8,14 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.stellar.anchor.api.sep.AssetInfo import org.stellar.anchor.api.sep.sep38.InfoResponse -import org.stellar.anchor.asset.ResourceJsonAssetService +import org.stellar.anchor.asset.DefaultAssetService class InfoResponseTest { private lateinit var assets: List @BeforeEach fun setUp() { - val rjas = ResourceJsonAssetService("test_assets.json") + val rjas = DefaultAssetService.fromResource("test_assets.json") assets = rjas.listAllAssets() assertEquals(3, assets.size) } 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 5293d0563c..6a9cbc920a 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -31,7 +31,7 @@ import org.stellar.anchor.api.exception.SepValidationException import org.stellar.anchor.api.sep.sep24.GetTransactionRequest import org.stellar.anchor.api.sep.sep24.GetTransactionsRequest import org.stellar.anchor.asset.AssetService -import org.stellar.anchor.asset.ResourceJsonAssetService +import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.auth.JwtService import org.stellar.anchor.auth.JwtToken import org.stellar.anchor.config.AppConfig @@ -58,7 +58,7 @@ internal class Sep24ServiceTest { @MockK(relaxed = true) lateinit var txnStore: Sep24TransactionStore @MockK(relaxed = true) lateinit var interactiveUrlConstructor: InteractiveUrlConstructor - private val assetService: AssetService = ResourceJsonAssetService("test_assets.json") + private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") private lateinit var jwtService: JwtService private lateinit var sep24Service: Sep24Service 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 72bdcf3577..38e9cfb825 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -31,7 +31,7 @@ import org.stellar.anchor.api.sep.sep31.Sep31PostTransactionRequest.Sep31TxnFiel import org.stellar.anchor.api.sep.sep38.RateFee import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.asset.AssetService -import org.stellar.anchor.asset.ResourceJsonAssetService +import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.auth.JwtService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.SecretConfig @@ -267,7 +267,7 @@ class Sep31ServiceTest { """ } - private val assetService: AssetService = ResourceJsonAssetService("test_assets.json") + private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") @MockK(relaxed = true) private lateinit var txnStore: Sep31TransactionStore @@ -351,7 +351,7 @@ class Sep31ServiceTest { @Test fun `test quotes supported and required validation`() { val assetServiceQuotesNotSupported: AssetService = - ResourceJsonAssetService( + DefaultAssetService.fromResource( "test_assets.json.quotes_required_but_not_supported", ) val ex: AnchorException = assertThrows { @@ -887,7 +887,7 @@ class Sep31ServiceTest { } val assetServiceQuotesNotSupported: AssetService = - ResourceJsonAssetService( + DefaultAssetService.fromResource( "test_assets.json.quotes_not_supported", ) 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 487c36c7a4..1e6b048169 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -24,7 +24,7 @@ import org.stellar.anchor.api.sep.sep38.* 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.asset.DefaultAssetService import org.stellar.anchor.config.SecretConfig import org.stellar.anchor.config.Sep38Config import org.stellar.anchor.event.EventService @@ -61,7 +61,7 @@ class Sep38ServiceTest { fun setUp() { MockKAnnotations.init(this, relaxUnitFun = true) - val assetService = ResourceJsonAssetService("test_assets.json") + val assetService = DefaultAssetService.fromResource("test_assets.json") val assets = assetService.listAllAssets() val sep8Config = PropertySep38Config() this.sep38Service = Sep38Service(sep8Config, assetService, null, null, eventService) diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java index b0970599e3..8d0cac609d 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java @@ -5,9 +5,9 @@ import org.springframework.context.annotation.Configuration; import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.asset.AssetService; +import org.stellar.anchor.asset.DefaultAssetService; import org.stellar.anchor.config.AssetsConfig; import org.stellar.anchor.platform.config.PropertyAssetsConfig; -import org.stellar.anchor.platform.service.PropertyAssetsService; @Configuration public class AssetBeans { @@ -19,6 +19,6 @@ AssetsConfig assetsConfig() { @Bean AssetService assetService(AssetsConfig assetsConfig) throws InvalidConfigException { - return new PropertyAssetsService(assetsConfig); + return DefaultAssetService.fromAssetConfig(assetsConfig); } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt index f197bf2646..76f17451f3 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.stellar.anchor.api.exception.ServerErrorException import org.stellar.anchor.asset.AssetService -import org.stellar.anchor.asset.ResourceJsonAssetService +import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.platform.component.observer.PaymentObserverBeans import org.stellar.anchor.platform.config.PaymentObserverConfig @@ -38,7 +38,7 @@ class PaymentObservingAccountsBeansTest { @Test fun test_stellarPaymentObserverService_failure() { - val assetService: AssetService = ResourceJsonAssetService("test_assets.json") + val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") val paymentObserverBeans = PaymentObserverBeans() val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) @@ -131,7 +131,7 @@ class PaymentObservingAccountsBeansTest { fun test_givenGoodManager_whenConstruct_thenOk() { // success! val paymentObserverBeans = PaymentObserverBeans() - val assetService: AssetService = ResourceJsonAssetService("test_assets.json") + val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) 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 50dc393bd1..381e81043c 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 @@ -24,7 +24,7 @@ import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.api.shared.RefundPayment import org.stellar.anchor.api.shared.Refunds import org.stellar.anchor.asset.AssetService -import org.stellar.anchor.asset.ResourceJsonAssetService +import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.event.EventService import org.stellar.anchor.platform.data.JdbcSep31RefundPayment import org.stellar.anchor.platform.data.JdbcSep31Refunds @@ -166,7 +166,7 @@ class TransactionServiceTest { assertEquals("'$fiatUSD' is not a supported asset.", ex.message) // fails if listAllAssets does not contain the desired asset - this.assetService = ResourceJsonAssetService("test_assets.json") + this.assetService = DefaultAssetService.fromResource("test_assets.json") ex = assertThrows { transactionService.validateAsset("amount_in", mockAsset) } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("'$fiatUSD' is not a supported asset.", ex.message) @@ -174,7 +174,7 @@ class TransactionServiceTest { @Test fun test_validateAsset() { - this.assetService = ResourceJsonAssetService("test_assets.json") + this.assetService = DefaultAssetService.fromResource("test_assets.json") transactionService = TransactionService( sep24TransactionStore, @@ -280,7 +280,7 @@ class TransactionServiceTest { every { mockSep38Quote.fee.asset } returns fiatUSD every { sep38QuoteStore.findByQuoteId(quoteId) } returns mockSep38Quote - this.assetService = ResourceJsonAssetService("test_assets.json") + this.assetService = DefaultAssetService.fromResource("test_assets.json") transactionService = TransactionService( sep24TransactionStore, 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 2427d6c59c..24c51d7cab 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -17,7 +17,7 @@ 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.asset.DefaultAssetService import org.stellar.anchor.config.AppConfig import org.stellar.anchor.config.Sep31Config import org.stellar.anchor.event.EventService @@ -47,7 +47,7 @@ class Sep31DepositInfoGeneratorTest { """ } - private val assetService: AssetService = ResourceJsonAssetService("test_assets.json") + private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") @MockK(relaxed = true) private lateinit var txnStore: Sep31TransactionStore @MockK(relaxed = true) private lateinit var appConfig: AppConfig From d2929699c49c15615595f6229dca3961c5e1bf0b Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 26 Dec 2022 11:30:29 +0800 Subject: [PATCH 0072/1439] core: [ANCHOR-49] Make PropertySep1Config.type as an enum (#694) * Make PropertySepConfig.type as an enum --- .../org/stellar/anchor/config/Sep1Config.java | 24 +++++++++- .../org/stellar/anchor/sep1/Sep1Service.java | 8 ++-- .../stellar/anchor/sep1/Sep1ServiceTest.kt | 18 ++------ .../platform/config/PropertySep1Config.java | 14 +++--- .../org/stellar/anchor/Sep1ServiceTest.kt | 7 +-- .../anchor/platform/config/Sep1ConfigTest.kt | 46 ++++++++----------- 6 files changed, 60 insertions(+), 57 deletions(-) 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 b199f3f78f..bc96315de8 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep1Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep1Config.java @@ -1,10 +1,32 @@ package org.stellar.anchor.config; +import org.stellar.anchor.api.exception.InvalidConfigException; +import org.stellar.anchor.util.StringHelper; + @SuppressWarnings("unused") public interface Sep1Config { boolean isEnabled(); - String getType(); + TomlType getType(); String getValue(); + + enum TomlType { + STRING, + FILE, + URL; + + public static TomlType fromString(String name) throws InvalidConfigException { + if (StringHelper.isEmpty(name)) name = ""; + switch (name.toLowerCase()) { + case "string": + return STRING; + case "file": + return FILE; + case "url": + return URL; + } + throw new InvalidConfigException(String.format("Invalid sep1.type:[%s]", 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 348203a8d0..d6a77eba9e 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -24,16 +24,16 @@ public class Sep1Service { public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { if (sep1Config.isEnabled()) { debug("sep1Config:", sep1Config); - switch (sep1Config.getType().toLowerCase()) { - case "string": + switch (sep1Config.getType()) { + case STRING: debug("reading stellar.toml from config[sep1.toml.value]"); tomlValue = sep1Config.getValue(); break; - case "file": + case FILE: debugF("reading stellar.toml from {}", sep1Config.getValue()); tomlValue = Files.readString(Path.of(sep1Config.getValue())); break; - case "url": + case URL: debugF("reading stellar.toml from {}", sep1Config.getValue()); tomlValue = NetUtil.fetch(sep1Config.getValue()); break; 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 36ba003758..da4a84a433 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -11,9 +11,8 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.assertThrows -import org.stellar.anchor.api.exception.InvalidConfigException import org.stellar.anchor.config.Sep1Config +import org.stellar.anchor.config.Sep1Config.TomlType.* import org.stellar.anchor.util.NetUtil @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -36,7 +35,7 @@ internal class Sep1ServiceTest { @Test fun `test string type`() { every { sep1Config.isEnabled } returns true - every { sep1Config.type } returns "string" + every { sep1Config.type } returns STRING every { sep1Config.value } returns "toml content" val sep1Service = Sep1Service(sep1Config) assertEquals("toml content", sep1Service.stellarToml) @@ -47,7 +46,7 @@ internal class Sep1ServiceTest { mockkStatic(Files::class) every { Files.readString(any()) } returns "toml content" every { sep1Config.isEnabled } returns true - every { sep1Config.type } returns "file" + every { sep1Config.type } returns FILE every { sep1Config.value } returns "toml_file_path" val sep1Service = Sep1Service(sep1Config) @@ -59,19 +58,10 @@ internal class Sep1ServiceTest { mockkStatic(NetUtil::class) every { NetUtil.fetch(any()) } returns "toml content" every { sep1Config.isEnabled } returns true - every { sep1Config.type } returns "url" + every { sep1Config.type } returns URL every { sep1Config.value } returns "toml_file_path" val sep1Service = Sep1Service(sep1Config) assertEquals("toml content", sep1Service.stellarToml) } - - @Test - fun `test bad type`() { - every { sep1Config.isEnabled } returns true - every { sep1Config.type } returns "bad" - every { sep1Config.value } returns "toml_file_path" - - assertThrows { Sep1Service(sep1Config) } - } } 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 7a7ad9f209..e73156f91d 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,7 +1,5 @@ package org.stellar.anchor.platform.config; -import static org.stellar.anchor.util.StringHelper.isEmpty; - import java.io.File; import lombok.AllArgsConstructor; import lombok.Data; @@ -22,7 +20,7 @@ public class PropertySep1Config implements Sep1Config, Validator { boolean enabled; @Value("${sep1.toml.type:#{null}}") - String type; + TomlType type; @Value("${sep1.toml.value:#{null}}") String value; @@ -43,11 +41,11 @@ public void validate(@NotNull Object target, @NotNull Errors errors) { } void validateTomlTypeAndValue(Sep1Config config, Errors errors) { - if (isEmpty(config.getType())) { + if (config.getType() == null) { errors.rejectValue("type", "sep1-toml-type-empty", "sep1.toml.type must not be empty"); } else { - switch (config.getType().toLowerCase()) { - case "string": + switch (config.getType()) { + case STRING: try { Sep1Helper.parse(config.getValue()); } catch (IllegalStateException isex) { @@ -58,7 +56,7 @@ void validateTomlTypeAndValue(Sep1Config config, Errors errors) { "sep1.toml.value does not contain a valid TOML. %s", isex.getMessage())); } break; - case "url": + case URL: if (!NetUtil.isUrlValid(config.getValue())) { errors.rejectValue( "value", @@ -66,7 +64,7 @@ void validateTomlTypeAndValue(Sep1Config config, Errors errors) { String.format("sep1.toml.value=%s is not a valid URL", config.getValue())); } break; - case "file": + case FILE: File file = new File(config.getValue()); if (!file.exists()) { errors.rejectValue( diff --git a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt index 403a8785cc..a8a3d21a2c 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/Sep1ServiceTest.kt @@ -6,6 +6,7 @@ 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.config.Sep1Config.TomlType.* import org.stellar.anchor.platform.config.PropertySep1Config import org.stellar.anchor.platform.config.Sep1ConfigTest import org.stellar.anchor.sep1.Sep1Service @@ -46,14 +47,14 @@ class Sep1ServiceTest { @Test fun `test Sep1Service reading toml from inline string`() { - val config = PropertySep1Config(true, "string", stellarToml) + 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()) + val config = PropertySep1Config(true, FILE, Sep1ConfigTest.getTestTomlAsFile()) sep1 = Sep1Service(config) assertEquals(sep1.stellarToml, Files.readString(Path.of(Sep1ConfigTest.getTestTomlAsFile()))) } @@ -65,7 +66,7 @@ class Sep1ServiceTest { val mockAnchorUrl = mockServer.url("").toString() mockServer.enqueue(MockResponse().setBody(stellarToml)) - val config = PropertySep1Config(true, "url", mockAnchorUrl) + val config = PropertySep1Config(true, URL, mockAnchorUrl) sep1 = Sep1Service(config) assertEquals(sep1.stellarToml, stellarToml) } 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 07a06861c6..da9c34c9c5 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 @@ -2,14 +2,17 @@ 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.Test +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.assertThrows 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 +import org.stellar.anchor.api.exception.InvalidConfigException +import org.stellar.anchor.config.Sep1Config.TomlType.FILE +import org.stellar.anchor.config.Sep1Config.TomlType.fromString class Sep1ConfigTest { companion object { @@ -39,72 +42,61 @@ class Sep1ConfigTest { @ParameterizedTest @ValueSource(strings = ["file", "FILE", "File"]) fun `test reading from sep1-stellar-test toml file`(type: String) { - val errors = validate(PropertySep1Config(true, type, getTestTomlAsFile())) + val errors = validate(PropertySep1Config(true, fromString(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\"")) + val errors = validate(PropertySep1Config(true, fromString(type), "VERSION = \"0.1.0\"")) assertEquals(0, errors.errorCount) } @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())) + val errors = validate(PropertySep1Config(true, fromString(type), getTestTomlAsUrl())) assertEquals(0, errors.errorCount) } @ParameterizedTest @ValueSource(strings = ["bad", "strin g"]) fun `test bad Sep1Config values`(type: String?) { - val errors = validate(PropertySep1Config(true, type, getTestTomlAsUrl())) - assertEquals(1, errors.errorCount) - assertEquals("sep1-toml-type-invalid", errors.allErrors[0].code) + assertThrows { + validate(PropertySep1Config(true, fromString(type), getTestTomlAsUrl())) + } } @ParameterizedTest @NullSource @ValueSource(strings = [""]) - fun `test empty Sep1Config values`(type: String?) { - val errors = validate(PropertySep1Config(true, type, getTestTomlAsUrl())) - assertEquals(1, errors.errorCount) - assertEquals("sep1-toml-type-empty", errors.allErrors[0].code) + fun `test empty Sep1Config types`(type: String?) { + assertThrows { + validate(PropertySep1Config(true, fromString(type), getTestTomlAsUrl())) + } } @ParameterizedTest @ValueSource(strings = ["bad file", "c:/hello"]) fun `test file of Sep1Config does not exist`(file: String) { - var errors = validate(PropertySep1Config(true, "file", file)) + var errors = validate(PropertySep1Config(true, FILE, file)) assertEquals(1, errors.errorCount) assertEquals("sep1-toml-value-file-does-not-exist", errors.allErrors[0].code) - errors = validate(PropertySep1Config(false, "file", file)) + errors = validate(PropertySep1Config(false, FILE, file)) assertEquals(0, errors.errorCount) } @ParameterizedTest @ValueSource(strings = ["string", "file", "url"]) fun `test Sep1Config empty values`(type: String) { - var errors = validate(PropertySep1Config(true, type, null)) + var errors = validate(PropertySep1Config(true, fromString(type), null)) assertEquals(1, errors.errorCount) assertEquals("sep1-toml-value-empty", errors.allErrors[0].code) - errors = validate(PropertySep1Config(true, type, "")) + errors = validate(PropertySep1Config(true, fromString(type), "")) assertEquals(1, errors.errorCount) assertEquals("sep1-toml-value-empty", errors.allErrors[0].code) } - - @Test - fun `test Sep1Config empty types`() { - var errors = validate(PropertySep1Config(true, null, "test value")) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "sep1-toml-type-empty") } - - errors = validate(PropertySep1Config(true, "", "test value")) - assertEquals(1, errors.errorCount) - errors.message?.let { assertContains(it, "sep1-toml-type-empty") } - } } From 643b390e85bfb498ccab8772154b663b8020dc6d Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 26 Dec 2022 14:45:52 +0800 Subject: [PATCH 0073/1439] Sanitize the request message to avoid scripting vulnerability (#695) --- .../java/org/stellar/anchor/util/StringHelper.java | 13 +++++++++++++ .../platform/utils/RequestResponseMessage.java | 10 ++++++++-- 2 files changed, 21 insertions(+), 2 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 0f361992b9..8888743b63 100644 --- a/core/src/main/java/org/stellar/anchor/util/StringHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/StringHelper.java @@ -1,5 +1,8 @@ package org.stellar.anchor.util; +import com.google.gson.Gson; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Objects; import org.apache.commons.text.WordUtils; @@ -50,4 +53,14 @@ public static String toPosixForm(String camel) { .replaceAll("\\.", "_") .toUpperCase(); } + + static Gson gson = GsonUtils.getInstance(); + + public static String json(Object obj) { + return gson.toJson(obj); + } + + public static String sanitize(String value) { + return URLEncoder.encode(value.replace("\n", "").replace("\r", ""), StandardCharsets.UTF_8); + } } 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 index 58fa27d7e4..9c9527abd0 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/utils/RequestResponseMessage.java +++ b/platform/src/main/java/org/stellar/anchor/platform/utils/RequestResponseMessage.java @@ -1,5 +1,7 @@ package org.stellar.anchor.platform.utils; +import static org.stellar.anchor.util.StringHelper.sanitize; + import java.util.stream.Collectors; import lombok.Builder; @@ -57,12 +59,16 @@ public String toString() { } static String getStrValue(Object obj) { + String result = null; if (obj == null) { return null; } if (obj instanceof String) { - return String.format("\"%s\"", obj); + result = String.format("\"%s\"", obj); + } else { + result = obj.toString(); } - return obj.toString(); + + return sanitize(result); } } From f291b9e27e5ba0a77252c59094b03c0ed99183c0 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 27 Dec 2022 10:42:44 +0800 Subject: [PATCH 0074/1439] all: [ANCHOR-121] Add more_info_url configuration and construction classes (#696) * Add more_info_url configuration and construction * Added unit tests * Add Sep24Config validations --- .../anchor/sep24/MoreInfoUrlConstructor.java | 9 ++ .../org/stellar/anchor/sep24/Sep24Helper.java | 112 +++--------------- .../stellar/anchor/sep24/Sep24Service.java | 64 +++++++--- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 12 +- .../platform/component/sep/SepBeans.java | 14 ++- .../platform/config/PropertySep24Config.java | 69 ++++++++++- .../SimpleInteractiveUrlConstructor.java | 4 +- .../service/SimpleMoreInfoUrlConstructor.java | 49 ++++++++ .../config/anchor-config-default-values.yaml | 20 +++- .../config/anchor-config-schema-v1.yaml | 4 + .../anchor/platform/config/Sep24ConfigTest.kt | 39 +++++- .../SimpleMoreInfoUrlConstructorTest.kt | 68 +++++++++++ 12 files changed, 333 insertions(+), 131 deletions(-) create mode 100644 core/src/main/java/org/stellar/anchor/sep24/MoreInfoUrlConstructor.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructor.java create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructorTest.kt diff --git a/core/src/main/java/org/stellar/anchor/sep24/MoreInfoUrlConstructor.java b/core/src/main/java/org/stellar/anchor/sep24/MoreInfoUrlConstructor.java new file mode 100644 index 0000000000..7730f546be --- /dev/null +++ b/core/src/main/java/org/stellar/anchor/sep24/MoreInfoUrlConstructor.java @@ -0,0 +1,9 @@ +package org.stellar.anchor.sep24; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; + +public abstract class MoreInfoUrlConstructor { + public abstract String construct(Sep24Transaction txn) + throws URISyntaxException, MalformedURLException; +} diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java index 91c7597bd9..4921ae5a4b 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Helper.java @@ -4,30 +4,25 @@ import static org.stellar.anchor.util.Log.debugF; import static org.stellar.anchor.util.MathHelper.decimal; -import com.google.gson.Gson; import java.math.BigDecimal; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; -import org.apache.http.client.utils.URIBuilder; import org.springframework.beans.BeanUtils; import org.stellar.anchor.api.exception.EventPublishException; import org.stellar.anchor.api.sep.AssetInfo; -import org.stellar.anchor.api.sep.sep24.*; -import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.api.sep.sep24.RefundPayment; +import org.stellar.anchor.api.sep.sep24.Refunds; +import org.stellar.anchor.api.sep.sep24.TransactionResponse; import org.stellar.anchor.auth.JwtToken; import org.stellar.anchor.config.Sep24Config; import org.stellar.anchor.event.EventService; import org.stellar.anchor.event.models.TransactionEvent; -import org.stellar.anchor.util.GsonUtils; public class Sep24Helper { - private static final List needsMoreInfoUrlDeposit = + static final List needsMoreInfoUrlDeposit = Arrays.asList( PENDING_USR_TRANSFER_START.toString(), PENDING_USR_TRANSFER_COMPLETE.toString(), @@ -36,7 +31,7 @@ public class Sep24Helper { PENDING_EXTERNAL.toString(), PENDING_ANCHOR.toString(), PENDING_USER.toString()); - private static final List needsMoreInfoUrlWithdraw = + static final List needsMoreInfoUrlWithdraw = Arrays.asList( PENDING_USR_TRANSFER_START.toString(), PENDING_USR_TRANSFER_COMPLETE.toString(), @@ -46,89 +41,7 @@ public class Sep24Helper { PENDING_ANCHOR.toString(), PENDING_USER.toString()); - private static final Gson gson = GsonUtils.getInstance(); - - public static String constructMoreInfoUrl( - JwtService jwtService, Sep24Config sep24Config, Sep24Transaction txn, String lang) - throws URISyntaxException, MalformedURLException { - - JwtToken token = - JwtToken.of( - "moreInfoUrl", - txn.getSep10Account(), - Instant.now().getEpochSecond(), - Instant.now().getEpochSecond() + sep24Config.getInteractiveJwtExpiration(), - txn.getTransactionId(), - txn.getClientDomain()); - - // TODO: Fix the more_info_url - URI uri = new URI("https://www.stellar.org"); - - URIBuilder builder = - new URIBuilder() - .setScheme(uri.getScheme()) - .setHost(uri.getHost()) - .setPort(uri.getPort()) - .setPath("transaction-status") - .addParameter("transaction_id", txn.getTransactionId()) - .addParameter("token", jwtService.encode(token)); - - if (lang != null) { - builder.addParameter("lang", lang); - } - - return builder.build().toURL().toString(); - } - - public static TransactionResponse fromDepositTxn( - JwtService jwtService, - Sep24Config sep24Config, - Sep24Transaction txn, - String lang, - boolean allowMoreInfoUrl) - throws MalformedURLException, URISyntaxException { - - DepositTransactionResponse txnR = - gson.fromJson(gson.toJson(txn), DepositTransactionResponse.class); - - setSharedTransactionResponseFields(txnR, txn); - - txnR.setDepositMemo(txn.getMemo()); - txnR.setDepositMemoType(txn.getMemoType()); - - if (allowMoreInfoUrl && needsMoreInfoUrlDeposit.contains(txn.getStatus())) { - txnR.setMoreInfoUrl(constructMoreInfoUrl(jwtService, sep24Config, txn, lang)); - } - - return txnR; - } - - public static WithdrawTransactionResponse fromWithdrawTxn( - JwtService jwtService, - Sep24Config sep24Config, - Sep24Transaction txn, - String lang, - boolean allowMoreInfoUrl) - throws MalformedURLException, URISyntaxException { - - WithdrawTransactionResponse txnR = - gson.fromJson(gson.toJson(txn), WithdrawTransactionResponse.class); - - setSharedTransactionResponseFields(txnR, txn); - - txnR.setWithdrawMemo(txn.getMemo()); - txnR.setWithdrawMemoType(txn.getMemoType()); - txnR.setWithdrawAnchorAccount(txn.getWithdrawAnchorAccount()); - - if (allowMoreInfoUrl && needsMoreInfoUrlWithdraw.contains(txn.getStatus())) { - txnR.setMoreInfoUrl(constructMoreInfoUrl(jwtService, sep24Config, txn, lang)); - } - - return txnR; - } - - private static void setSharedTransactionResponseFields( - TransactionResponse txnR, Sep24Transaction txn) { + static void setSharedTransactionResponseFields(TransactionResponse txnR, Sep24Transaction txn) { txnR.setId(txn.getTransactionId()); if (txn.getFromAccount() != null) txnR.setFrom(txn.getFromAccount()); if (txn.getToAccount() != null) txnR.setTo(txn.getToAccount()); @@ -136,7 +49,7 @@ private static void setSharedTransactionResponseFields( if (txn.getCompletedAt() != null) txnR.setCompletedAt(txn.getCompletedAt()); } - public static TransactionResponse updateRefundInfo( + static TransactionResponse updateRefundInfo( TransactionResponse response, Sep24Transaction txn, AssetInfo assetInfo) { debugF("Calculating refund information"); @@ -189,4 +102,15 @@ public static void publishEvent( .build(); eventService.publish(event); } + + public static JwtToken buildRedirectJwtToken( + Sep24Config sep24Config, String fullRequestUrl, JwtToken token, Sep24Transaction txn) { + return JwtToken.of( + fullRequestUrl, + token.getSub(), + Instant.now().getEpochSecond(), + Instant.now().getEpochSecond() + sep24Config.getInteractiveJwtExpiration(), + txn.getTransactionId(), + token.getClientDomain()); + } } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 54abf76475..2b102f8f14 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -2,7 +2,7 @@ import static org.stellar.anchor.api.sep.SepTransactionStatus.INCOMPLETE; import static org.stellar.anchor.event.models.TransactionEvent.Type.TRANSACTION_CREATED; -import static org.stellar.anchor.sep24.Sep24Helper.publishEvent; +import static org.stellar.anchor.sep24.Sep24Helper.*; import static org.stellar.anchor.sep24.Sep24Transaction.Kind.DEPOSIT; import static org.stellar.anchor.sep24.Sep24Transaction.Kind.WITHDRAWAL; import static org.stellar.anchor.sep9.Sep9Fields.extractSep9Fields; @@ -14,6 +14,7 @@ import static org.stellar.anchor.util.SepHelper.memoTypeString; import static org.stellar.anchor.util.SepLanguageHelper.validateLanguage; +import com.google.gson.Gson; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; @@ -28,6 +29,7 @@ import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.config.Sep24Config; import org.stellar.anchor.event.EventService; +import org.stellar.anchor.util.GsonUtils; import org.stellar.sdk.KeyPair; import org.stellar.sdk.Memo; @@ -39,6 +41,9 @@ public class Sep24Service { final Sep24TransactionStore txnStore; final EventService eventService; final InteractiveUrlConstructor interactiveUrlConstructor; + final MoreInfoUrlConstructor moreInfoUrlConstructor; + + static final Gson gson = GsonUtils.getInstance(); public Sep24Service( AppConfig appConfig, @@ -47,7 +52,9 @@ public Sep24Service( JwtService jwtService, Sep24TransactionStore txnStore, EventService eventService, - InteractiveUrlConstructor interactiveUrlConstructor) { + InteractiveUrlConstructor interactiveUrlConstructor, + MoreInfoUrlConstructor moreInfoUrlConstructor) { + this.moreInfoUrlConstructor = moreInfoUrlConstructor; debug("appConfig:", appConfig); debug("sep24Config:", sep24Config); this.appConfig = appConfig; @@ -155,7 +162,7 @@ public InteractiveTransactionResponse withdraw( return new InteractiveTransactionResponse( "interactive_customer_info_needed", interactiveUrlConstructor.construct( - buildRedirectJwtToken(fullRequestUrl, token, txn), txn, lang, sep9Fields), + buildRedirectJwtToken(sep24Config, fullRequestUrl, token, txn), txn, lang, sep9Fields), txn.getTransactionId()); } @@ -270,7 +277,7 @@ public InteractiveTransactionResponse deposit( return new InteractiveTransactionResponse( "interactive_customer_info_needed", interactiveUrlConstructor.construct( - buildRedirectJwtToken(fullRequestUrl, token, txn), txn, lang, sep9Fields), + buildRedirectJwtToken(sep24Config, fullRequestUrl, token, txn), txn, lang, sep9Fields), txn.getTransactionId()); } @@ -352,7 +359,7 @@ public Sep24GetTransactionResponse findTransaction(JwtToken token, GetTransactio public InfoResponse getInfo() { info("Getting Sep24 info"); - List assets = listAllAssets(); + List assets = assetService.listAllAssets(); InfoResponse info = new InfoResponse(); info.setDeposit(new HashMap<>()); info.setWithdraw(new HashMap<>()); @@ -378,9 +385,9 @@ TransactionResponse fromTxn(Sep24Transaction txn, String lang) lang); TransactionResponse response; if (txn.getKind().equals(DEPOSIT.toString())) { - response = Sep24Helper.fromDepositTxn(jwtService, sep24Config, txn, lang, true); + response = fromDepositTxn(txn); } else if (txn.getKind().equals(WITHDRAWAL.toString())) { - response = Sep24Helper.fromWithdrawTxn(jwtService, sep24Config, txn, lang, true); + response = fromWithdrawTxn(txn); } else { throw new SepException(String.format("unsupported txn kind:%s", txn.getKind())); } @@ -391,17 +398,40 @@ TransactionResponse fromTxn(Sep24Transaction txn, String lang) return Sep24Helper.updateRefundInfo(response, txn, assetInfo); } - JwtToken buildRedirectJwtToken(String fullRequestUrl, JwtToken token, Sep24Transaction txn) { - return JwtToken.of( - fullRequestUrl, - token.getSub(), - Instant.now().getEpochSecond(), - Instant.now().getEpochSecond() + sep24Config.getInteractiveJwtExpiration(), - txn.getTransactionId(), - token.getClientDomain()); + public TransactionResponse fromDepositTxn(Sep24Transaction txn) + throws MalformedURLException, URISyntaxException { + + DepositTransactionResponse txnR = + gson.fromJson(gson.toJson(txn), DepositTransactionResponse.class); + + setSharedTransactionResponseFields(txnR, txn); + + txnR.setDepositMemo(txn.getMemo()); + txnR.setDepositMemoType(txn.getMemoType()); + + if (needsMoreInfoUrlDeposit.contains(txn.getStatus())) { + txnR.setMoreInfoUrl(moreInfoUrlConstructor.construct(txn)); + } + + return txnR; } - List listAllAssets() { - return this.assetService.listAllAssets(); + public WithdrawTransactionResponse fromWithdrawTxn(Sep24Transaction txn) + throws MalformedURLException, URISyntaxException { + + WithdrawTransactionResponse txnR = + gson.fromJson(gson.toJson(txn), WithdrawTransactionResponse.class); + + setSharedTransactionResponseFields(txnR, txn); + + txnR.setWithdrawMemo(txn.getMemo()); + txnR.setWithdrawMemoType(txn.getMemoType()); + txnR.setWithdrawAnchorAccount(txn.getWithdrawAnchorAccount()); + + if (needsMoreInfoUrlWithdraw.contains(txn.getStatus())) { + txnR.setMoreInfoUrl(moreInfoUrlConstructor.construct(txn)); + } + + return txnR; } } 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 6a9cbc920a..2bd3678623 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -47,6 +47,7 @@ import org.stellar.sdk.MemoText internal class Sep24ServiceTest { companion object { const val TEST_SEP24_INTERACTIVE_URL = "https://test-anchor.stellar.org" + const val TEST_SEP24_MORE_INFO_URL = "https://test-anchor.stellar.org/more_info_url" val TEST_STARTED_AT: Instant = Instant.now() val TEST_COMPLETED_AT: Instant = Instant.now().plusSeconds(100) } @@ -57,6 +58,7 @@ internal class Sep24ServiceTest { @MockK(relaxed = true) lateinit var eventService: EventService @MockK(relaxed = true) lateinit var txnStore: Sep24TransactionStore @MockK(relaxed = true) lateinit var interactiveUrlConstructor: InteractiveUrlConstructor + @MockK(relaxed = true) lateinit var moreInfoUrlConstructor: MoreInfoUrlConstructor private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") @@ -80,6 +82,8 @@ internal class Sep24ServiceTest { val strToken = jwtService.encode(createdJwt) every { interactiveUrlConstructor.construct(any(), any(), any(), any()) } returns "${TEST_SEP24_INTERACTIVE_URL}?lang=en&token=$strToken" + every { moreInfoUrlConstructor.construct(any()) } returns + "${TEST_SEP24_MORE_INFO_URL}?lang=en&token=$strToken" sep24Service = Sep24Service( @@ -89,7 +93,8 @@ internal class Sep24ServiceTest { jwtService, txnStore, eventService, - interactiveUrlConstructor + interactiveUrlConstructor, + moreInfoUrlConstructor ) } @@ -225,7 +230,7 @@ internal class Sep24ServiceTest { val request = createTestTransactionRequest() request["claimable_balance_supported"] = claimable_balance_supported - var response = sep24Service.deposit("/sep24/deposit", createdJwt, request) + val response = sep24Service.deposit("/sep24/deposit", createdJwt, request) verify(exactly = 1) { txnStore.save(any()) } @@ -242,9 +247,6 @@ internal class Sep24ServiceTest { assertEquals(slotTxn.captured.clientDomain, TEST_CLIENT_DOMAIN) assertEquals(slotTxn.captured.amountIn, "123.4") assertEquals(slotTxn.captured.amountOut, "123.4") - - // Now test with a memo - } @Test diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java index fccbfb8342..8f9ea1ecab 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java @@ -22,10 +22,12 @@ import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorApi; import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorSelf; import org.stellar.anchor.platform.service.SimpleInteractiveUrlConstructor; +import org.stellar.anchor.platform.service.SimpleMoreInfoUrlConstructor; import org.stellar.anchor.sep1.Sep1Service; import org.stellar.anchor.sep10.Sep10Service; import org.stellar.anchor.sep12.Sep12Service; import org.stellar.anchor.sep24.InteractiveUrlConstructor; +import org.stellar.anchor.sep24.MoreInfoUrlConstructor; import org.stellar.anchor.sep24.Sep24Service; import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.sep31.Sep31DepositInfoGenerator; @@ -139,7 +141,8 @@ Sep24Service sep24Service( JwtService jwtService, Sep24TransactionStore sep24TransactionStore, EventService eventService, - InteractiveUrlConstructor interactiveUrlConstructor) { + InteractiveUrlConstructor interactiveUrlConstructor, + MoreInfoUrlConstructor moreInfoUrlConstructor) { return new Sep24Service( appConfig, sep24Config, @@ -147,7 +150,8 @@ Sep24Service sep24Service( jwtService, sep24TransactionStore, eventService, - interactiveUrlConstructor); + interactiveUrlConstructor, + moreInfoUrlConstructor); } @Bean @@ -157,6 +161,12 @@ InteractiveUrlConstructor interactiveUrlConstructor( sep24Config.getInteractiveUrl().getSimple(), jwtService); } + @Bean + MoreInfoUrlConstructor moreInfoUrlConstructor( + PropertySep24Config sep24Config, JwtService jwtService) { + return new SimpleMoreInfoUrlConstructor(sep24Config.getMoreInfoUrl().getSimple(), jwtService); + } + @Bean Sep31DepositInfoGenerator sep31DepositInfoGenerator( Sep31Config sep31Config, 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 6fbad4d88c..0be2ed5f1f 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,20 +1,20 @@ package org.stellar.anchor.platform.config; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import lombok.*; import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; import org.stellar.anchor.config.Sep24Config; import org.stellar.anchor.util.NetUtil; -@Data +@Getter +@Setter public class PropertySep24Config implements Sep24Config, Validator { boolean enabled; int interactiveJwtExpiration; InteractiveUrlConfig interactiveUrl; + MoreInfoUrlConfig moreInfoUrl; @Data @AllArgsConstructor @@ -32,6 +32,23 @@ public static class SimpleInteractiveUrlConfig { List txnFields; } + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class MoreInfoUrlConfig { + String type; + SimpleMoreInfoUrlConfig simple; + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class SimpleMoreInfoUrlConfig { + String baseUrl; + List txnFields; + int jwtExpiration; + } + @Override public boolean supports(@NotNull Class clazz) { return Sep24Config.class.isAssignableFrom(clazz); @@ -50,6 +67,11 @@ public void validate(@NotNull Object target, @NotNull Errors errors) { config.getInteractiveJwtExpiration())); } + validateInteractiveUrlConfig(config, errors); + validateMoreInfoUrlConfig(config, errors); + } + + void validateInteractiveUrlConfig(PropertySep24Config config, Errors errors) { if (config.interactiveUrl == null) { errors.rejectValue( "interactiveUrl", @@ -81,4 +103,43 @@ public void validate(@NotNull Object target, @NotNull Errors errors) { } } } + + void validateMoreInfoUrlConfig(PropertySep24Config config, Errors errors) { + if (config.moreInfoUrl == null) { + errors.rejectValue( + "moreInfoUrl", "sep24-moreinfo-url-invalid", "sep24.more-info-url is not defined."); + } else { + if ("simple".equals(config.moreInfoUrl.getType())) { + if (config.moreInfoUrl.getSimple() == null) { + errors.rejectValue( + "moreInfoUrl", + "sep24-more-info-url-simple-not-defined", + "sep24.more_info_url.simple is not defined."); + } + if (!NetUtil.isUrlValid(config.moreInfoUrl.simple.baseUrl)) { + errors.rejectValue( + "moreInfoUrl", + "sep24-more-info-url-simple-base-url-not-valid", + String.format( + "sep24.more_info_url.simple.base_url:[%s] is not a valid URL.", + config.moreInfoUrl.simple.baseUrl)); + } + if (config.moreInfoUrl.simple.jwtExpiration <= 0) { + errors.rejectValue( + "moreInfoUrl", + "sep24-more-info-url-simple-jwt-expiration-not-valid", + String.format( + "sep24.more_info_url.simple.jwt_expiration:[%s] must be greater than 0.", + config.moreInfoUrl.simple.jwtExpiration)); + } + } else { + errors.rejectValue( + "moreInfoUrl", + "sep24-more-info-url-invalid-type", + String.format( + "sep24.more_info_url.type:[%s] is not supported.", + config.interactiveUrl.getType())); + } + } + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java index 65cfd9b9b1..b7132de7ab 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java @@ -16,8 +16,8 @@ import org.stellar.anchor.util.StringHelper; public class SimpleInteractiveUrlConstructor extends InteractiveUrlConstructor { - private SimpleInteractiveUrlConfig config; - private JwtService jwtService; + private final SimpleInteractiveUrlConfig config; + private final JwtService jwtService; public SimpleInteractiveUrlConstructor(SimpleInteractiveUrlConfig config, JwtService jwtService) { this.config = config; diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructor.java b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructor.java new file mode 100644 index 0000000000..7fa15d7337 --- /dev/null +++ b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructor.java @@ -0,0 +1,49 @@ +package org.stellar.anchor.platform.service; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Instant; +import org.apache.http.client.utils.URIBuilder; +import org.stellar.anchor.auth.JwtService; +import org.stellar.anchor.auth.JwtToken; +import org.stellar.anchor.platform.config.PropertySep24Config; +import org.stellar.anchor.sep24.MoreInfoUrlConstructor; +import org.stellar.anchor.sep24.Sep24Transaction; + +public class SimpleMoreInfoUrlConstructor extends MoreInfoUrlConstructor { + private final PropertySep24Config.SimpleMoreInfoUrlConfig config; + private final JwtService jwtService; + + public SimpleMoreInfoUrlConstructor( + PropertySep24Config.SimpleMoreInfoUrlConfig config, JwtService jwtService) { + this.config = config; + this.jwtService = jwtService; + } + + @Override + public String construct(Sep24Transaction txn) throws URISyntaxException, MalformedURLException { + JwtToken token = + JwtToken.of( + "moreInfoUrl", + txn.getSep10Account(), + Instant.now().getEpochSecond(), + Instant.now().getEpochSecond() + config.getJwtExpiration(), + txn.getTransactionId(), + txn.getClientDomain()); + + // TODO: Fix the more_info_url + URI uri = new URI(config.getBaseUrl()); + + URIBuilder builder = + new URIBuilder() + .setScheme(uri.getScheme()) + .setHost(uri.getHost()) + .setPort(uri.getPort()) + .setPath("transaction-status") + .addParameter("transaction_id", txn.getTransactionId()) + .addParameter("token", jwtService.encode(token)); + + return builder.build().toURL().toString(); + } +} 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 06efefdb65..1be2bdbef3 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -300,7 +300,7 @@ sep24: # Configures the interactive URL where the platform server will redirect to start the SEP-24 interactive flow. # If the type is `simple`, sep24.interactive_url.simple will be read to construct the url. # base_uri: is the base_uri used to construct the interactive url - # txn_fields: is the list of transaction fields that will be read to the interactive url as query parameters + # txn_fields: is the list of transaction fields that will be appended to the interactive url as query parameters # In addition to the txn_fields, the following fields are also added to the query parameters. # `transaction_id`: the transaction ID # `token`: the JWT token @@ -312,6 +312,24 @@ sep24: base_url: http://localhost:8080/sep24/interactive txn_fields: status, kind, amount_in, amount_in_asset + ## @param: more_info_url + # Configures the more_info_url of the transaction response when calling GET /transaction and GET /transactions endpoints. + # For details, please refer to https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#shared-fields-for-both-deposits-and-withdrawals + # + # If the type is `simple`, sep24.more_info_url.simple will be read to construct the url. + # base_uri: is the base_uri used to construct the more_info_url + # txn_fields: is the list of transaction fields that will be appended to the interactive url as query parameters + # jwt_expiration: the JWT expiration in seconds + # In addition to the txn_fields, the following fields are also added to the query parameters. + # `transaction_id`: the transaction ID + # `token`: the JWT token + more_info_url: + type: simple + simple: + base_url: http://localhost:8080/sep24/transaction/more_info + txn_fields: kind, amount_in, amount_in_asset + jwt_expiration: 600 + sep31: ## @param: 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 29dad214fe..cfec357ca2 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -73,6 +73,10 @@ sep24.interactive_jwt_expiration: sep24.interactive_url.simple.base_url: sep24.interactive_url.simple.txn_fields: sep24.interactive_url.type: +sep24.more_info_url.simple.base_url: +sep24.more_info_url.simple.txn_fields: +sep24.more_info_url.simple.jwt_expiration: +sep24.more_info_url.type: sep31.deposit_info_generator_type: sep31.enabled: sep31.payment_type: diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt index 3f55430d17..a2f16cb725 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt @@ -1,14 +1,14 @@ package org.stellar.anchor.platform.config -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertTrue +import java.lang.Integer.MIN_VALUE +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import org.springframework.validation.BindException import org.springframework.validation.Errors -import org.stellar.anchor.platform.config.PropertySep24Config.InteractiveUrlConfig +import org.stellar.anchor.platform.config.PropertySep24Config.* class Sep24ConfigTest { lateinit var config: PropertySep24Config @@ -25,6 +25,11 @@ class Sep24ConfigTest { "simple", PropertySep24Config.SimpleInteractiveUrlConfig("https://www.stellar.org", listOf("")) ) + config.moreInfoUrl = + MoreInfoUrlConfig( + "simple", + SimpleMoreInfoUrlConfig("https://www.stellar.org", listOf(""), 10) + ) } @Test @@ -34,7 +39,7 @@ class Sep24ConfigTest { } @ParameterizedTest - @ValueSource(ints = [-1, Integer.MIN_VALUE, 0]) + @ValueSource(ints = [-1, MIN_VALUE, 0]) fun `test bad interactive jwt expiration`(expiration: Int) { config.interactiveJwtExpiration = expiration @@ -45,7 +50,7 @@ class Sep24ConfigTest { @ParameterizedTest @ValueSource(strings = ["httpss://www.stellar.org"]) - fun `test bad url`(url: String) { + fun `test interactive url with bad url configuration`(url: String) { config.interactiveUrl = InteractiveUrlConfig( "simple", @@ -53,6 +58,28 @@ class Sep24ConfigTest { ) config.validate(config, errors) - assertTrue(errors.hasErrors()) + assertEquals("sep24-interactive-url-simple-base-url-not-valid", errors.allErrors[0].code) + } + + @ParameterizedTest + @ValueSource(strings = ["httpss://www.stellar.org"]) + fun `test more_info_url with invalid url`(url: String) { + config.moreInfoUrl = MoreInfoUrlConfig("simple", SimpleMoreInfoUrlConfig(url, listOf(""), 100)) + + config.validate(config, errors) + assertEquals("sep24-more-info-url-simple-base-url-not-valid", errors.allErrors[0].code) + } + + @ParameterizedTest + @ValueSource(ints = [-1, MIN_VALUE, 0]) + fun `test more_info_url with invalid jwt_expiration`(expiration: Int) { + config.moreInfoUrl = + MoreInfoUrlConfig( + "simple", + SimpleMoreInfoUrlConfig("https://www.stellar.org", listOf(""), expiration) + ) + + config.validate(config, errors) + assertEquals("sep24-more-info-url-simple-jwt-expiration-not-valid", errors.allErrors[0].code) } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructorTest.kt new file mode 100644 index 0000000000..6e89358471 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleMoreInfoUrlConstructorTest.kt @@ -0,0 +1,68 @@ +package org.stellar.anchor.platform.service + +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.stellar.anchor.auth.JwtService +import org.stellar.anchor.auth.JwtToken +import org.stellar.anchor.platform.config.PropertySep24Config +import org.stellar.anchor.platform.data.JdbcSep24Transaction +import org.stellar.anchor.util.GsonUtils + +class SimpleMoreInfoUrlConstructorTest { + companion object { + private val gson = GsonUtils.getInstance() + } + + @MockK(relaxed = true) private lateinit var jwtService: JwtService + lateinit var jwtToken: JwtToken + lateinit var txn: JdbcSep24Transaction + + @BeforeEach + fun setup() { + MockKAnnotations.init(this, relaxUnitFun = true) + + every { jwtService.encode(any()) } returns "mock_token" + + jwtToken = JwtToken() + txn = gson.fromJson(txnJson, JdbcSep24Transaction::class.java) + } + + @Test + fun `test correct config`() { + val config = + gson.fromJson(simpleConfig, PropertySep24Config.SimpleMoreInfoUrlConfig::class.java) + val constructor = SimpleMoreInfoUrlConstructor(config, jwtService) + val url = constructor.construct(txn) + assertEquals(expectedUrl, url) + } +} + +private const val simpleConfig = + """ +{ + "baseUrl": "http://localhost:8080/sep24/more_info_url", + "txnFields": [ + "kind", + "status" + ] +} +""" + +private const val txnJson = + """ +{ + "id": "123", + "transaction_id": "txn_123", + "status": "incomplete", + "kind" : "deposit", + "amount_in": "100", + "amount_in_asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" +} +""" + +private const val expectedUrl = + """http://localhost:8080/transaction-status?transaction_id=txn_123&token=mock_token""" From 73d78925fbc9ffa9cc7b9c662e3be581ec926beb Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 28 Dec 2022 01:52:22 +0800 Subject: [PATCH 0075/1439] all: [ANCHOR-89] [ANCHOR-72] [ANCHOR-45] Add SEP-24 /fee endpoint (#697) * Add sep24 fee endpoint * Remove fee from sep24 /info --- .github/workflows/basic_tests.yml | 2 - .../anchor/reference/service/FeeService.java | 71 ++++++++------- .../reference/service/FeeServiceTest.kt | 83 ----------------- .../org/stellar/anchor/api/sep/AssetInfo.java | 9 -- .../anchor/api/sep/sep24/InfoResponse.java | 2 +- .../api/sep/sep24/Sep24GetFeeResponse.java | 10 +++ .../stellar/anchor/sep24/Sep24Service.java | 39 ++++++-- .../anchor/api/sep/sep24/Sep24DtoTests.kt | 8 -- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 32 +++++++ .../stellar/anchor/platform/Sep24Client.kt | 16 ++++ .../org/stellar/anchor/platform/Sep24Tests.kt | 47 +++++----- .../org/stellar/anchor/platform/test.json | 89 ++++--------------- .../integration-test.anchor-config.yaml | 12 --- .../platform/component/sep/SepBeans.java | 2 + .../platform/controller/Sep24Controller.java | 21 ++++- .../stellar/anchor/platform/service/test.json | 0 16 files changed, 188 insertions(+), 255 deletions(-) delete mode 100644 anchor-reference-server/src/test/kotlin/org/stellar/anchor/reference/service/FeeServiceTest.kt create mode 100644 api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetFeeResponse.java delete mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/service/test.json diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml index 30c3792cf8..e9d573a016 100644 --- a/.github/workflows/basic_tests.yml +++ b/.github/workflows/basic_tests.yml @@ -43,8 +43,6 @@ jobs: cat /home/runner/work/java-stellar-anchor-sdk/java-stellar-anchor-sdk/platform/build/reports/tests/test/index.html 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: needs: [build_and_test] diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java index 6d88466947..1ad528c49c 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/service/FeeService.java @@ -3,13 +3,11 @@ import static org.stellar.anchor.util.MathHelper.decimal; import java.math.BigDecimal; -import java.util.Optional; import org.springframework.stereotype.Service; import org.stellar.anchor.api.callback.GetFeeRequest; import org.stellar.anchor.api.callback.GetFeeResponse; import org.stellar.anchor.api.exception.BadRequestException; import org.stellar.anchor.api.shared.Amount; -import org.stellar.anchor.reference.model.Customer; import org.stellar.anchor.reference.repo.CustomerRepo; @Service @@ -23,39 +21,44 @@ public class FeeService { } public GetFeeResponse getFee(GetFeeRequest request) throws BadRequestException { - if (request.getSendAsset() == null) { - throw new BadRequestException("send_asset cannot be empty."); - } - - if (request.getReceiveAsset() == null) { - throw new BadRequestException("receive_asset cannot be empty."); - } - - if (request.getClientId() == null) { - throw new BadRequestException("client_id cannot be empty."); - } - - if (request.getSendAmount() == null && request.getReceiveAmount() == null) { - throw new BadRequestException("sender_amount or receiver_amount must be present."); - } - - if (request.getSenderId() == null) { - throw new BadRequestException("sender_id cannot be empty."); - } - Optional maybeSender = customerRepo.findById(request.getSenderId()); - if (maybeSender.isEmpty()) { - throw new BadRequestException("sender_id was not found."); - } - - if (request.getReceiverId() == null) { - throw new BadRequestException("receiver_id cannot be empty."); - } - Optional maybeReceiver = customerRepo.findById(request.getReceiverId()); - if (maybeReceiver.isEmpty()) { - throw new BadRequestException("receiver_id was not found."); - } - + // TODO: The following validation only works with SEP-31. The validation must be re-considered + // for SEP-24. + // if (request.getSendAsset() == null) { + // throw new BadRequestException("send_asset cannot be empty."); + // } + // + // if (request.getReceiveAsset() == null) { + // throw new BadRequestException("receive_asset cannot be empty."); + // } + // + // if (request.getClientId() == null) { + // throw new BadRequestException("client_id cannot be empty."); + // } + // + // if (request.getSendAmount() == null && request.getReceiveAmount() == null) { + // throw new BadRequestException("sender_amount or receiver_amount must be present."); + // } + // + // if (request.getSenderId() == null) { + // throw new BadRequestException("sender_id cannot be empty."); + // } + // Optional maybeSender = customerRepo.findById(request.getSenderId()); + // if (maybeSender.isEmpty()) { + // throw new BadRequestException("sender_id was not found."); + // } + // + // if (request.getReceiverId() == null) { + // throw new BadRequestException("receiver_id cannot be empty."); + // } + // Optional maybeReceiver = customerRepo.findById(request.getReceiverId()); + // if (maybeReceiver.isEmpty()) { + // throw new BadRequestException("receiver_id was not found."); + // } + // BigDecimal amount = decimal(request.getSendAmount()); + if (amount == null) { + amount = decimal(request.getReceiveAmount()); + } // fee = feeFixed + feePercent * sendAmount BigDecimal fee = amount.multiply(feePercent).add(feeFixed); return new GetFeeResponse(new Amount(String.valueOf(fee), request.getSendAsset())); diff --git a/anchor-reference-server/src/test/kotlin/org/stellar/anchor/reference/service/FeeServiceTest.kt b/anchor-reference-server/src/test/kotlin/org/stellar/anchor/reference/service/FeeServiceTest.kt deleted file mode 100644 index fa9d678686..0000000000 --- a/anchor-reference-server/src/test/kotlin/org/stellar/anchor/reference/service/FeeServiceTest.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.stellar.anchor.reference.service - -import io.mockk.every -import io.mockk.mockk -import java.util.Optional -import kotlin.test.assertEquals -import org.junit.jupiter.api.Assertions.assertInstanceOf -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.assertThrows -import org.stellar.anchor.api.callback.GetFeeRequest -import org.stellar.anchor.api.exception.BadRequestException -import org.stellar.anchor.reference.model.Customer -import org.stellar.anchor.reference.repo.CustomerRepo - -internal class FeeServiceTest { - companion object { - private const val stellarCircleUSDC = - "stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5" - private const val fiatUSD = "iso4217:USD" - } - - @Test - fun testGetFee_errorHandling() { - val mockCustomerRepo = mockk(relaxed = true) - val feeService = FeeService(mockCustomerRepo) - var feeRequestBuilder = GetFeeRequest.builder() - - // empty send_asset - var ex: BadRequestException = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("send_asset cannot be empty.", ex.message) - - // empty receive_asset - feeRequestBuilder = feeRequestBuilder.sendAsset(fiatUSD) - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("receive_asset cannot be empty.", ex.message) - - // empty client_id - feeRequestBuilder = feeRequestBuilder.receiveAsset(stellarCircleUSDC) - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("client_id cannot be empty.", ex.message) - - // empty sender_amount and receiver_amount - feeRequestBuilder = feeRequestBuilder.clientId("") - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("sender_amount or receiver_amount must be present.", ex.message) - - // empty sender_id - feeRequestBuilder = feeRequestBuilder.sendAmount("123.45") - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("sender_id cannot be empty.", ex.message) - - // not found sender_id - every { mockCustomerRepo.findById("") } returns Optional.empty() - feeRequestBuilder = feeRequestBuilder.senderId("") - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("sender_id was not found.", ex.message) - - // empty receiver_id - val mockCustomer = mockk(relaxed = true) - every { mockCustomerRepo.findById("") } returns Optional.of(mockCustomer) - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("receiver_id cannot be empty.", ex.message) - - // not found receiver_id - every { mockCustomerRepo.findById("") } returns Optional.empty() - feeRequestBuilder = feeRequestBuilder.receiverId("") - ex = assertThrows { feeService.getFee(feeRequestBuilder.build()) } - assertInstanceOf(BadRequestException::class.java, ex) - assertEquals("receiver_id was not found.", ex.message) - - // success - every { mockCustomerRepo.findById("") } returns Optional.of(mockCustomer) - assertDoesNotThrow { feeService.getFee(feeRequestBuilder.build()) } - } -} 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 709f258372..ece061e921 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 @@ -65,20 +65,11 @@ public String toString() { public static class AssetOperation { Boolean enabled; - @SerializedName("fee_fixed") - int feeFixed; - - @SerializedName("fee_percent") - Integer feePercent; - @SerializedName("min_amount") Long minAmount; @SerializedName("max_amount") Long maxAmount; - - @SerializedName("fee_minimum") - Long feeMinimum; } public static class DepositOperation extends AssetOperation {} diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java index 459a2ae29d..74f1565dd9 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java @@ -13,7 +13,7 @@ public class InfoResponse { FeeResponse fee = new FeeResponse(); @SerializedName("features") - FeatureFlagResponse featureFlags = new FeatureFlagResponse(true, true); + FeatureFlagResponse featureFlags = new FeatureFlagResponse(false, false); @SuppressWarnings("unused") @Data diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetFeeResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetFeeResponse.java new file mode 100644 index 0000000000..a7acb93460 --- /dev/null +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/Sep24GetFeeResponse.java @@ -0,0 +1,10 @@ +package org.stellar.anchor.api.sep.sep24; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@AllArgsConstructor +@Data +public class Sep24GetFeeResponse { + String fee; +} diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 2b102f8f14..96b8fd5c5a 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -3,7 +3,6 @@ import static org.stellar.anchor.api.sep.SepTransactionStatus.INCOMPLETE; import static org.stellar.anchor.event.models.TransactionEvent.Type.TRANSACTION_CREATED; import static org.stellar.anchor.sep24.Sep24Helper.*; -import static org.stellar.anchor.sep24.Sep24Transaction.Kind.DEPOSIT; import static org.stellar.anchor.sep24.Sep24Transaction.Kind.WITHDRAWAL; import static org.stellar.anchor.sep9.Sep9Fields.extractSep9Fields; import static org.stellar.anchor.util.Log.*; @@ -20,6 +19,9 @@ import java.net.URISyntaxException; import java.time.Instant; import java.util.*; +import org.stellar.anchor.api.callback.FeeIntegration; +import org.stellar.anchor.api.callback.GetFeeRequest; +import org.stellar.anchor.api.callback.GetFeeResponse; import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.sep.AssetInfo; import org.stellar.anchor.api.sep.sep24.*; @@ -34,12 +36,16 @@ import org.stellar.sdk.Memo; public class Sep24Service { + public static final String OPERATION_WITHDRAW = "withdraw"; + public static final String OPERATION_DEPOSIT = "deposit"; + final AppConfig appConfig; final Sep24Config sep24Config; final AssetService assetService; final JwtService jwtService; final Sep24TransactionStore txnStore; final EventService eventService; + private FeeIntegration feeIntegration; final InteractiveUrlConstructor interactiveUrlConstructor; final MoreInfoUrlConstructor moreInfoUrlConstructor; @@ -52,9 +58,9 @@ public Sep24Service( JwtService jwtService, Sep24TransactionStore txnStore, EventService eventService, + FeeIntegration feeIntegration, InteractiveUrlConstructor interactiveUrlConstructor, MoreInfoUrlConstructor moreInfoUrlConstructor) { - this.moreInfoUrlConstructor = moreInfoUrlConstructor; debug("appConfig:", appConfig); debug("sep24Config:", sep24Config); this.appConfig = appConfig; @@ -63,7 +69,9 @@ public Sep24Service( this.jwtService = jwtService; this.txnStore = txnStore; this.eventService = eventService; + this.feeIntegration = feeIntegration; this.interactiveUrlConstructor = interactiveUrlConstructor; + this.moreInfoUrlConstructor = moreInfoUrlConstructor; info("Sep24Service initialized."); } @@ -246,7 +254,7 @@ public InteractiveTransactionResponse deposit( new Sep24TransactionBuilder(txnStore) .transactionId(txnId) .status(INCOMPLETE.toString()) - .kind(DEPOSIT.toString()) + .kind(Sep24Transaction.Kind.DEPOSIT.toString()) .amountIn(strAmount) .amountOut(strAmount) .assetCode(assetCode) @@ -361,10 +369,6 @@ public InfoResponse getInfo() { info("Getting Sep24 info"); List assets = assetService.listAllAssets(); InfoResponse info = new InfoResponse(); - info.setDeposit(new HashMap<>()); - info.setWithdraw(new HashMap<>()); - info.setFee(new InfoResponse.FeeResponse()); - info.setFeatureFlags(new InfoResponse.FeatureFlagResponse()); debugF("{} assets found", assets.size()); for (AssetInfo asset : assets) { @@ -384,7 +388,7 @@ TransactionResponse fromTxn(Sep24Transaction txn, String lang) txn.getTransactionId(), lang); TransactionResponse response; - if (txn.getKind().equals(DEPOSIT.toString())) { + if (txn.getKind().equals(Sep24Transaction.Kind.DEPOSIT.toString())) { response = fromDepositTxn(txn); } else if (txn.getKind().equals(WITHDRAWAL.toString())) { response = fromWithdrawTxn(txn); @@ -434,4 +438,23 @@ public WithdrawTransactionResponse fromWithdrawTxn(Sep24Transaction txn) return txnR; } + + public Sep24GetFeeResponse getFee(String operation, String type, String assetCode, String amount) + throws AnchorException { + GetFeeRequest getFeeRequest; + switch (operation) { + case OPERATION_WITHDRAW: + getFeeRequest = + GetFeeRequest.builder().receiveAsset(assetCode).receiveAmount(amount).build(); + break; + case OPERATION_DEPOSIT: + getFeeRequest = GetFeeRequest.builder().sendAsset(assetCode).receiveAmount(amount).build(); + break; + default: + throw new SepValidationException(String.format("Invalid operation:%s", operation)); + } + + GetFeeResponse response = feeIntegration.getFee(getFeeRequest); + return new Sep24GetFeeResponse(response.getFee().getAmount()); + } } 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 13d344b8bd..3f2cd46361 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 @@ -14,14 +14,6 @@ internal class Sep24DtoTests { ar.getSep31Enabled() ar.setSep31Enabled(true) - val ao = AssetInfo.AssetOperation() - ao.getFeeFixed() - ao.setFeeFixed(0) - ao.getFeePercent() - ao.setFeePercent(0) - ao.getFeeMinimum() - ao.setFeeMinimum(0) - val so = AssetInfo.SendOperation() so.getFeeFixed() so.setFeeFixed(0) 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 2bd3678623..d23ca55321 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -14,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource import org.junit.jupiter.params.provider.ValueSource import org.stellar.anchor.TestConstants import org.stellar.anchor.TestConstants.Companion.TEST_ACCOUNT @@ -24,12 +25,15 @@ import org.stellar.anchor.TestConstants.Companion.TEST_MEMO import org.stellar.anchor.TestConstants.Companion.TEST_TRANSACTION_ID_0 import org.stellar.anchor.TestConstants.Companion.TEST_TRANSACTION_ID_1 import org.stellar.anchor.TestHelper +import org.stellar.anchor.api.callback.FeeIntegration +import org.stellar.anchor.api.callback.GetFeeResponse import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.exception.SepNotAuthorizedException import org.stellar.anchor.api.exception.SepNotFoundException import org.stellar.anchor.api.exception.SepValidationException import org.stellar.anchor.api.sep.sep24.GetTransactionRequest import org.stellar.anchor.api.sep.sep24.GetTransactionsRequest +import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.auth.JwtService @@ -56,6 +60,7 @@ internal class Sep24ServiceTest { @MockK(relaxed = true) lateinit var secretConfig: SecretConfig @MockK(relaxed = true) lateinit var sep24Config: Sep24Config @MockK(relaxed = true) lateinit var eventService: EventService + @MockK(relaxed = true) lateinit var feeIntegration: FeeIntegration @MockK(relaxed = true) lateinit var txnStore: Sep24TransactionStore @MockK(relaxed = true) lateinit var interactiveUrlConstructor: InteractiveUrlConstructor @MockK(relaxed = true) lateinit var moreInfoUrlConstructor: MoreInfoUrlConstructor @@ -93,6 +98,7 @@ internal class Sep24ServiceTest { jwtService, txnStore, eventService, + feeIntegration, interactiveUrlConstructor, moreInfoUrlConstructor ) @@ -469,6 +475,32 @@ internal class Sep24ServiceTest { assertTrue(response.url.indexOf("lang=en") != -1) } + @ParameterizedTest + @CsvSource( + value = + [ + "withdraw,CASH,USDC,10,0.1", + "withdraw,CASH,USD,10,0.1", + "deposit,CASH,USDC,10,0.1", + "deposit,CASH,USD,10,0.1" + ] + ) + fun `test fee`(op: String, type: String, assetCode: String, amount: String, fee: String) { + // TODO: The tests are to be revised when the Callback API is further revised. + every { feeIntegration.getFee(any()) } returns GetFeeResponse(Amount(fee, assetCode)) + var fee = sep24Service.getFee(op, type, assetCode, amount) + assertEquals("0.1", fee.getFee()) + } + + // @Test + // @CsvSource(value = ["withdraw,CASH"]) + // fun `test fee`(op: String, type: String) { + // return; + //// every { feeIntegration.getFee(any()) } returns GetFeeResponse(Amount(fee, assetCode)) + //// var fee = sep24Service.getFee(op, type, assetCode, amount) + //// assertEquals("0.1", fee.getFee()) + // } + private fun createJwtToken(): JwtToken { return TestHelper.createJwtToken(TEST_ACCOUNT, null, appConfig.hostUrl, TEST_CLIENT_DOMAIN) } diff --git a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt index bd5a1bbc9b..d3a86342a8 100644 --- a/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt +++ b/integration-tests/src/main/kotlin/org/stellar/anchor/platform/Sep24Client.kt @@ -2,6 +2,7 @@ package org.stellar.anchor.platform import org.stellar.anchor.api.sep.sep24.InfoResponse import org.stellar.anchor.api.sep.sep24.InteractiveTransactionResponse +import org.stellar.anchor.api.sep.sep24.Sep24GetFeeResponse import org.stellar.anchor.api.sep.sep24.Sep24GetTransactionResponse class Sep24Client(private val endpoint: String, private val jwt: String) : SepClient() { @@ -31,4 +32,19 @@ class Sep24Client(private val endpoint: String, private val jwt: String) : SepCl val responseBody = httpGet("$endpoint/transaction?id=$id&asset_code=$assetCode", jwt) return gson.fromJson(responseBody, Sep24GetTransactionResponse::class.java) } + + fun getFee( + operation: String, + type: String, + assetCode: String, + amount: String + ): Sep24GetFeeResponse { + println("SEP24 $endpoint/fee") + val responseBody = + httpGet( + "$endpoint/fee?operation=$operation&type=$type&assetCode=$assetCode&amount=$amount", + jwt + ) + return gson.fromJson(responseBody, Sep24GetFeeResponse::class.java) + } } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt index 6ccaeb94fc..10f1c38e15 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt @@ -3,9 +3,9 @@ package org.stellar.anchor.platform import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.assertThrows import org.skyscreamer.jsonassert.JSONAssert -import org.skyscreamer.jsonassert.JSONCompareMode import org.skyscreamer.jsonassert.JSONCompareMode.LENIENT import org.stellar.anchor.api.exception.SepException import org.stellar.anchor.api.platform.PatchTransactionsRequest @@ -25,7 +25,7 @@ class Sep24Tests { fun `test Sep24 info endpoint`() { printRequest("Calling GET /info") val info = sep24Client.getInfo() - JSONAssert.assertEquals(gson.toJson(info), expectedSep24Info, JSONCompareMode.STRICT) + JSONAssert.assertEquals(expectedSep24Info, gson.toJson(info), LENIENT) } fun `test Sep24 withdraw`() { @@ -92,6 +92,23 @@ class Sep24Tests { assertThrows { platformApiClient.getTransaction(txnId) } } } + + fun `test GET fee`() { + var amount = "10.0" + var fee = sep24Client.getFee("withdraw", "CASH", "USDC", amount) + assertTrue(fee.fee.toFloat() < amount.toFloat()) + + fee = sep24Client.getFee("deposit", "CASH", "USDC", amount) + assertTrue(fee.fee.toFloat() < amount.toFloat()) + + amount = "20" + fee = sep24Client.getFee("withdraw", "CASH", "USDC", amount) + assertTrue(fee.fee.toFloat() < amount.toFloat()) + + amount = "20" + fee = sep24Client.getFee("deposit", "CASH", "USDC", amount) + assertTrue(fee.fee.toFloat() < amount.toFloat()) + } } } @@ -105,6 +122,7 @@ fun sep24TestAll() { Sep24Tests.`test PlatformAPI GET transaction for deposit and withdrawal`() Sep24Tests.`test patch, get and compare`() Sep24Tests.`test GET transactions with bad ids`() + Sep24Tests.`test GET fee`() } private const val withdrawRequest = @@ -282,48 +300,33 @@ private const val expectedSep24Info = "deposit": { "JPYC": { "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, "min_amount": 1, - "max_amount": 1000000, - "fee_minimum": 0 + "max_amount": 1000000 }, "USD": { "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, "min_amount": 0, - "max_amount": 10000, - "fee_minimum": 0 + "max_amount": 10000 }, "USDC": { "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, "min_amount": 1, - "max_amount": 1000000, - "fee_minimum": 0 + "max_amount": 1000000 } }, "withdraw": { "JPYC": { "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, "min_amount": 1, "max_amount": 1000000 }, "USD": { "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, "min_amount": 0, "max_amount": 10000 }, "USDC": { "enabled": true, - "fee_fixed": 0, - "fee_percent": 0, "min_amount": 1, "max_amount": 1000000 } @@ -332,8 +335,8 @@ private const val expectedSep24Info = "enabled": true }, "features": { - "account_creation": true, - "claimable_balances": true + "account_creation": false, + "claimable_balances": false } } """ diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json index a17760e029..03f832860f 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/test.json @@ -1,74 +1,19 @@ { - "id": "ecf4fe8d-b61e-4edf-a20f-47c995246e5c", - "sep": 31, - "kind": "receive", - "status": "completed", - "amount_expected": { - "amount": "10", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "amount_in": { - "amount": "10", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "amount_out": { - "amount": "1071.4285982", - "asset": "stellar:JPYC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "amount_fee": { - "amount": "1.00", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "quote_id": "53055040-592f-48fb-8c26-7586064ccc65", - "started_at": "2022-12-23T07:54:49.713Z", - "updated_at": "2022-12-23T07:54:51.038Z", - "completed_at": "2022-12-23T07:54:51.038Z", - "message": "this is the message", - "refunds": { - "amount_refunded": { - "amount": "1", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "amount_fee": { - "amount": "0.1", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "payments": [ - { - "id": "1", - "id_type": "stellar", - "amount": { - "amount": "0.6", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "fee": { - "amount": "0.1", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - } - }, - { - "id": "2", - "id_type": "stellar", - "amount": { - "amount": "0.4", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - }, - "fee": { - "amount": "0", - "asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - } - } - ] - }, - "customers": { - "sender": { - "id": "fa699c8b-16d7-40b3-9159-50fd4df5d439" - }, - "receiver": { - "id": "fa699c8b-16d7-40b3-9159-50fd4df5d439" - } - }, - "creator": { - "account": "GDJLBYYKMCXNVVNABOE66NYXQGIA5AC5D223Z2KF6ZEYK4UBCA7FKLTG" + "transaction": { + "withdraw_memo": "", + "withdraw_memo_type": "none", + "id": "b88c78b0-34c9-4c1a-944c-75777b2a5146", + "kind": "withdrawal", + "status": "incomplete", + "more_info_url": "http://www.stellar.org", + "amount_in": "0", + "amount_out": "0", + "amount_fee": "0", + "started_at": "2022-12-27T07:27:18.225138Z", + "completed_at": "1970-01-01T00:00:00Z", + "stellar_transaction_id": "", + "refunded": false, + "from": "GAIUIZPHLIHQEMNJGSZKCEUWHAZVGUZDBDMO2JXNAJZZZVNSVHQCEWJ4", + "to": "" } -} \ No newline at end of file +} 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 07929a0e95..34d93d0f27 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -67,15 +67,11 @@ assets: "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 }, @@ -148,15 +144,11 @@ assets: "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 }, @@ -226,15 +218,11 @@ assets: "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 }, diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java index 8f9ea1ecab..ee1bb6a666 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java @@ -141,6 +141,7 @@ Sep24Service sep24Service( JwtService jwtService, Sep24TransactionStore sep24TransactionStore, EventService eventService, + FeeIntegration feeIntegration, InteractiveUrlConstructor interactiveUrlConstructor, MoreInfoUrlConstructor moreInfoUrlConstructor) { return new Sep24Service( @@ -150,6 +151,7 @@ Sep24Service sep24Service( jwtService, sep24TransactionStore, eventService, + feeIntegration, interactiveUrlConstructor, moreInfoUrlConstructor); } 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 385a94b4dc..9158d2c71d 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 @@ -13,10 +13,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; -import org.stellar.anchor.api.exception.EventPublishException; -import org.stellar.anchor.api.exception.SepException; -import org.stellar.anchor.api.exception.SepNotFoundException; -import org.stellar.anchor.api.exception.SepValidationException; +import org.stellar.anchor.api.exception.*; import org.stellar.anchor.api.sep.SepExceptionResponse; import org.stellar.anchor.api.sep.sep24.*; import org.stellar.anchor.auth.JwtToken; @@ -181,6 +178,22 @@ public Sep24GetTransactionResponse getTransaction( return getTransaction(request, tr); } + @CrossOrigin(origins = "*") + @RequestMapping( + value = "/fee", + method = {RequestMethod.GET}) + public Sep24GetFeeResponse getFee( + HttpServletRequest request, + @RequestParam(required = false, value = "operation") String operation, + @RequestParam(required = false, value = "type") String type, + @RequestParam(required = false, value = "asset_code") String assetCode, + @RequestParam(required = false, value = "amount") String amount) + throws AnchorException { + debugF( + "/fee operation={} type={} asset_code={}, amount={}", operation, type, assetCode, amount); + return sep24Service.getFee(operation, type, assetCode, amount); + } + String getFullRequestUrl(HttpServletRequest request) { if (request.getQueryString() != null) { return request.getRequestURL() + "?" + request.getQueryString(); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/test.json b/platform/src/test/kotlin/org/stellar/anchor/platform/service/test.json deleted file mode 100644 index e69de29bb2..0000000000 From be852efcdcc85ce628a59c2f287eb964ad0964c8 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 3 Jan 2023 13:20:34 -0800 Subject: [PATCH 0076/1439] [ANCHOR-103] Add kotlin reference server (#686) Add kotlin reference server --- ...rver - ServiceRunner [Example cfg].run.xml | 32 +++++++++++++++++ ...er - ServiceRunner [Int test cfg].run.xml} | 2 +- docker-compose.yaml | 1 + .../example_config/anchor_config.yaml | 2 +- gradle/libs.versions.toml | 12 ++++++- .../anchor-platform-config.yaml | 2 +- kotlin-reference-server/build.gradle.kts | 16 +++++++++ .../com/example/RefenreceServerStart.kt | 6 ++++ .../kotlin/com/example/ReferenceServer.kt | 28 +++++++++++++++ .../main/kotlin/com/example/plugins/Sep24.kt | 34 +++++++++++++++++++ .../anchor-docker-compose-config.yaml | 2 +- .../main/resources/example.anchor-config.yaml | 2 +- service-runner/build.gradle.kts | 1 + .../anchor/platform/ServiceRunner.java | 12 +++++++ settings.gradle.kts | 1 + 15 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 .run/1. Platform Server - ServiceRunner [Example cfg].run.xml rename .run/{1. Platform Server - ServiceRunner.run.xml => 1. Platform Server - ServiceRunner [Int test cfg].run.xml} (94%) create mode 100644 kotlin-reference-server/build.gradle.kts create mode 100644 kotlin-reference-server/src/main/kotlin/com/example/RefenreceServerStart.kt create mode 100644 kotlin-reference-server/src/main/kotlin/com/example/ReferenceServer.kt create mode 100644 kotlin-reference-server/src/main/kotlin/com/example/plugins/Sep24.kt diff --git a/.run/1. Platform Server - ServiceRunner [Example cfg].run.xml b/.run/1. Platform Server - ServiceRunner [Example cfg].run.xml new file mode 100644 index 0000000000..62ff02e1ec --- /dev/null +++ b/.run/1. Platform Server - ServiceRunner [Example cfg].run.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.run/1. Platform Server - ServiceRunner.run.xml b/.run/1. Platform Server - ServiceRunner [Int test cfg].run.xml similarity index 94% rename from .run/1. Platform Server - ServiceRunner.run.xml rename to .run/1. Platform Server - ServiceRunner [Int test cfg].run.xml index 5804a71022..59b9ac1174 100644 --- a/.run/1. Platform Server - ServiceRunner.run.xml +++ b/.run/1. Platform Server - ServiceRunner [Int test cfg].run.xml @@ -1,5 +1,5 @@ - + diff --git a/docker-compose.yaml b/docker-compose.yaml index 64d688a365..671a24bbaf 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,6 +24,7 @@ services: ports: - "8080:8080" - "8081:8081" + - "8091:8091" db: image: postgres:latest 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 d60349961f..3ce63bd928 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 @@ -150,7 +150,7 @@ app-config: interactiveJwtExpiration: 3600 # The interactive URL where the platform server will redirect to start the SEP-24 interactive flow. - interactiveUrl: http://localhost:8081/sep24/interactive + interactive_url: http://localhost:8091/sep24/interactive # sep-31 sep31: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9036bf1147..5b37aafc40 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,8 @@ junit-suite-engine = "1.8.2" kafka = "3.3.1" kafka-json-schema = "7.0.1" kotlin = "1.7.20" +kotlin-logging = "3.0.2" +ktor = "2.2.1" log4j = "2.19.0" log4j-template-json = "2.19.0" lombok = "1.18.22" @@ -45,6 +47,7 @@ spring-aws-messaging = "2.2.6.RELEASE" aws-java-sdk-s3 = "1.12.342" sqlite-jdbc = "3.34.0" slf4j = "1.7.35" +slf4j2 = "2.0.5" toml4j = "0.7.2" # Plugin versions @@ -85,6 +88,10 @@ 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" } +kotlin-logging = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlin-logging" } +ktor-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" } +ktor-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } +ktor-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor" } 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" } @@ -105,6 +112,7 @@ 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" } +slf4j2-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j2" } 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" } @@ -114,9 +122,11 @@ aws-java-sdk-s3 = { module = "com.amazonaws:aws-java-sdk-s3", version.ref = "aws slf4j = ["slf4j-api", "slf4j-log4j12"] junit = ["junit-api", "junit-engine", "junit-params", "junit-suite-engine", "kotlin-stdlib", "kotlin-junit", "mockk"] kafka = ["kafka-clients", "kafka-connect", "kafka-json-schema"] +ktor = ["ktor-core", "ktor-netty", "ktor-yaml"] [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 +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } \ 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 index 5766de8e98..4bb7a60d72 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 @@ -63,7 +63,7 @@ sep12: sep24: enabled: true - interactiveUrl: http://host.docker.internal:8081/sep24/interactive + interactive_url: http://host.docker.internal:8091/sep24/interactive sep31: enabled: true diff --git a/kotlin-reference-server/build.gradle.kts b/kotlin-reference-server/build.gradle.kts new file mode 100644 index 0000000000..94fba0c0a7 --- /dev/null +++ b/kotlin-reference-server/build.gradle.kts @@ -0,0 +1,16 @@ +// The alias call in plugins scope produces IntelliJ false error which is suppressed here. +@Suppress("DSL_SCOPE_VIOLATION") +plugins { + application + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.ktor) +} + +dependencies { + implementation(libs.bundles.ktor) + + implementation(libs.slf4j2.simple) + implementation(libs.kotlin.logging) +} + +application { mainClass.set("com.example.ReferenceServerKt") } diff --git a/kotlin-reference-server/src/main/kotlin/com/example/RefenreceServerStart.kt b/kotlin-reference-server/src/main/kotlin/com/example/RefenreceServerStart.kt new file mode 100644 index 0000000000..e9c53d8dec --- /dev/null +++ b/kotlin-reference-server/src/main/kotlin/com/example/RefenreceServerStart.kt @@ -0,0 +1,6 @@ +package com.example + +// Used in ServiceRunner +@JvmOverloads // Annotation required to call from Java with optional argument +fun start(port: Int, waitServer: Boolean = false) = + main(arrayOf(port.toString(), waitServer.toString())) diff --git a/kotlin-reference-server/src/main/kotlin/com/example/ReferenceServer.kt b/kotlin-reference-server/src/main/kotlin/com/example/ReferenceServer.kt new file mode 100644 index 0000000000..dad27b5bd0 --- /dev/null +++ b/kotlin-reference-server/src/main/kotlin/com/example/ReferenceServer.kt @@ -0,0 +1,28 @@ +package com.example + +import com.example.plugins.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.routing.* +import mu.KotlinLogging + +const val DEFAULT_KOTLIN_REFERENCE_SERVER_PORT = 8091 + +val log = KotlinLogging.logger {} + +fun main(args: Array) { + log.info { "Starting Kotlin reference server" } + + embeddedServer( + Netty, + port = args.getOrNull(0)?.toIntOrNull() ?: DEFAULT_KOTLIN_REFERENCE_SERVER_PORT + ) { + configureRouting() + } + .start(args.getOrNull(1)?.toBooleanStrictOrNull() ?: true) +} + +fun Application.configureRouting() { + routing { sep24() } +} diff --git a/kotlin-reference-server/src/main/kotlin/com/example/plugins/Sep24.kt b/kotlin-reference-server/src/main/kotlin/com/example/plugins/Sep24.kt new file mode 100644 index 0000000000..4862c25d8e --- /dev/null +++ b/kotlin-reference-server/src/main/kotlin/com/example/plugins/Sep24.kt @@ -0,0 +1,34 @@ +package com.example.plugins + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import mu.KotlinLogging + +val log = KotlinLogging.logger {} + +fun Route.sep24() { + route("/sep24/interactive") { + get { + log.info("Called /sep24/interactive with parameters ${call.parameters}") + + val operation = + call.parameters["operation"] + ?: return@get call.respondText( + "Missing operation parameter", + status = HttpStatusCode.BadRequest + ) + + when (operation.lowercase()) { + "deposit" -> call.respondText("The sep24 interactive DEPOSIT starts here.") + "withdraw" -> call.respondText("The sep24 interactive WITHDRAW starts here.") + else -> + call.respondText( + "The only supported operations are \"deposit\" or \"withdraw\"", + status = HttpStatusCode.BadRequest + ) + } + } + } +} diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 5d392c1f9e..4cead2a30a 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -63,7 +63,7 @@ sep12: sep24: enabled: true - interactiveUrl: http://localhost:8081/sep24/interactive + interactive_url: http://localhost:8091/sep24/interactive sep31: enabled: true diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml index 34d3833133..bcd3a1e8ec 100644 --- a/platform/src/main/resources/example.anchor-config.yaml +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -83,7 +83,7 @@ sep12: sep24: enabled: true - interactiveUrl: http://localhost:8081/sep24/interactive + interactive_url: http://localhost:8091/sep24/interactive sep31: enabled: true diff --git a/service-runner/build.gradle.kts b/service-runner/build.gradle.kts index eae9f4e78e..735726fe99 100644 --- a/service-runner/build.gradle.kts +++ b/service-runner/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { // From projects implementation(project(":platform")) implementation(project(":anchor-reference-server")) + implementation(project(":kotlin-reference-server")) } tasks { 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 f391c58864..1971a1b083 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,8 @@ package org.stellar.anchor.platform; +import static com.example.ReferenceServerKt.DEFAULT_KOTLIN_REFERENCE_SERVER_PORT; + +import com.example.RefenreceServerStartKt; import java.util.Map; import org.apache.commons.cli.*; import org.springframework.context.ConfigurableApplicationContext; @@ -55,11 +58,20 @@ static ConfigurableApplicationContext startStellarObserver(Map e static void startAnchorReferenceServer() { String strPort = System.getProperty("ANCHOR_REFERENCE_SERVER_PORT"); + String kStrPort = System.getProperty("KOTLIN_REFERENCE_SERVER_PORT"); + int port = DEFAULT_ANCHOR_REFERENCE_SERVER_PORT; + int kPort = DEFAULT_KOTLIN_REFERENCE_SERVER_PORT; + if (strPort != null) { port = Integer.parseInt(strPort); } + if (kStrPort != null) { + kPort = Integer.parseInt(kStrPort); + } + AnchorReferenceServer.start(port, "/"); + RefenreceServerStartKt.start(kPort); } static void printUsage(Options options) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 5802d75825..01eada9fae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ include("platform") /** Anchor Reference Server */ include("anchor-reference-server") +include("kotlin-reference-server") /** Integration tests */ include("integration-tests") From 3054e1e3b8d990bf016b53ce49b4cf60558c55dc Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 3 Jan 2023 16:54:40 -0800 Subject: [PATCH 0077/1439] Fix SEP-24 schema (#689) --- .../anchor/api/sep/sep24/TransactionResponse.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java index 1c31f4b44e..194e73eeb9 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/TransactionResponse.java @@ -19,19 +19,19 @@ public class TransactionResponse { String moreInfoUrl = "http://www.stellar.org"; @SerializedName("amount_in") - String amountIn = "0"; + String amountIn; @SerializedName("amount_in_asset") String amountInAsset; @SerializedName("amount_out") - String amountOut = "0"; + String amountOut; @SerializedName("amount_out_asset") String amountOutAsset; @SerializedName("amount_fee") - String amountFee = "0"; + String amountFee; @SerializedName("amount_fee_asset") String amountFeeAsset; @@ -40,10 +40,10 @@ public class TransactionResponse { Instant startedAt; @SerializedName("completed_at") - Instant completedAt = Instant.EPOCH; + Instant completedAt; @SerializedName("stellar_transaction_id") - String stellarTransactionId = ""; + String stellarTransactionId; @SerializedName("external_transaction_id") String externalTransactionId; @@ -51,6 +51,6 @@ public class TransactionResponse { String message; Boolean refunded = false; Refunds refunds; - String from = ""; - String to = ""; + String from; + String to; } From 34515f132da834dfb23065dfe08e5c22f294cccf Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 10 Jan 2023 09:13:45 +0800 Subject: [PATCH 0078/1439] docs: [ANCHOR-126] Simplify the component schemas (#698) * Simplify the transaction event of the Event Schema * Reverse order of SEPs of the states description * Added missing paths: field * merge TransactionEvent and QuoteEvent * extracted schema.yml --- .../Communication/Callbacks API.yml | 235 +-------- .../Communication/Events Schema.yml | 269 +--------- .../Communication/Platform API.yml | 287 +--------- .../Communication/schemas.yml | 489 ++++++++++++++++++ 4 files changed, 524 insertions(+), 756 deletions(-) create mode 100644 docs/03 - Implementing the Anchor Server/Communication/schemas.yml 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 b02eea7036..75d17cdc67 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Callbacks API.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Callbacks API.yml @@ -1,11 +1,11 @@ openapi: 3.0.0 info: + version: "2.0.0" description: | The Synchronous Callbacks API specification for the Stellar Anchor Platform project. The Synchronous Callbacks API defines requests made by the Platform while it is processing a request from a client application. The anchor's responses to these requests affect the Platform responses to the client application. - version: "1.0" title: Synchronous Callbacks API tags: - name: "Unique Address" @@ -78,7 +78,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /fee: get: description: | @@ -158,7 +158,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/FeeResponse' + $ref: './schemas.yml#/components/schemas/FeeResponse' '422': description: | Unprocessable Entity. This status should be returned when the anchor understood the request but cannot @@ -169,13 +169,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' '500': description: Error. The Platform will respond to the client application with the same response code and body. content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /rate: get: description: | @@ -297,7 +297,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RateResponse' + $ref: './schemas.yml#/components/schemas/RateResponse' '422': description: | Unprocessable Entity. This status should be returned when the anchor understood the request but cannot @@ -307,13 +307,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' '500': description: Error. The Platform will respond to the client application with the same response code and body. content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /customer: get: description: | @@ -385,13 +385,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' '404': description: Not Found. content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' put: tags: - Customers @@ -406,26 +406,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PutCustomerRequest' + $ref: './schemas.yml#/components/schemas/PutCustomerRequest' responses: '200': description: Success. content: application/json: schema: - $ref: '#/components/schemas/PutCustomerResponse' + $ref: './schemas.yml#/components/schemas/PutCustomerResponse' '400': description: Invalid data. content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' '404': description: Not Found. content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /customer/callback: put: tags: @@ -448,7 +448,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PutCustomerCallbackRequest' + $ref: './schemas.yml#/components/schemas/PutCustomerCallbackRequest' responses: '204': description: "Success" @@ -457,13 +457,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' '404': description: Customer not found. content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /customer/{id}: delete: tags: @@ -486,203 +486,4 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' -components: - schemas: - PutCustomerRequest: - type: object - properties: - id: - description: The ID of the customer as returned in the response of a previous PUT request. - type: string - account: - description: The Stellar or Muxed Account authenticated with the Platform via SEP-10. - type: string - memo: - description: The memo value identifying a customer with a shared account, where the shared account is `account`. - type: string - memo_type: - description: The type of memo used to identify a customer with a shared account. - type: string - enum: - - id - - hash - - text - type: - description: | - The type of action the customer is being KYCd for. See the - [Type Specification](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#type-specification) - documented in SEP-12. - type: string - first_name: - type: string - last_name: - type: string - additional_name: - type: string - address_country_code: - type: string - state_or_province: - type: string - city: - type: string - postal_code: - type: string - address: - type: string - mobile_number: - type: string - email_address: - type: string - birth_date: - type: string - format: date - birth_place: - type: string - birth_country_code: - type: string - bank_account_number: - type: string - bank_number: - type: string - bank_phone_number: - type: string - bank_branch_number: - type: string - tax_id: - type: string - tax_id_name: - type: string - occupation: - type: string - employer_name: - type: string - employer_address: - type: string - language_code: - type: string - id_type: - type: string - id_country_code: - type: string - id_issue_date: - type: string - format: date - id_expiration_date: - type: string - format: date - id_number: - type: string - ip_address: - type: string - sex: - type: string - PutCustomerResponse: - type: object - properties: - id: - type: string - PutCustomerCallbackRequest: - type: object - properties: - id: - description: The ID of the customer as returned in the response of a previous PUT request. - type: string - account: - description: The Stellar or Muxed Account authenticated with the Platform via SEP-10. - type: string - memo: - description: The memo value identifying a customer with a shared account, where the shared account is `account`. - type: string - memo_type: - description: The type of memo used to identify a customer with a shared account. - type: string - enum: - - id - - hash - - text - url: - description: A callback URL that the SEP-12 server will POST to when the status of the customer changes. - type: string - FeeResponse: - type: object - properties: - fee: - $ref: '#/components/schemas/Amount' - RateResponse: - type: object - properties: - rate: - type: object - properties: - id: - type: string - description: Id of the firm quote. NOT USED when `type=indicative*`. - expires_at: - type: string - format: date-time - description: Expirations are NOT USED when `type=indicative*`. - price: - type: string - description: The conversion price offered by the anchor for one unit of `buy_asset` in terms of `sell_asset`, without including fees. In traditional finance, `buy_asset` would be referred to as the base asset and `sell_asset` as the counter asset. The formula `sell_amount - fee = price * buy_amount` must hold true ([ref](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md#price-formulas)). - total_price: - type: string - description: The total conversion price offered by the anchor for one unit of `buy_asset` in terms of `sell_asset`, including fees. In traditional finance, `buy_asset` would be referred to as the base asset and `sell_asset` as the counter asset. NOT USED when `type=indicative_prices`. The formula `sell_amount = total_price * buy_amount` must hold true ([ref](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md#price-formulas)). - sell_amount: - type: string - description: The amount of `sell_asset` the anchor will exchange for `buy_asset`. It could be different from the `sell_amount` provided in the request, depending on how fees are applied by the Anchor. - buy_amount: - type: string - description: The amount of `buy_asset` the anchor will provide with `sell_asset`. It could be different from the `buy_amount` provided in the request, depending on how fees are applied by the Anchor. - fee: - $ref: '#/components/schemas/RateFeeResponse' - required: - - price - - sell_amount - - buy_amount - RateFeeResponse: - type: object - description: Fees are NOT USED when `type=indicative_prices`. - properties: - total: - type: string - description: The total fee to be applied. - asset: - type: string - description: The asset the fee will be charged in. Must be represented in [Asset Identification Format](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md#asset-identification-format). - details: - type: array - items: - $ref: '#/components/schemas/RateFeeDetailResponse' - required: - - total - - asset - RateFeeDetailResponse: - type: object - properties: - name: - type: string - description: The name of the fee, for example `ACH fee`, `Brazilian conciliation fee`, `Service fee`, etc. - description: - type: string - description: A text describing the fee. - amount: - type: string - description: The amount of asset applied. If `fee.details` is provided, `sum(fee.details.amount)` should be equals `fee.total`. - required: - - name - - amount - Amount: - type: object - properties: - amount: - type: string - asset: - type: string - Error: - type: object - properties: - error: - type: string - required: - - error + $ref: './schemas.yml#/components/schemas/Error' diff --git a/docs/03 - Implementing the Anchor Server/Communication/Events Schema.yml b/docs/03 - Implementing the Anchor Server/Communication/Events Schema.yml index 0c9585d720..58a2154cfb 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Events Schema.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Events Schema.yml @@ -1,6 +1,6 @@ openapi: 3.0.0 info: - version: "0.1" + version: "2.0.0" title: Event Schemas description: | This document specifies the schemas used for Events in Stellar Anchor Platform project. @@ -11,269 +11,8 @@ info: tags: - name: "Event Schemas" description: "Event Schemas for events that are published to a queue for the Anchor to consume." +paths: { } components: schemas: - QuoteEvent: - type: object - properties: - event_id: - type: string - type: - type: string - enum: - - quote_created - description: - quote_created - a quote was created via the SEP38 API - id: - type: string - sell_asset: - type: string - sell_amount: - type: string - buy_asset: - type: string - buy_amount: - type: string - expires_at: - type: string - format: date-time - price: - type: string - description: price without including fees. - total_price: - type: string - description: price including fees. - creator: - $ref: '#/components/schemas/StellarId' - transaction_id: - type: string - created_at: - type: string - format: date-time - fee: - type: object - properties: - total: - type: string - asset: - type: string - details: - type: array - items: - type: object - properties: - name: - type: string - description: - type: string - amount: - type: string - required: - - name - - amount - required: - - total - - asset - required: - - event_id - - type - - id - - sell_asset - - sell_amount - - buy_asset - - buy_amount - - expires_at - - price - - total_price - - created_at - TransactionEvent: - type: object - properties: - event_id: - type: string - type: - type: string - enum: - - transaction_created - - transaction_status_changed - - transaction_error - description: | - The transaction event type. Can be one of the following: - - `transaction_created` - a transaction was created through the SEP endpoints. - - `transaction_status_changed` - the status of a transaction has changed. The detail of the status change is described in the `status_change` field. - - `transaction_error` - error processing the transaction. - - id: - type: string - description: "The id of the transaction." - sep: - type: integer - description: "The SEP related to this TransactionEvent. Currently, only `31` is supported." - enum: [31] - kind: - type: string - description: "The kind of transaction event. Currently, the only supported one is `receive`." - enum: ["receive"] - status: - type: string - description: | - The status of the transaction according with the latest Platform update. Currently, we're supporting all - [SEP-31 states](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0031.md#get-transaction), exept for `pending_transaction_info_update`. - enum: [ - "pending_sender", - "pending_stellar", - "pending_customer_info_update", - "pending_receiver", - "pending_external", - "completed", - "expired", - "error" - ] - status_change: - $ref: '#/components/schemas/StatusChange' - amount_expected: - $ref: '#/components/schemas/Amount' - amount_in: - $ref: '#/components/schemas/Amount' - amount_out: - $ref: '#/components/schemas/Amount' - amount_fee: - $ref: '#/components/schemas/Amount' - quote_id: - type: string - description: "The id of the [SEP-38](https://stellar.org/protocol/sep-38) firm quote used for the transaction." - started_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - completed_at: - type: string - format: date-time - description: "When the transaction was marked as `completed` or when it was fully refunded." - transfer_received_at: - type: string - format: date-time - description: "When the Stellar transaction related to this object was received." - message: - type: string - description: "This field may contain any clarifying message related to this TransactionEvent or to the transaction itself." - refunds: - $ref: '#/components/schemas/Refunds' - stellar_transactions: - type: array - items: - $ref: '#/components/schemas/StellarTransaction' - external_transaction_id: - type: string - description: "The id of an external transaction to be handled by the Anchor Backend." - custodial_transaction_id: - type: string - source_account: - type: string - description: "The Stellar account whose balance was debited in the payment that originated this event. Should be present if this event was originated from a Stellar payment." - destination_account: - type: string - description: "The Stellar account who received the payment that originated this event. Should be present if this event was originated from a Stellar payment." - customers: - type: object - description: | - The Identification info of the sending and receiving customers. If they were created through [SEP-12](https://stellar.org/protocol/sep-12), - this object should contain the SEP-12 customer `id`. Otherwise, the `account` address of the customer. - properties: - sender: - $ref: '#/components/schemas/StellarId' - receiver: - $ref: '#/components/schemas/StellarId' - creator: - $ref: '#/components/schemas/StellarId' - Amount: - type: object - properties: - amount: - type: string - asset: - type: string - StatusChange: - type: object - properties: - from: - type: string - to: - type: string - Refunds: - type: object - properties: - amount_refunded: - $ref: '#/components/schemas/Amount' - amount_fee: - $ref: '#/components/schemas/Amount' - payments: - type: array - items: - type: object - properties: - id: - type: string - id_type: - type: string - enum: - - stellar - - external - amount: - $ref: '#/components/schemas/Amount' - fee: - $ref: '#/components/schemas/Amount' - requested_at: - type: string - format: date-time - refunded_at: - type: string - format: date-time - StellarId: - type: object - description: | - StellarId's are objects that identify end-users and SEP-31 Sending Anchors, but not SEP-31 Receiving Anchors. - - For a SEP-12 customer, the `id` field should be sufficient to fully identify the customer in the Anchor Backend. - - For a SEP-31 Sending Anchor, the `account` field should be used. - properties: - id: - type: string - description: The `id` of the customer registered through SEP-12. - account: - type: string - description: Either the Stellar account or Muxed account address of the on-chain entity. - StellarTransaction: - type: object - properties: - id: - type: string - memo: - type: string - memo_type: - type: string - enum: - - text - - hash - - id - created_at: - type: string - format: date-time - envelope: - type: string - payments: - type: array - items: - type: object - properties: - operationId: - type: string - source_account: - type: string - destination_account: - type: string - amount: - $ref: '#/components/schemas/Amount' \ No newline at end of file + Event: + $ref: './schemas.yml#/components/schemas/Event' 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 5d76962f22..8e7cb86261 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/Platform API.yml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: + version: "2.0.0" description: The Platform API specification for the Stellar Anchor Platform project. - version: "1.0.1" title: Platform API tags: - name: "Transactions" @@ -90,7 +90,7 @@ paths: records: type: array items: - $ref: '#/components/schemas/Transaction' + $ref: './schemas.yml#/components/schemas/Transaction' cursor: type: string '400': @@ -124,7 +124,7 @@ paths: records: type: array items: - $ref: '#/components/schemas/Transaction' + $ref: './schemas.yml#/components/schemas/Transaction' '400': description: 'Invalid request body. The error returned pertains to the transaction first determined to be invalid.' content: @@ -146,7 +146,7 @@ paths: records: type: array items: - $ref: '#/components/schemas/PatchTransaction' + $ref: './schemas.yml#/components/schemas/PatchTransaction' /transactions/{id}: get: tags: @@ -168,19 +168,19 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Transaction' + $ref: './schemas.yml#/components/schemas/Transaction' '400': description: "Bad Request" content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' '404': description: "Transaction not found." content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /exchange/quotes: get: tags: @@ -240,7 +240,7 @@ paths: records: type: array items: - $ref: '#/components/schemas/Quote' + $ref: './schemas.yml#/components/schemas/Quote' cursor: type: string '400': @@ -248,7 +248,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: './schemas.yml#/components/schemas/Error' /exchange/quotes/{id}: get: tags: @@ -271,274 +271,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Quote' + $ref: './schemas.yml#/components/schemas/Quote' '404': description: "Not Found." content: application/json: schema: - $ref: '#/components/schemas/Error' -components: - schemas: - Transaction: - type: object - properties: - id: - type: string - sep: - type: integer - enum: [ 24, 31 ] - kind: - type: string - enum: [ - # SEP24 - "deposit", - "withdrawal", - #SEP31 - "receive" - ] - status: - type: string - enum: [ - # Shared - incomplete, - completed, - refunded, - expired, - error, - pending_stellar, - pending_external, - # SEP24 - pending_user_transfer_start, - pending_user_transfer_complete, - pending_anchor, - pending_trust, - pending_user, - no_market, - too_small, - too_large, - # SEP31 - pending_sender, - pending_receiver, - pending_transaction_info_update, - pending_customer_info_update - ] - amount_expected: - $ref: '#/components/schemas/Amount' - amount_in: - $ref: '#/components/schemas/Amount' - amount_out: - $ref: '#/components/schemas/Amount' - amount_fee: - $ref: '#/components/schemas/Amount' - quote_id: - type: string - started_at: - type: string - format: date-time - updated_at: - type: string - format: date-time - completed_at: - type: string - format: date-time - transfer_received_at: - type: string - format: date-time - message: - type: string - refunds: - $ref: '#/components/schemas/Refunds' - stellar_transactions: - type: array - items: - $ref: '#/components/schemas/StellarTransaction' - external_transaction_id: - type: string - custodial_transaction_id: - type: string - customers: - type: object - description: | - The Identification info of the sending and receiving customers. If they were created through [SEP-12](https://stellar.org/protocol/sep-12), - this object should contain the SEP-12 customer `id`. Otherwise, the `account` address of the customer. - properties: - sender: - $ref: '#/components/schemas/StellarId' - receiver: - $ref: '#/components/schemas/StellarId' - creator: - $ref: '#/components/schemas/StellarId' - PatchTransaction: - type: object - required: - - id - properties: - id: - type: string - status: - type: string - enum: [ - "pending_stellar", - "pending_customer_info_update", - "pending_receiver", - "pending_external", - "completed", - "error" - ] - amount_in: - $ref: '#/components/schemas/Amount' - amount_out: - $ref: '#/components/schemas/Amount' - amount_fee: - $ref: '#/components/schemas/Amount' - transfer_received_at: - type: string - format: date-time - message: - type: string - refund: - $ref: '#/components/schemas/Refunds' - external_transaction_id: - type: string - Amount: - type: object - required: - - amount - - asset - properties: - amount: - type: string - asset: - type: string - Refunds: - type: object - properties: - amount_refunded: - $ref: '#/components/schemas/Amount' - amount_fee: - $ref: '#/components/schemas/Amount' - payments: - type: array - items: - type: object - properties: - id: - type: string - id_type: - type: string - enum: - - stellar - - external - amount: - $ref: '#/components/schemas/Amount' - fee: - $ref: '#/components/schemas/Amount' - requested_at: - type: string - format: date-time - refunded_at: - type: string - format: date-time - Quote: - type: object - properties: - id: - type: string - sell_asset: - type: string - buy_asset: - type: string - expires_at: - type: string - format: date-time - price: - type: string - creator: - $ref: '#/components/schemas/StellarId' - transaction_id: - type: string - created_at: - type: string - format: date-time - StellarId: - type: object - description: | - StellarId's are objects that identify end-users and SEP-31 Sending Anchors, but not SEP-31 Receiving Anchors. + $ref: './schemas.yml#/components/schemas/Error' - For a SEP-12 customer, the `id` field should be sufficient to fully identify the customer in the Anchor Backend. - - For a SEP-31 Sending Anchor, the `account` field should be used. - properties: - id: - type: string - description: The `id` of the customer registered through SEP-12. - account: - type: string - description: Either the Stellar account or Muxed account address of the on-chain entity. - StellarTransaction: - type: object - required: - - id - - 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 - - id - 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: - 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: - type: object - properties: - error: - type: string - id: - type: string - required: - - error \ No newline at end of file +# CHANGELOG +## v2.0.0: Simplified the Event by merging the QuoteEvent and Transaction Event and embedding the detail in the `transaction` and `quote` field. Removed unused schemas. diff --git a/docs/03 - Implementing the Anchor Server/Communication/schemas.yml b/docs/03 - Implementing the Anchor Server/Communication/schemas.yml new file mode 100644 index 0000000000..afb63fa1db --- /dev/null +++ b/docs/03 - Implementing the Anchor Server/Communication/schemas.yml @@ -0,0 +1,489 @@ +openapi: 3.0.0 +info: + version: "2.0.0" + title: Component Schemas + description: | + This document specifies the schemas used for all APIs. +tags: + - name: "Component Schemas" + description: "Component Schemas" +paths: { } +components: + schemas: + PutCustomerRequest: + type: object + properties: + id: + description: The ID of the customer as returned in the response of a previous PUT request. + type: string + account: + description: The Stellar or Muxed Account authenticated with the Platform via SEP-10. + type: string + memo: + description: The memo value identifying a customer with a shared account, where the shared account is `account`. + type: string + memo_type: + description: The type of memo used to identify a customer with a shared account. + type: string + enum: + - id + - hash + - text + type: + description: | + The type of action the customer is being KYCd for. See the + [Type Specification](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#type-specification) + documented in SEP-12. + type: string + first_name: + type: string + last_name: + type: string + additional_name: + type: string + address_country_code: + type: string + state_or_province: + type: string + city: + type: string + postal_code: + type: string + address: + type: string + mobile_number: + type: string + email_address: + type: string + birth_date: + type: string + format: date + birth_place: + type: string + birth_country_code: + type: string + bank_account_number: + type: string + bank_number: + type: string + bank_phone_number: + type: string + bank_branch_number: + type: string + tax_id: + type: string + tax_id_name: + type: string + occupation: + type: string + employer_name: + type: string + employer_address: + type: string + language_code: + type: string + id_type: + type: string + id_country_code: + type: string + id_issue_date: + type: string + format: date + id_expiration_date: + type: string + format: date + id_number: + type: string + ip_address: + type: string + sex: + type: string + + PutCustomerResponse: + type: object + properties: + id: + type: string + + PutCustomerCallbackRequest: + type: object + properties: + id: + description: The ID of the customer as returned in the response of a previous PUT request. + type: string + account: + description: The Stellar or Muxed Account authenticated with the Platform via SEP-10. + type: string + memo: + description: The memo value identifying a customer with a shared account, where the shared account is `account`. + type: string + memo_type: + description: The type of memo used to identify a customer with a shared account. + type: string + enum: + - id + - hash + - text + url: + description: A callback URL that the SEP-12 server will POST to when the status of the customer changes. + type: string + + FeeResponse: + type: object + properties: + fee: + $ref: '#/components/schemas/Amount' + + RateResponse: + type: object + properties: + rate: + type: object + properties: + id: + type: string + description: Id of the firm quote. NOT USED when `type=indicative*`. + expires_at: + type: string + format: date-time + description: Expirations are NOT USED when `type=indicative*`. + price: + type: string + description: The conversion price offered by the anchor for one unit of `buy_asset` in terms of `sell_asset`, without including fees. In traditional finance, `buy_asset` would be referred to as the base asset and `sell_asset` as the counter asset. The formula `sell_amount - fee = price * buy_amount` must hold true ([ref](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md#price-formulas)). + total_price: + type: string + description: The total conversion price offered by the anchor for one unit of `buy_asset` in terms of `sell_asset`, including fees. In traditional finance, `buy_asset` would be referred to as the base asset and `sell_asset` as the counter asset. NOT USED when `type=indicative_prices`. The formula `sell_amount = total_price * buy_amount` must hold true ([ref](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md#price-formulas)). + sell_amount: + type: string + description: The amount of `sell_asset` the anchor will exchange for `buy_asset`. It could be different from the `sell_amount` provided in the request, depending on how fees are applied by the Anchor. + buy_amount: + type: string + description: The amount of `buy_asset` the anchor will provide with `sell_asset`. It could be different from the `buy_amount` provided in the request, depending on how fees are applied by the Anchor. + fee: + $ref: '#/components/schemas/RateFeeResponse' + required: + - price + - sell_amount + - buy_amount + + RateFeeResponse: + type: object + description: Fees are NOT USED when `type=indicative_prices`. + properties: + total: + type: string + description: The total fee to be applied. + asset: + type: string + description: The asset the fee will be charged in. Must be represented in [Asset Identification Format](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0038.md#asset-identification-format). + details: + type: array + items: + $ref: '#/components/schemas/RateFeeDetailResponse' + required: + - total + - asset + + RateFeeDetailResponse: + type: object + properties: + name: + type: string + description: The name of the fee, for example `ACH fee`, `Brazilian conciliation fee`, `Service fee`, etc. + description: + type: string + description: A text describing the fee. + amount: + type: string + description: The amount of asset applied. If `fee.details` is provided, `sum(fee.details.amount)` should be equals `fee.total`. + required: + - name + - amount + + Transaction: + type: object + properties: + id: + type: string + sep: + type: integer + enum: [ 24, 31 ] + kind: + type: string + enum: [ + # SEP24 + "deposit", + "withdrawal", + #SEP31 + "receive" + ] + status: + type: string + enum: [ + # Shared + incomplete, + completed, + refunded, + expired, + error, + pending_stellar, + pending_external, + # SEP24 + pending_user_transfer_start, + pending_user_transfer_complete, + pending_anchor, + pending_trust, + pending_user, + no_market, + too_small, + too_large, + # SEP31 + pending_sender, + pending_receiver, + pending_transaction_info_update, + pending_customer_info_update + ] + amount_expected: + $ref: '#/components/schemas/Amount' + amount_in: + $ref: '#/components/schemas/Amount' + amount_out: + $ref: '#/components/schemas/Amount' + amount_fee: + $ref: '#/components/schemas/Amount' + quote_id: + type: string + started_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + completed_at: + type: string + format: date-time + transfer_received_at: + type: string + format: date-time + message: + type: string + refunds: + $ref: '#/components/schemas/Refunds' + stellar_transactions: + type: array + items: + $ref: '#/components/schemas/StellarTransaction' + external_transaction_id: + type: string + custodial_transaction_id: + type: string + customers: + type: object + description: | + The Identification info of the sending and receiving customers. If they were created through [SEP-12](https://stellar.org/protocol/sep-12), + this object should contain the SEP-12 customer `id`. Otherwise, the `account` address of the customer. + properties: + sender: + $ref: '#/components/schemas/StellarId' + receiver: + $ref: '#/components/schemas/StellarId' + creator: + $ref: '#/components/schemas/StellarId' + PatchTransaction: + type: object + required: + - id + properties: + id: + type: string + status: + type: string + enum: [ + "pending_stellar", + "pending_customer_info_update", + "pending_receiver", + "pending_external", + "completed", + "error" + ] + amount_in: + $ref: '#/components/schemas/Amount' + amount_out: + $ref: '#/components/schemas/Amount' + amount_fee: + $ref: '#/components/schemas/Amount' + transfer_received_at: + type: string + format: date-time + message: + type: string + refund: + $ref: '#/components/schemas/Refunds' + external_transaction_id: + type: string + Amount: + type: object + required: + - amount + - asset + properties: + amount: + type: string + asset: + type: string + Refunds: + type: object + properties: + amount_refunded: + $ref: '#/components/schemas/Amount' + amount_fee: + $ref: '#/components/schemas/Amount' + payments: + type: array + items: + type: object + properties: + id: + type: string + id_type: + type: string + enum: + - stellar + - external + amount: + $ref: '#/components/schemas/Amount' + fee: + $ref: '#/components/schemas/Amount' + requested_at: + type: string + format: date-time + refunded_at: + type: string + format: date-time + + StellarId: + type: object + description: | + StellarId's are objects that identify end-users and SEP-31 Sending Anchors, but not SEP-31 Receiving Anchors. + + For a SEP-12 customer, the `id` field should be sufficient to fully identify the customer in the Anchor Backend. + + For a SEP-31 Sending Anchor, the `account` field should be used. + properties: + id: + type: string + description: The `id` of the customer registered through SEP-12. + account: + type: string + description: Either the Stellar account or Muxed account address of the on-chain entity. + StellarTransaction: + type: object + required: + - id + - 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 + - id + 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: + 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: + type: object + properties: + error: + type: string + id: + type: string + required: + - error + + Event: + type: object + properties: + id: + type: string + type: + type: string + enum: + - transaction_created + - transaction_status_changed + - transaction_error + - quote_created - a quote was created via the SEP38 API + description: | + The transaction event type. Can be one of the following: + - `transaction_created` - a transaction was created through the SEP endpoints. The payload is in the `transaction` field. + - `transaction_status_changed` - the status of a transaction has changed. The payload is in the `transaction` field. + - `transaction_error` - error processing the transaction. The payload is in the `transaction` field. + - `quote_created - a quote was created via the SEP38 API. The payload is in the `quote` field. + transaction: + $ref: '#/components/schemas/Transaction' + quote: + $ref: '#/components/schemas/Quote' + + Quote: + type: object + properties: + id: + type: string + sell_asset: + type: string + buy_asset: + type: string + expires_at: + type: string + format: date-time + price: + type: string + creator: + $ref: '#/components/schemas/StellarId' + transaction_id: + type: string + created_at: + type: string + format: date-time From f1f121291edcda094c9a609b635009e7d0e8dcf3 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 10 Jan 2023 11:55:21 +0800 Subject: [PATCH 0079/1439] removed MetricsConfig interface (#706) --- .../main/java/org/stellar/anchor/config/MetricConfig.java | 7 ------- .../anchor/platform/component/share/MetricsBeans.java | 5 ++--- .../{PropertyMetricConfig.java => MetricConfig.java} | 8 +------- .../anchor/platform/service/MetricEmitterService.java | 2 +- 4 files changed, 4 insertions(+), 18 deletions(-) delete mode 100644 core/src/main/java/org/stellar/anchor/config/MetricConfig.java rename platform/src/main/java/org/stellar/anchor/platform/config/{PropertyMetricConfig.java => MetricConfig.java} (75%) diff --git a/core/src/main/java/org/stellar/anchor/config/MetricConfig.java b/core/src/main/java/org/stellar/anchor/config/MetricConfig.java deleted file mode 100644 index e6cb312463..0000000000 --- a/core/src/main/java/org/stellar/anchor/config/MetricConfig.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.stellar.anchor.config; - -public interface MetricConfig { - boolean isExtrasEnabled(); - - Integer getRunInterval(); -} diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java index 15e2b6d68c..73bcdeb208 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/MetricsBeans.java @@ -3,8 +3,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.stellar.anchor.config.MetricConfig; -import org.stellar.anchor.platform.config.PropertyMetricConfig; +import org.stellar.anchor.platform.config.MetricConfig; import org.stellar.anchor.platform.data.JdbcSep31TransactionRepo; import org.stellar.anchor.platform.service.MetricEmitterService; @@ -17,7 +16,7 @@ public class MetricsBeans { @Bean @ConfigurationProperties(prefix = "metrics") MetricConfig metricConfig() { - return new PropertyMetricConfig(); + return new MetricConfig(); } @Bean diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/MetricConfig.java similarity index 75% rename from platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java rename to platform/src/main/java/org/stellar/anchor/platform/config/MetricConfig.java index 75e35595d6..cbbe68efec 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyMetricConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/MetricConfig.java @@ -5,10 +5,9 @@ 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, Validator { +public class MetricConfig implements Validator { private boolean enabled = false; private boolean extrasEnabled = false; private Integer runInterval = 30; @@ -22,9 +21,4 @@ public boolean supports(@NotNull Class clazz) { public void validate(@NotNull Object target, @NotNull Errors errors) { System.out.println("here"); } - - @Override - public boolean isExtrasEnabled() { - return this.extrasEnabled; - } } 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 37fe79856a..2c944b4171 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 @@ -8,7 +8,7 @@ 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.config.MetricConfig; import org.stellar.anchor.platform.data.JdbcSep31TransactionRepo; import org.stellar.anchor.util.Log; From b906a9885c1f86d282ebc2abdc987dd457c83104 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 13 Jan 2023 10:27:50 +0800 Subject: [PATCH 0080/1439] all: [ANCHOR-138] Scaffold the Event Processor (#707) * Create controller packages for sep, observer and event handler * Add EventProcesingServer and start from ServiceRunner * Add event-processor configuration * Read the config for event processor * Add EventProcessor and use ScheduledThreadPoolExecutor to prevent the termination of the thread. --- ... Stellar Observer - ServiceRunner.run.xml} | 4 +- ...4. Event Processor - ServiceRunner.run.xml | 32 +++++ .../anchor/reference/event/KafkaListener.java | 6 +- .../anchor/event/models/AnchorEvent.java | 3 + .../platform/AbstractPlatformServer.java | 15 +++ .../anchor/platform/AnchorPlatformServer.java | 16 +-- .../platform/EventProcessingServer.java | 40 ++++++ .../platform/StellarObservingServer.java | 15 +-- .../component/eventprocessor/ConfigBeans.java | 22 ++++ .../eventprocessor/EventProcessorBeans.java | 15 +++ .../component/observer/ConfigBeans.java | 4 - .../platform/config/EventProcessorConfig.java | 50 ++++++++ .../EventProcessorConfigManager.java | 47 +++++++ ...r.java => ControllerExceptionHandler.java} | 5 +- .../platform/controller/HealthController.java | 11 +- .../ObserverControllerExceptionHandler.java | 8 ++ .../observer/ObserverHealthController.java | 16 +++ .../{ => sep}/PlatformController.java | 2 +- .../controller/{ => sep}/Sep10Controller.java | 2 +- .../controller/{ => sep}/Sep10Helper.java | 2 +- .../controller/{ => sep}/Sep12Controller.java | 4 +- .../controller/{ => sep}/Sep1Controller.java | 2 +- .../controller/{ => sep}/Sep24Controller.java | 4 +- .../controller/{ => sep}/Sep31Controller.java | 4 +- .../controller/{ => sep}/Sep38Controller.java | 4 +- .../sep/SepControllerExceptionHandler.java | 8 ++ .../controller/sep/SepHealthController.java | 16 +++ .../anchor/platform/event/EventProcessor.java | 115 ++++++++++++++++++ .../config/anchor-config-default-values.yaml | 20 +++ .../config/anchor-config-schema-v1.yaml | 11 +- .../controller/HealthControllerTest.kt | 3 +- .../anchor/platform/ServiceRunner.java | 10 ++ 32 files changed, 461 insertions(+), 55 deletions(-) rename .run/{1. Platform Server - ServiceRunner [Int test cfg].run.xml => 3. Stellar Observer - ServiceRunner.run.xml} (87%) create mode 100644 .run/4. Event Processor - ServiceRunner.run.xml create mode 100644 platform/src/main/java/org/stellar/anchor/platform/AbstractPlatformServer.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/EventProcessingServer.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/eventprocessor/ConfigBeans.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/component/eventprocessor/EventProcessorBeans.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/config/EventProcessorConfig.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/configurator/EventProcessorConfigManager.java rename platform/src/main/java/org/stellar/anchor/platform/controller/{GlobalControllerExceptionHandler.java => ControllerExceptionHandler.java} (93%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/controller/observer/ObserverControllerExceptionHandler.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/controller/observer/ObserverHealthController.java rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/PlatformController.java (97%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep10Controller.java (98%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep10Helper.java (92%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep12Controller.java (97%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep1Controller.java (96%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep24Controller.java (98%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep31Controller.java (96%) rename platform/src/main/java/org/stellar/anchor/platform/controller/{ => sep}/Sep38Controller.java (96%) create mode 100644 platform/src/main/java/org/stellar/anchor/platform/controller/sep/SepControllerExceptionHandler.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/controller/sep/SepHealthController.java create mode 100644 platform/src/main/java/org/stellar/anchor/platform/event/EventProcessor.java diff --git a/.run/1. Platform Server - ServiceRunner [Int test cfg].run.xml b/.run/3. Stellar Observer - ServiceRunner.run.xml similarity index 87% rename from .run/1. Platform Server - ServiceRunner [Int test cfg].run.xml rename to .run/3. Stellar Observer - ServiceRunner.run.xml index 59b9ac1174..5bc51bae85 100644 --- a/.run/1. Platform Server - ServiceRunner [Int test cfg].run.xml +++ b/.run/3. Stellar Observer - ServiceRunner.run.xml @@ -1,5 +1,5 @@ - + @@ -12,7 +12,7 @@

We should avoid the validations in this method if they can be done by the Spring + * validations. + * + * @param config The configuration map + * @throws InvalidConfigException Invalid configuration value exception + */ + abstract void validate(ConfigMap config) throws InvalidConfigException; } From d0431b03382223a546e0d6dba4da5e31a1bc901f Mon Sep 17 00:00:00 2001 From: Gleb Date: Thu, 16 Feb 2023 10:04:36 -0800 Subject: [PATCH 0104/1439] Fixes for stellarTransactionId and Customers (#757) * Fixes for stellarTransactionId and Customers * Fixes * Update source/destination accounts --- .../anchor/api/platform/PlatformTransactionData.java | 7 +++++-- .../org/stellar/anchor/api/shared/Customers.java | 2 ++ .../service/PaymentOperationToEventListener.java | 2 -- .../anchor/platform/utils/TransactionHelper.java | 12 ++++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java index fde654d0b4..31a596d394 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java @@ -57,8 +57,11 @@ public class PlatformTransactionData { @SerializedName("stellar_transactions") List stellarTransactions; - @SerializedName("stellar_transaction_id") - String stellarTransactionId; + @SerializedName("source_account") + String sourceAccount; + + @SerializedName("destination_account") + String destinationAccount; @SerializedName("external_transaction_id") String externalTransactionId; diff --git a/api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java b/api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java index 072e7d4221..957aabbe4f 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/shared/Customers.java @@ -1,9 +1,11 @@ package org.stellar.anchor.api.shared; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; @Data +@Builder @AllArgsConstructor public class Customers { StellarId sender; 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 f311e758ec..162f7a8383 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 @@ -118,7 +118,6 @@ public void handleSep31Transaction(ObservedPayment payment, JdbcSep31Transaction .stellarTransactions( StellarTransaction.addOrUpdateTransactions( txn.getStellarTransactions(), stellarTransaction)) - .stellarTransactionId(payment.getTransactionHash()) .id(txn.getId()) .build()) .build())) @@ -268,7 +267,6 @@ public void handleSep24Transaction(ObservedPayment payment, JdbcSep24Transaction .stellarTransactions( StellarTransaction.addOrUpdateTransactions( txn.getStellarTransactions(), stellarTransaction)) - .stellarTransactionId(payment.getTransactionHash()) .id(txn.getId()) .build()) .build())) diff --git a/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java b/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java index 1659cd1cd5..b2a9e7c62f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java +++ b/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java @@ -1,6 +1,6 @@ package org.stellar.anchor.platform.utils; -import static org.stellar.anchor.api.platform.PlatformTransactionData.Kind.RECEIVE; +import static org.stellar.anchor.api.platform.PlatformTransactionData.Kind.*; import javax.annotation.Nullable; import org.stellar.anchor.api.exception.SepException; @@ -70,6 +70,7 @@ static GetTransactionResponse toGetTransactionResponse( String amountOutAsset = makeAsset(txn.getAmountOutAsset(), assetService, txn); String amountFeeAsset = makeAsset(txn.getAmountFeeAsset(), assetService, txn); String amountExpectedAsset = makeAsset(null, assetService, txn); + String sourceAccount = txn.getFromAccount(); return GetTransactionResponse.builder() .id(txn.getId()) @@ -79,11 +80,10 @@ static GetTransactionResponse toGetTransactionResponse( .amountIn(Amount.create(txn.getAmountIn(), amountInAsset)) .amountOut(Amount.create(txn.getAmountOut(), amountOutAsset)) .amountFee(Amount.create(txn.getAmountFee(), amountFeeAsset)) - .amountExpected(Amount.create(txn.getAmountExpected(), amountExpectedAsset)) - .customers( - new Customers( - StellarId.builder().account(txn.getToAccount()).build(), - StellarId.builder().account(txn.getFromAccount()).build())) + // constructor is used because AMOUNT can be null, when ASSET is always non-null + .amountExpected(new Amount(txn.getAmountExpected(), amountExpectedAsset)) + .sourceAccount(sourceAccount) + .destinationAccount(txn.getToAccount()) .startedAt(txn.getStartedAt()) .updatedAt(txn.getUpdatedAt()) .completedAt(txn.getCompletedAt()) From feea1dbfdeb79d3e50f4fbdb2a76db50d21a0665 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 11:24:18 -0800 Subject: [PATCH 0105/1439] [ANCHOR-173] Removed data.url and add data.server and data.database fields (#759) * Removed data.url and add data.server and data.database field * Add postgres connection validation --- ...ceRunner [Integration Test Config].run.xml | 2 +- ...eference-server-docker-compose-config.yaml | 1 - .../resources/anchor-reference-server.yaml | 1 - .../common-services/common-services.yaml | 6 +- .../sep24/docker-compose.yaml | 3 +- .../sep31/docker-compose.yaml | 3 +- .../example_config/anchor_config.yaml | 2 - helm-charts/sep-service/example_values.yaml | 2 +- .../anchor-platform-config.yaml | 9 ++- .../platform/config/PropertyDataConfig.java | 3 +- .../platform/configurator/ConfigHelper.java | 7 --- .../platform/configurator/ConfigManager.java | 5 +- .../configurator/DataConfigAdapter.java | 58 +++++++++++++++++-- .../anchor-docker-compose-config.yaml | 8 +-- .../config/anchor-config-default-values.yaml | 12 ++-- .../config/anchor-config-schema-v1.yaml | 11 +--- .../main/resources/example.anchor-config.yaml | 5 +- 17 files changed, 86 insertions(+), 52 deletions(-) diff --git a/.run/v2.0 - Platform Server - ServiceRunner [Integration Test Config].run.xml b/.run/v2.0 - Platform Server - ServiceRunner [Integration Test Config].run.xml index df125f087f..7973f8c3a6 100644 --- a/.run/v2.0 - Platform Server - ServiceRunner [Integration Test Config].run.xml +++ b/.run/v2.0 - Platform Server - ServiceRunner [Integration Test Config].run.xml @@ -1,9 +1,9 @@ + - 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 7db4dcf459..e9ff6aa931 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 @@ -91,7 +91,6 @@ spring.mvc.converters.preferred-json-mapper: gson #spring.jpa.database: POSTGRESQL #spring.jpa.show-sql: false #spring.datasource.driver-class-name: org.postgresql.Driver -#spring.datasource.url: jdbc:postgresql://localhost:5432/anchorplatform #spring.datasource.username: anchorplatform #spring.datasource.password: #spring.mvc.converters.preferred-json-mapper: gson \ No newline at end of file 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 daec64f9e1..61d6169abf 100644 --- a/anchor-reference-server/src/main/resources/anchor-reference-server.yaml +++ b/anchor-reference-server/src/main/resources/anchor-reference-server.yaml @@ -101,7 +101,6 @@ spring.mvc.converters.preferred-json-mapper: gson #spring.jpa.database: POSTGRESQL #spring.jpa.show-sql: false #spring.datasource.driver-class-name: org.postgresql.Driver -#spring.datasource.url: jdbc:postgresql://localhost:5432/anchorplatform #spring.datasource.username: anchorplatform #spring.datasource.password: #spring.mvc.converters.preferred-json-mapper: gson \ No newline at end of file diff --git a/docker-compose/anchor-get-started/common-services/common-services.yaml b/docker-compose/anchor-get-started/common-services/common-services.yaml index 04e2ceb20b..b86d9c0ae0 100644 --- a/docker-compose/anchor-get-started/common-services/common-services.yaml +++ b/docker-compose/anchor-get-started/common-services/common-services.yaml @@ -19,7 +19,7 @@ services: depends_on: - zookeeper ports: - - 29092:29092 + - "29092:29092" environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 @@ -35,12 +35,12 @@ services: ZOOKEEPER_CLIENT_PORT: 2181 ZOOKEEPER_TICK_TIME: 2000 ports: - - 2181:2181 + - "2181:2181" db_a: image: postgres:latest ports: - - "5431:5432" # use 5431 so this doesn't conflict if you have a local postgres instance running + - "5432:5432" environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password \ No newline at end of file diff --git a/docker-compose/anchor-get-started/sep24/docker-compose.yaml b/docker-compose/anchor-get-started/sep24/docker-compose.yaml index 4f07519943..c24929be1a 100644 --- a/docker-compose/anchor-get-started/sep24/docker-compose.yaml +++ b/docker-compose/anchor-get-started/sep24/docker-compose.yaml @@ -22,7 +22,8 @@ services: - events.publisher.kafka.bootstrap_server=kafka:29092 # data - data.type=postgres - - data.url=jdbc:postgresql://db:5432/ + - data.server=db:5432 + - data.database=postgres - data.flyway_enabled=false # seps - sep1.enabled=true diff --git a/docker-compose/anchor-get-started/sep31/docker-compose.yaml b/docker-compose/anchor-get-started/sep31/docker-compose.yaml index a3db4898b1..a2c40910f4 100644 --- a/docker-compose/anchor-get-started/sep31/docker-compose.yaml +++ b/docker-compose/anchor-get-started/sep31/docker-compose.yaml @@ -20,7 +20,8 @@ services: - events.publisher.kafka.bootstrap_server=kafka:29092 # data - data.type=postgres - - data.url=jdbc:postgresql://db:5432/ + - data.server=db:5432 + - data.database=postgres - data.flyway_enabled=false # seps - sep1.enabled=true 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 3ce63bd928..df4a6c48c7 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 @@ -284,7 +284,6 @@ data-spring-jdbc-aws-aurora-postgres: 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 @@ -295,7 +294,6 @@ 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:5432/anchorplatform spring.datasource.username: ${POSTGRES_USERNAME} spring.datasource.password: ${POSTGRES_PASSWORD} spring.mvc.converters.preferred-json-mapper: gson diff --git a/helm-charts/sep-service/example_values.yaml b/helm-charts/sep-service/example_values.yaml index b0f9b747c2..e623d72eb2 100644 --- a/helm-charts/sep-service/example_values.yaml +++ b/helm-charts/sep-service/example_values.yaml @@ -105,7 +105,7 @@ app_config: # # Database (PostgreSQL): # data.type: postgres - # data.url: jdbc:postgresql://:/ + # data.server: : # data.username: POSTGRES_USERNAME # TODO: use secrets # data.password: POSTGRES_PASSWORD # TODO: use secrets # data.initial_connection_pool_size: 3 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 40d4bf3397..bcac78754d 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 @@ -302,11 +302,10 @@ data: # type: postgres - ## @param: url - ## @type: string - ## Location of the database - # - url: jdbc:postgresql://host.docker.internal:5440/ + server: host.docker.internal:5440 + + database: postgres + ## @param: initial_connection_pool_size ## @type: integer diff --git a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyDataConfig.java b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyDataConfig.java index 27509ecdad..8ad9dc9398 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyDataConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyDataConfig.java @@ -16,7 +16,8 @@ public class PropertyDataConfig implements Validator { public static final String ERROR_SECRET_DATA_USERNAME_EMPTY = "secret-data-username-empty"; public static final String ERROR_SECRET_DATA_PASSWORD_EMPTY = "secret-data-password-empty"; String type; - String url; + String server; + String database; int initialConnectionPoolSize; int max_active_connections; boolean flywayEnabled; 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 0969f50c3e..de5825ca04 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 @@ -96,13 +96,6 @@ public static String suggestedSchema() throws IOException { 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); - 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/ConfigManager.java b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java index 26aa7e139c..8c4049e8a3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java +++ b/platform/src/main/java/org/stellar/anchor/platform/configurator/ConfigManager.java @@ -75,10 +75,11 @@ void sendToSpring( adapter.validate(config); } catch (InvalidConfigException icex) { errorF( - "Invalid configuration. {}{} Reason={}", + "Invalid configuration. {}{} Reason={}{}", System.lineSeparator(), System.lineSeparator(), - icex.getMessage()); + icex.getMessage(), + System.lineSeparator()); // We should not continue. System.exit(1); } 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 ba90d55b96..e71d8dff3f 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 @@ -5,8 +5,11 @@ import static org.stellar.anchor.util.Log.error; import static org.stellar.anchor.util.StringHelper.isEmpty; +import java.sql.DriverManager; +import java.sql.SQLException; import java.util.Arrays; import java.util.List; +import java.util.Properties; import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.util.Log; @@ -98,7 +101,7 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { 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.datasource.url", constructH2Url(config)); set("spring.jpa.database-platform", "org.hibernate.dialect.H2Dialect"); set("spring.jpa.properties.hibernate.dialect", "org.hibernate.dialect.H2Dialect"); break; @@ -108,7 +111,7 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.jpa.database-platform", "org.stellar.anchor.platform.sqlite.SQLiteDialect"); set("spring.jpa.generate-ddl", true); set("spring.jpa.hibernate.ddl-auto", "update"); - copy(config, "data.url", "spring.datasource.url"); + set("spring.datasource.url", constructSQLiteUrl(config)); set("spring.datasource.username", SecretManager.getInstance().get(SECRET_DATA_USERNAME)); set("spring.datasource.password", SecretManager.getInstance().get(SECRET_DATA_PASSWORD)); break; @@ -119,7 +122,7 @@ 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.url", "spring.datasource.url"); + set("spring.datasource.url", constructPostgressUrl(config)); 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")) { @@ -127,7 +130,7 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.flyway.locations", "classpath:/db/migration"); 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"); + set("spring.flyway.url", constructPostgressUrl(config)); } else { set("spring.jpa.generate-ddl", true); set("spring.jpa.hibernate.ddl-auto", "update"); @@ -137,7 +140,7 @@ 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.url", "spring.datasource.url"); + set("spring.datasource.url", constructPostgressUrl(config)); 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")) { @@ -145,7 +148,7 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { set("spring.flyway.locations", "classpath:/db/migration"); 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"); + set("spring.flyway.url", constructPostgressUrl(config)); } else { set("spring.jpa.generate-ddl", true); set("spring.jpa.hibernate.ddl-auto", "update"); @@ -159,8 +162,51 @@ void updateSpringEnv(ConfigMap config) throws InvalidConfigException { checkIfAllFieldsAreSet(); } + private String constructPostgressUrl(ConfigMap config) { + return String.format( + "jdbc:postgresql://%s/%s", + config.getString("data.server"), config.getString("data.database")); + } + + private String constructSQLiteUrl(ConfigMap config) { + return String.format("jdbc:sqlite:%s.db", config.getString("data.database")); + } + + private String constructH2Url(ConfigMap config) { + return "jdbc:h2:mem:anchor-platform"; + } + @Override void validate(ConfigMap config) throws InvalidConfigException { + validateCredential(config); + validateConnection(config); + } + + void validateConnection(ConfigMap config) throws InvalidConfigException { + String type = config.getString("data.type").toLowerCase(); + switch (type) { + case DATABASE_H2: + case DATABASE_SQLITE: + // no need for connection validation. + break; + case DATABASE_AURORA: + case DATABASE_POSTGRES: + String url = constructPostgressUrl(config); + try { + Properties props = new Properties(); + props.setProperty("user", SecretManager.getInstance().get(SECRET_DATA_USERNAME)); + props.setProperty("password", SecretManager.getInstance().get(SECRET_DATA_PASSWORD)); + DriverManager.getConnection(url, props); + } catch (SQLException e) { + error(e.getMessage()); + throw new InvalidConfigException( + String.format("Unable to connect to database. url=%s", url)); + } + break; + } + } + + void validateCredential(ConfigMap config) throws InvalidConfigException { String type = config.getString("data.type").toLowerCase(); switch (type) { case DATABASE_H2: diff --git a/platform/src/main/resources/anchor-docker-compose-config.yaml b/platform/src/main/resources/anchor-docker-compose-config.yaml index 6b1427e03f..5ea228dd09 100644 --- a/platform/src/main/resources/anchor-docker-compose-config.yaml +++ b/platform/src/main/resources/anchor-docker-compose-config.yaml @@ -300,11 +300,9 @@ data: # type: postgres - ## @param: url - ## @type: string - ## Location of the database - # - url: jdbc:postgresql://db:5432/ + server: db:5432 + + database: postgres ## @param: initial_connection_pool_size ## @type: integer 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 46f6b238a1..ea633142fe 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -485,11 +485,13 @@ data: # # type: h2 - ## @param: url - ## @type: string - ## Location of the database - # - url: jdbc:h2:mem:anchor-platform + + ## The hostname and port of the database server. + server: + + ## Name of the database. + database: + ## @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 d0dee3add4..6a9daee2f7 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -6,13 +6,14 @@ callback_api.auth.jwt.http_header: callback_api.auth.type: callback_api.base_url: callback_api.check_certificate: +data.database: data.ddl_auto: data.flyway_enabled: data.flyway_location: data.initial_connection_pool_size: data.max_active_connections: +data.server: data.type: -data.url: event_processor.context_path: event_processor.port: event_processor.queue.kafka.bootstrap_server: @@ -63,12 +64,6 @@ platform_api.auth.jwt.expiration_milliseconds: platform_api.auth.jwt.http_header: platform_api.auth.type: platform_api.base_url: -secret.callback_api.secret: -secret.data.password: -secret.data.username: -secret.jwt_secret: -secret.platform_api.secret: -secret.sep10_signing_seed: sep1.enabled: sep1.toml.type: sep1.toml.value: @@ -98,4 +93,4 @@ sep_server.management_server_port: sep_server.port: stellar_network.horizon_url: stellar_network.network: -stellar_network.network_passphrase: +stellar_network.network_passphrase: \ No newline at end of file diff --git a/platform/src/main/resources/example.anchor-config.yaml b/platform/src/main/resources/example.anchor-config.yaml index 47fa383e78..5ca6f0571b 100644 --- a/platform/src/main/resources/example.anchor-config.yaml +++ b/platform/src/main/resources/example.anchor-config.yaml @@ -24,7 +24,7 @@ logging: data: type: sqlite - url: jdbc:sqlite:anchor-platform.db + database: anchor-platform initial_connection_pool_size: 2 max_active_connections: 10 flyway_enabled: true @@ -32,7 +32,8 @@ data: #data: # type: postgres -# url: jdbc:postgresql://localhost:5432/anchorplatform +# server: localhost:5432 +# database: postgres # username: anchorplatform # password: # initial_connection_pool_size: 3 From f6db57e19b42a9e3960ee8396e3ac90925ff90f7 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 12:04:34 -0800 Subject: [PATCH 0106/1439] [ANCHOR-178] Add sep10AccountMemo in Sep24Service (#760) * Add sep10AccountMemo to Sep24Service --- .../stellar/anchor/sep24/Sep24Service.java | 2 + .../anchor/sep24/Sep24Transaction.java | 9 ++ .../anchor/sep24/Sep24TransactionBuilder.java | 9 +- .../anchor/sep24/PojoSep24Transaction.java | 1 + .../org/stellar/anchor/auth/Sep10JwtTest.kt | 22 ++--- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 82 ++++++++++++------- .../platform/data/JdbcSep24Transaction.java | 11 ++- 7 files changed, 90 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 0d9571f604..3955d3cb4f 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -143,6 +143,7 @@ public InteractiveTransactionResponse withdraw( .assetIssuer(asset.getIssuer()) .startedAt(Instant.now()) .sep10Account(token.getAccount()) + .sep10AccountMemo(token.getAccountMemo()) .fromAccount(sourceAccount) // TODO - jamie to add unique address generator .withdrawAnchorAccount(asset.getDistributionAccount()) @@ -262,6 +263,7 @@ public InteractiveTransactionResponse deposit(Sep10Jwt token, Map pair.name.equals("token") } - assertEquals(tokenStrings.size, 1) + assertEquals(1, tokenStrings.size) val tokenString = tokenStrings[0].value val decodedToken = jwtService.decode(tokenString, Sep24InteractiveUrlJwt::class.java) - assertEquals(decodedToken.sub, TEST_ACCOUNT) - assertEquals(decodedToken.claims()[CLIENT_DOMAIN], TEST_CLIENT_DOMAIN) + assertEquals(TEST_ACCOUNT, decodedToken.sub) + assertEquals(TEST_CLIENT_DOMAIN, decodedToken.claims()[CLIENT_DOMAIN]) } @Test @@ -158,9 +157,10 @@ internal class Sep24ServiceTest { val strToken = jwtService.encode(createdJwt) every { interactiveUrlConstructor.construct(any(), any(), any()) } returns "${TEST_SEP24_INTERACTIVE_URL}?lang=en&token=$strToken" + val slotTxn = slot() + every { txnStore.save(capture(slotTxn)) } returns null val response = sep24Service.withdraw(createdJwt, createTestTransactionRequest()) - val params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) val tokenStrings = params.filter { pair -> pair.name.equals("token") } assertEquals(tokenStrings.size, 1) @@ -170,7 +170,20 @@ internal class Sep24ServiceTest { "$TEST_ACCOUNT:$TEST_MEMO", decodedToken.sub, ) - assertEquals(decodedToken.claims()[CLIENT_DOMAIN], TEST_CLIENT_DOMAIN) + assertEquals(TEST_CLIENT_DOMAIN, decodedToken.claims()[CLIENT_DOMAIN]) + + assertEquals("incomplete", slotTxn.captured.status) + assertEquals("withdrawal", slotTxn.captured.kind) + assertEquals("USDC", slotTxn.captured.requestAssetCode) + assertEquals( + slotTxn.captured.requestAssetIssuer, + "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ) + assertEquals(TEST_ACCOUNT, slotTxn.captured.sep10Account) + assertEquals(TEST_MEMO, slotTxn.captured.sep10AccountMemo) + assertEquals(TEST_ACCOUNT, slotTxn.captured.fromAccount) + assertEquals(TEST_CLIENT_DOMAIN, slotTxn.captured.clientDomain) + assertEquals("123.4", slotTxn.captured.amountExpected) } @Test @@ -236,18 +249,17 @@ internal class Sep24ServiceTest { verify(exactly = 1) { txnStore.save(any()) } - assertEquals("interactive_customer_info_needed", response.type) + assertEquals(response.type, "interactive_customer_info_needed") assertTrue(response.url.startsWith(TEST_SEP24_INTERACTIVE_URL)) assertEquals(response.id, slotTxn.captured.transactionId) - assertEquals(slotTxn.captured.status, "incomplete") - assertEquals(slotTxn.captured.kind, "deposit") - assertEquals(slotTxn.captured.requestAssetCode, TEST_ASSET) - assertEquals(slotTxn.captured.requestAssetIssuer, TEST_ASSET_ISSUER_ACCOUNT_ID) - assertEquals(slotTxn.captured.sep10Account, TEST_ACCOUNT) - assertEquals(slotTxn.captured.toAccount, TEST_ACCOUNT) - assertEquals(slotTxn.captured.clientDomain, TEST_CLIENT_DOMAIN) - assertEquals(slotTxn.captured.amountExpected, "123.4") + assertEquals("incomplete", slotTxn.captured.status) + assertEquals("deposit", slotTxn.captured.kind) + assertEquals(TEST_ASSET, slotTxn.captured.requestAssetCode) + assertEquals(TEST_ASSET_ISSUER_ACCOUNT_ID, slotTxn.captured.requestAssetIssuer) + assertEquals(TEST_ACCOUNT, slotTxn.captured.toAccount) + assertEquals(TEST_CLIENT_DOMAIN, slotTxn.captured.clientDomain) + assertEquals("123.4", slotTxn.captured.amountExpected) } @Test @@ -256,9 +268,10 @@ internal class Sep24ServiceTest { val strToken = jwtService.encode(createdJwt) every { interactiveUrlConstructor.construct(any(), any(), any()) } returns "${TEST_SEP24_INTERACTIVE_URL}?lang=en&token=$strToken" + val slotTxn = slot() + every { txnStore.save(capture(slotTxn)) } returns null val response = sep24Service.withdraw(createdJwt, createTestTransactionRequest()) - val params = URLEncodedUtils.parse(URI(response.url), Charset.forName("UTF-8")) val tokenStrings = params.filter { pair -> pair.name.equals("token") } assertEquals(tokenStrings.size, 1) @@ -269,6 +282,19 @@ internal class Sep24ServiceTest { decodedToken.sub, ) assertEquals(TEST_CLIENT_DOMAIN, decodedToken.claims[CLIENT_DOMAIN]) + + assertEquals("incomplete", slotTxn.captured.status) + assertEquals("withdrawal", slotTxn.captured.kind) + assertEquals("USDC", slotTxn.captured.requestAssetCode) + assertEquals( + slotTxn.captured.requestAssetIssuer, + "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ) + assertEquals(TEST_ACCOUNT, slotTxn.captured.sep10Account) + assertEquals(TEST_MEMO, slotTxn.captured.sep10AccountMemo) + assertEquals(TEST_ACCOUNT, slotTxn.captured.fromAccount) + assertEquals(TEST_CLIENT_DOMAIN, slotTxn.captured.clientDomain) + assertEquals("123.4", slotTxn.captured.amountExpected) } @Test @@ -375,11 +401,11 @@ internal class Sep24ServiceTest { var gtr = GetTransactionRequest(TEST_TRANSACTION_ID_0, null, null, "en-US") val response = sep24Service.findTransaction(createJwtToken(), gtr) - assertEquals(response.transaction.id, TEST_TRANSACTION_ID_0) - assertEquals(response.transaction.status, "incomplete") - assertEquals(response.transaction.kind, kind) - assertEquals(response.transaction.startedAt, TEST_STARTED_AT) - assertEquals(response.transaction.completedAt, TEST_COMPLETED_AT) + assertEquals(TEST_TRANSACTION_ID_0, response.transaction.id) + assertEquals("incomplete", response.transaction.status) + assertEquals(kind, response.transaction.kind) + assertEquals(TEST_STARTED_AT, response.transaction.startedAt) + assertEquals(TEST_COMPLETED_AT, response.transaction.completedAt) verify(exactly = 1) { txnStore.findByTransactionId(TEST_TRANSACTION_ID_0) } // test with stellar transaction_id diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index be14530a0f..047ead1ba8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -105,15 +105,14 @@ public void setRefunds(Sep24Refunds refunds) { @SerializedName("request_asset_issuer") String requestAssetIssuer; - /** - * The SEP10 account used for authentication. - * - *

The account can be in the format of 1) stellar_account (G...) 2) stellar_account:memo - * (G...:2810101841641761712) 3) muxed account (M...) - */ + /** The SEP10 account used for authentication. */ @SerializedName("sep10_account") String sep10Account; + /** The SEP10 account memo used for authentication. */ + @SerializedName("sep10_account_memo") + String sep10AccountMemo; + @SerializedName("client_domain") String clientDomain; From a1527cd413f65a49a60f91bc76a24b350e5e2925 Mon Sep 17 00:00:00 2001 From: Stephen Date: Thu, 16 Feb 2023 15:19:07 -0500 Subject: [PATCH 0107/1439] [ANCHOR-148] Generate db migration file and add docker-compose sep24 e2e tests (#745) * Generate db migration file and sep24 e2e tests * Remove required_info_message from sep24 table * Merge the migration file from rebasing with develop branch Co-authored-by: Jamie Li --- Makefile | 4 ++ .../docker-compose-config.override.yaml | 43 ++++++++++++++ .../platform/data/JdbcSep31Transaction.java | 3 + .../platform/data/JdbcSepTransaction.java | 3 - .../platform/service/TransactionService.java | 3 +- .../migration/V6__add_bank_account_type.sql | 1 - .../db/migration/V6__sep24_field_updates.sql | 59 +++++++++++++++++++ 7 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml delete mode 100644 platform/src/main/resources/db/migration/V6__add_bank_account_type.sql create mode 100644 platform/src/main/resources/db/migration/V6__sep24_field_updates.sql diff --git a/Makefile b/Makefile index e03335f5b5..29fa0f2da9 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ run-e2e-test-all: make run-e2e-test-default-config make run-e2e-test-allowlist make run-e2e-test-unique-address + make run-e2e-test-sep24-withdrawal define run_tests $(SUDO) docker-compose --env-file integration-tests/docker-compose-configs/.env \ @@ -50,3 +51,6 @@ run-e2e-test-allowlist: run-e2e-test-unique-address: $(call run_tests,anchor-platform-unique-address) + +run-e2e-test-sep24-withdrawal: + $(call run_tests,anchor-platform-sep24-withdrawal) diff --git a/integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml new file mode 100644 index 0000000000..ef8ab1b8b3 --- /dev/null +++ b/integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml @@ -0,0 +1,43 @@ +# Configuration Description - enable to omnibusAllowList feature to check if an account is whitelisted for SEP-10 +version: '2.4' +services: + anchor-platform-server: + volumes: + # add mounts for the new config directory + - ./anchor-platform-allowlist:/config_override + ports: + # override ports, do not append + - "8080:8080" + + anchor-reference-server: + volumes: + # add mounts for the new config directory + - ./anchor-platform-sep24-withdrawal:/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 sep24_withdrawal_flow \ No newline at end of file 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 381cbe77d2..cd59a70f22 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 @@ -56,6 +56,9 @@ public String getProtocol() { @SerializedName("receiver_id") String receiverId; + @SerializedName("required_info_message") + String requiredInfoMessage; + @Convert(converter = StellarIdConverter.class) @Column(length = 2047) StellarId creator; diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java index 61ac227672..f8145f3a30 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java @@ -57,9 +57,6 @@ public abstract class JdbcSepTransaction { @SerializedName("external_transaction_id") String externalTransactionId; - @SerializedName("required_info_message") - String requiredInfoMessage; - @Column(columnDefinition = "json") @Type(type = "json") List stellarTransactions; 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 37739fd691..0d95a993ca 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 @@ -218,7 +218,8 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) case "31": // update message if (shouldClearMessageStatus) { - txn.setRequiredInfoMessage(null); + JdbcSep31Transaction sep31Txn = (JdbcSep31Transaction) txn; + sep31Txn.setRequiredInfoMessage(null); } else { txnUpdated = updateField(patch, "message", txn, "requiredInfoMessage", txnUpdated); } diff --git a/platform/src/main/resources/db/migration/V6__add_bank_account_type.sql b/platform/src/main/resources/db/migration/V6__add_bank_account_type.sql deleted file mode 100644 index 27d841f03d..0000000000 --- a/platform/src/main/resources/db/migration/V6__add_bank_account_type.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE sep31_transaction ADD bank_account_type VARCHAR(255); \ No newline at end of file diff --git a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql new file mode 100644 index 0000000000..cf82acbb06 --- /dev/null +++ b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql @@ -0,0 +1,59 @@ +ALTER TABLE sep24_transaction ADD sep10_account_memo VARCHAR(255); + +ALTER TABLE sep24_transaction ADD amount_expected VARCHAR(255); + +ALTER TABLE sep24_transaction ADD kyc_verified VARCHAR(255); + +ALTER TABLE sep24_transaction ADD message VARCHAR(255); + +ALTER TABLE sep24_transaction ADD more_info_url VARCHAR(255); + +ALTER TABLE sep24_transaction ADD refund_memo VARCHAR(255); + +ALTER TABLE sep24_transaction ADD refund_memo_type VARCHAR(255); + +ALTER TABLE sep24_transaction ADD refunded BOOLEAN; + +ALTER TABLE sep24_transaction ADD refunds JSON; + +ALTER TABLE sep24_transaction ADD request_asset_code VARCHAR(255); + +ALTER TABLE sep24_transaction ADD request_asset_issuer VARCHAR(255); + +ALTER TABLE sep24_transaction ADD status_eta VARCHAR(255); + +ALTER TABLE sep24_transaction ADD stellar_transactions JSON; + +ALTER TABLE sep24_transaction ADD transfer_received_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE sep24_transaction ADD updated_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE sep24_refund_payment DROP CONSTRAINT fk_sep24_refund_payment_on_transaction; + +DROP TABLE sep24_refund_payment CASCADE; + +ALTER TABLE sep24_transaction DROP COLUMN sep_transaction_id; + +ALTER TABLE sep24_transaction DROP COLUMN asset_code; + +ALTER TABLE sep24_transaction DROP COLUMN asset_issuer; + +ALTER TABLE sep24_transaction DROP COLUMN muxed_account; + +ALTER TABLE sep24_transaction DROP COLUMN sep10account_memo; + +ALTER TABLE sep24_transaction DROP COLUMN completed_at; + +ALTER TABLE sep24_transaction DROP COLUMN started_at; + +ALTER TABLE sep24_transaction ADD completed_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE sep24_transaction ALTER COLUMN id SET NOT NULL; + +ALTER TABLE sep24_transaction ADD started_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE sep24_transaction ADD CONSTRAINT pk_sep24_transaction PRIMARY KEY (id); + +ALTER TABLE sep31_transaction ALTER COLUMN refunds type JSONB using refunds::jsonb; + +ALTER TABLE sep31_transaction ADD bank_account_type VARCHAR(255); From e121e78450a8fea613f26edb6e3ab65b4e3ce0f4 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 13:40:13 -0800 Subject: [PATCH 0108/1439] Stop creating SEP service beans when disabled in configuration (#761) --- .../stellar/anchor/platform/component/sep/SepBeans.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java index 7c48d9502d..a6a7f7b21a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/sep/SepBeans.java @@ -17,6 +17,7 @@ import org.stellar.anchor.event.EventService; import org.stellar.anchor.filter.JwtTokenFilter; import org.stellar.anchor.horizon.Horizon; +import org.stellar.anchor.platform.condition.ConditionalOnAllSepsEnabled; import org.stellar.anchor.platform.config.*; import org.stellar.anchor.platform.observer.stellar.PaymentObservingAccountsManager; import org.stellar.anchor.platform.service.Sep31DepositInfoGeneratorApi; @@ -120,11 +121,13 @@ public Horizon horizon(AppConfig appConfig) { } @Bean + @ConditionalOnAllSepsEnabled(seps = {"sep1"}) Sep1Service sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigException { return new Sep1Service(sep1Config); } @Bean + @ConditionalOnAllSepsEnabled(seps = {"sep10"}) Sep10Service sep10Service( AppConfig appConfig, SecretConfig secretConfig, @@ -135,11 +138,13 @@ Sep10Service sep10Service( } @Bean + @ConditionalOnAllSepsEnabled(seps = {"sep12"}) Sep12Service sep12Service(CustomerIntegration customerIntegration, AssetService assetService) { return new Sep12Service(customerIntegration, assetService); } @Bean + @ConditionalOnAllSepsEnabled(seps = {"sep24"}) Sep24Service sep24Service( AppConfig appConfig, Sep24Config sep24Config, @@ -189,6 +194,7 @@ Sep31DepositInfoGenerator sep31DepositInfoGenerator( } @Bean + @ConditionalOnAllSepsEnabled(seps = {"sep31"}) Sep31Service sep31Service( AppConfig appConfig, Sep31Config sep31Config, @@ -212,6 +218,7 @@ Sep31Service sep31Service( } @Bean + @ConditionalOnAllSepsEnabled(seps = {"sep38"}) Sep38Service sep38Service( Sep38Config sep38Config, AssetService assetService, From dcab856dea1e185ed384db8952ea8f0ba848958b Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 15:35:41 -0800 Subject: [PATCH 0109/1439] Fix the gradle build docker image to 7.6.0 (#763) --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 33687fd933..dd47dab511 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ -#FROM openjdk:11-jdk AS build -FROM gradle:jdk11-alpine AS build +FROM gradle:7.6.0-jdk11-alpine AS build WORKDIR /code COPY --chown=gradle:gradle . . RUN gradle clean bootJar --stacktrace From 1255bac10f04a07c87d1e9afe1de26ca673ae6a4 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 15:47:48 -0800 Subject: [PATCH 0110/1439] [ANCHOR-176] Add sub field to SEP24 interactive URL JWT (#762) * Add sub to SEP24 interactive URL JWT that stores the sep10 account information. --- .../org/stellar/anchor/auth/JwtService.java | 6 ++- .../anchor/auth/Sep24InteractiveUrlJwt.java | 4 +- .../org/stellar/anchor/auth/JwtServiceTest.kt | 3 +- .../SimpleInteractiveUrlConstructor.java | 41 ++++++++++++------- .../SimpleInteractiveUrlConstructorTest.kt | 28 ++++++++++--- 5 files changed, 58 insertions(+), 24 deletions(-) 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 34d4c3e9ce..c9d354d77d 100644 --- a/core/src/main/java/org/stellar/anchor/auth/JwtService.java +++ b/core/src/main/java/org/stellar/anchor/auth/JwtService.java @@ -75,7 +75,11 @@ public String encode(Sep24InteractiveUrlJwt token) throws InvalidConfigException } Calendar calExp = Calendar.getInstance(); calExp.setTimeInMillis(1000L * token.getExp()); - JwtBuilder builder = Jwts.builder().setId(token.getJti()).setExpiration(calExp.getTime()); + JwtBuilder builder = + Jwts.builder() + .setId(token.getJti()) + .setExpiration(calExp.getTime()) + .setSubject(token.getSub()); for (Map.Entry claim : token.claims.entrySet()) { builder.claim(claim.getKey(), claim.getValue()); } diff --git a/core/src/main/java/org/stellar/anchor/auth/Sep24InteractiveUrlJwt.java b/core/src/main/java/org/stellar/anchor/auth/Sep24InteractiveUrlJwt.java index af6237e7ce..dd8fbfc3ca 100644 --- a/core/src/main/java/org/stellar/anchor/auth/Sep24InteractiveUrlJwt.java +++ b/core/src/main/java/org/stellar/anchor/auth/Sep24InteractiveUrlJwt.java @@ -6,7 +6,9 @@ import org.stellar.anchor.api.exception.SepException; public class Sep24InteractiveUrlJwt extends AbstractJwt { - public Sep24InteractiveUrlJwt(String jti, long exp, String clientDomain) throws SepException { + public Sep24InteractiveUrlJwt(String sub, String jti, long exp, String clientDomain) + throws SepException { + super.sub = sub; super.jti = jti; super.exp = exp; super.claim(CLIENT_DOMAIN, clientDomain); 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 ea98bfd478..61a0d90103 100644 --- a/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/auth/JwtServiceTest.kt @@ -23,6 +23,7 @@ internal class JwtServiceTest { val TEST_EXP = System.currentTimeMillis() / 1000 + 900 const val TEST_JTI = "test_jti" const val TEST_CLIENT_DOMAIN = "test_client_domain" + const val TEST_ACCOUNT = "GBLGJA4TUN5XOGTV6WO2BWYUI2OZR5GYQ5PDPCRMQ5XEPJOYWB2X4CJO" } lateinit var secretConfig: SecretConfig @@ -76,7 +77,7 @@ internal class JwtServiceTest { @Test fun `test apply Sep24InteractiveUrlJwt encoding and decoding and make sure the original values are not changed`() { val jwtService = JwtService(secretConfig) - val token = Sep24InteractiveUrlJwt(TEST_ISS, TEST_EXP, TEST_CLIENT_DOMAIN) + val token = Sep24InteractiveUrlJwt(TEST_ACCOUNT, TEST_ISS, TEST_EXP, TEST_CLIENT_DOMAIN) val cipher = jwtService.encode(token) val sep24InteractiveUrlJwt = jwtService.decode(cipher, Sep24InteractiveUrlJwt::class.java) diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java index 352e0d590b..5c8b4e1332 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructor.java @@ -1,5 +1,7 @@ package org.stellar.anchor.platform.service; +import static org.stellar.anchor.util.StringHelper.isEmpty; + import java.net.URI; import java.time.Instant; import java.util.HashMap; @@ -24,36 +26,45 @@ public SimpleInteractiveUrlConstructor(InteractiveUrlConfig config, JwtService j @Override @SneakyThrows public String construct(Sep24Transaction txn, String lang, HashMap sep9Fields) { + String token = constructToken(txn, lang, sep9Fields); + String baseUrl = config.getBaseUrl(); + URI uri = new URI(baseUrl); + return new URIBuilder() + .setScheme(uri.getScheme()) + .setHost(uri.getHost()) + .setPort(uri.getPort()) + .setPath(uri.getPath()) + .addParameter("transaction_id", txn.getTransactionId()) + // Add the JWT token + .addParameter("token", token) + .build() + .toURL() + .toString(); + } + + @SneakyThrows + String constructToken(Sep24Transaction txn, String lang, HashMap sep9Fields) { + String account = + (isEmpty(txn.getSep10AccountMemo())) + ? txn.getSep10Account() + : txn.getSep10Account() + ":" + txn.getSep10AccountMemo(); Sep24InteractiveUrlJwt token = new Sep24InteractiveUrlJwt( + account, txn.getTransactionId(), Instant.now().getEpochSecond() + config.getJwtExpiration(), txn.getClientDomain()); Map data = new HashMap<>(); - // Add lang field if (lang != null) { data.put("lang", lang); } - data.putAll(sep9Fields); // Add fields defined in txnFields UrlConstructorHelper.addTxnFields(data, txn, config.getTxnFields()); token.claim("data", data); - - String baseUrl = config.getBaseUrl(); - URI uri = new URI(baseUrl); - return new URIBuilder() - .setScheme(uri.getScheme()) - .setHost(uri.getHost()) - .setPort(uri.getPort()) - .setPath(uri.getPath()) - .addParameter("transaction_id", txn.getTransactionId()) - .addParameter("token", jwtService.encode(token)) - .build() - .toURL() - .toString(); + return jwtService.encode(token); } } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt index 1632bf4dbf..dd6e5bccbf 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/SimpleInteractiveUrlConstructorTest.kt @@ -42,15 +42,23 @@ class SimpleInteractiveUrlConstructorTest { val config = gson.fromJson(SIMPLE_CONFIG_JSON, PropertySep24Config.InteractiveUrlConfig::class.java) val constructor = SimpleInteractiveUrlConstructor(config, jwtService) - val url = constructor.construct(txn, "en", sep9Fields as HashMap?) - val params = UriComponentsBuilder.fromUriString(url).build().queryParams - val cipher = params["token"]!![0] - val jwt = jwtService.decode(cipher, Sep24InteractiveUrlJwt::class.java) - val claims = jwt.claims() + var jwt = + parseJwtFromUrl(constructor.construct(txn, "en", sep9Fields as HashMap?)) + testJwt(jwt) + assertEquals("GBLGJA4TUN5XOGTV6WO2BWYUI2OZR5GYQ5PDPCRMQ5XEPJOYWB2X4CJO:1234", jwt.sub) + + txn.sep10AccountMemo = null + jwt = parseJwtFromUrl(constructor.construct(txn, "en", sep9Fields as HashMap?)) + testJwt(jwt) + assertEquals("GBLGJA4TUN5XOGTV6WO2BWYUI2OZR5GYQ5PDPCRMQ5XEPJOYWB2X4CJO", jwt.sub) + } + + private fun testJwt(jwt: Sep24InteractiveUrlJwt) { + val claims = jwt.claims() assertEquals("txn_123", jwt.jti as String) assertTrue(Instant.ofEpochSecond(jwt.exp).isAfter(Instant.now())) - val data = claims.get("data") as Map + val data = claims["data"] as Map assertEquals("deposit", data["kind"] as String) assertEquals("John Doe", data["name"] as String) assertEquals( @@ -60,6 +68,12 @@ class SimpleInteractiveUrlConstructorTest { assertEquals("en", data["lang"] as String) assertEquals("john_doe@stellar.org", data["email"] as String) } + + private fun parseJwtFromUrl(url: String?): Sep24InteractiveUrlJwt { + val params = UriComponentsBuilder.fromUriString(url!!).build().queryParams + val cipher = params["token"]!![0] + return jwtService.decode(cipher, Sep24InteractiveUrlJwt::class.java) + } } private const val SIMPLE_CONFIG_JSON = @@ -91,6 +105,8 @@ private const val TXN_JSON = "transaction_id": "txn_123", "status": "incomplete", "kind" : "deposit", + "sep10_account": "GBLGJA4TUN5XOGTV6WO2BWYUI2OZR5GYQ5PDPCRMQ5XEPJOYWB2X4CJO", + "sep10_account_memo": "1234", "amount_in": "100", "amount_in_asset": "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" } From f1dcd487a66f78a7174272705029b1ad8375011d Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 16:34:26 -0800 Subject: [PATCH 0111/1439] Fix missing more_info URL validation (#764) --- .../platform/config/PropertySep24Config.java | 2 +- .../anchor/platform/config/Sep24ConfigTest.kt | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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 b2e8dbed6e..afce03942b 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 @@ -138,7 +138,7 @@ void validateMoreInfoUrlConfig(Errors errors) { } } } - if (isEmpty(secretConfig.getSep24InteractiveUrlJwtSecret())) { + if (isEmpty(secretConfig.getSep24MoreInfoUrlJwtSecret())) { errors.reject( "sep24-more-info-url-jwt-secret-not-defined", "Please set the secret.sep24.more_info_url.jwt_secret or SECRET_SEP24_MORE_INFO_URL_JWT_SECRET environment variable"); diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt index 67fce994f8..8cd41fe978 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt @@ -39,6 +39,20 @@ class Sep24ConfigTest { assertFalse(errors.hasErrors()) } + @Test + fun `test validation rejecting missing more_info url jwt secret`() { + every { secretConfig.sep24MoreInfoUrlJwtSecret } returns null + config.validate(config, errors) + assertEquals("sep24-more-info-url-jwt-secret-not-defined", errors.allErrors[0].code) + } + + @Test + fun `test validation rejecting missing interactive url jwt secret`() { + every { secretConfig.sep24InteractiveUrlJwtSecret } returns null + config.validate(config, errors) + assertEquals("sep24-interactive-url-jwt-secret-not-defined", errors.allErrors[0].code) + } + @ParameterizedTest @ValueSource(strings = ["httpss://www.stellar.org"]) fun `test interactive url with bad url configuration`(url: String) { From 6db6e416f972feaa10e7c37e2f77c301fe60cfb7 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 16 Feb 2023 17:52:40 -0800 Subject: [PATCH 0112/1439] [ANCHOR-179] Change kycVerified to boolean and updated the schemas.yml (#765) * Change type of kycVerified to boolean * Change kycVerified to boolean and update the schemas.yml --- .../reference/event/AnchorEventProcessor.java | 2 +- .../api/platform/PlatformTransactionData.java | 2 +- .../Communication/schemas.yml | 2 + .../platform/data/JdbcSep24Transaction.java | 2 +- .../platform/service/TransactionService.java | 4 +- .../platform/utils/TransactionHelper.java | 1 + .../db/migration/V6__sep24_field_updates.sql | 2 +- .../service/TransactionServiceTest.kt | 336 +++++++++++++----- 8 files changed, 258 insertions(+), 93 deletions(-) diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java index 0436d2a71e..54b95acc78 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/event/AnchorEventProcessor.java @@ -92,7 +92,7 @@ public void handleSep24WithdrawalTransactionCreatedEvent(AnchorEvent event) { PlatformTransactionData.builder() .id(event.getTransaction().getId()) .status(newStatus) - .kycVerified("true") + .kycVerified(event.getTransaction().getKycVerified()) .build()) .build())) .build(); diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java index 31a596d394..b620567594 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java @@ -34,7 +34,7 @@ public class PlatformTransactionData { Amount amountFee; @SerializedName("kyc_verified") - String kycVerified; + Boolean kycVerified = false; @SerializedName("quote_id") String quoteId; diff --git a/docs/03 - Implementing the Anchor Server/Communication/schemas.yml b/docs/03 - Implementing the Anchor Server/Communication/schemas.yml index 131db2e03e..ca80987bc0 100644 --- a/docs/03 - Implementing the Anchor Server/Communication/schemas.yml +++ b/docs/03 - Implementing the Anchor Server/Communication/schemas.yml @@ -253,6 +253,8 @@ components: $ref: '#/components/schemas/Amount' amount_fee: $ref: '#/components/schemas/Amount' + kyc_verified: + type: boolean quote_id: type: string started_at: diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index 047ead1ba8..b974e42af2 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -33,7 +33,7 @@ public String getProtocol() { String statusEta; @SerializedName("kyc_verified") - String kycVerified; + Boolean kycVerified; @SerializedName("more_info_url") String moreInfoUrl; 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 0d95a993ca..027fe17372 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 @@ -95,9 +95,9 @@ public GetTransactionResponse getTransactionResponse(String txnId) throws Anchor * @return an object of JdbcSepTransaction */ public JdbcSepTransaction findTransaction(String txnId) throws AnchorException { - JdbcSep31Transaction txn31 = (JdbcSep31Transaction) txn31Store.findByTransactionId(txnId); + Sep31Transaction txn31 = txn31Store.findByTransactionId(txnId); if (txn31 != null) { - return txn31; + return (JdbcSep31Transaction) txn31; } return (JdbcSep24Transaction) txn24Store.findByTransactionId(txnId); diff --git a/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java b/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java index b2a9e7c62f..c673496424 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java +++ b/platform/src/main/java/org/stellar/anchor/platform/utils/TransactionHelper.java @@ -80,6 +80,7 @@ static GetTransactionResponse toGetTransactionResponse( .amountIn(Amount.create(txn.getAmountIn(), amountInAsset)) .amountOut(Amount.create(txn.getAmountOut(), amountOutAsset)) .amountFee(Amount.create(txn.getAmountFee(), amountFeeAsset)) + .kycVerified(txn.getKycVerified()) // constructor is used because AMOUNT can be null, when ASSET is always non-null .amountExpected(new Amount(txn.getAmountExpected(), amountExpectedAsset)) .sourceAccount(sourceAccount) diff --git a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql index cf82acbb06..b963309ab1 100644 --- a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql +++ b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql @@ -2,7 +2,7 @@ ALTER TABLE sep24_transaction ADD sep10_account_memo VARCHAR(255); ALTER TABLE sep24_transaction ADD amount_expected VARCHAR(255); -ALTER TABLE sep24_transaction ADD kyc_verified VARCHAR(255); +ALTER TABLE sep24_transaction ADD kyc_verified BOOLEAN SET default FALSE; ALTER TABLE sep24_transaction ADD message VARCHAR(255); 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 c0d4e95b1c..b00b4af219 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 @@ -26,10 +26,7 @@ import org.stellar.anchor.api.shared.Amount import org.stellar.anchor.asset.AssetService import org.stellar.anchor.asset.DefaultAssetService import org.stellar.anchor.event.EventService -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.platform.data.JdbcSep38Quote +import org.stellar.anchor.platform.data.* import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore import org.stellar.anchor.sep38.Sep38QuoteStore @@ -94,8 +91,9 @@ class TransactionServiceTest { } @Test - fun test_getTransaction() { + fun `test get SEP31 transaction`() { // Mock the store + every { sep24TransactionStore.findByTransactionId(any()) } returns null every { sep31TransactionStore.newTransaction() } returns JdbcSep31Transaction() every { sep31TransactionStore.newRefunds() } returns JdbcSep31Refunds() every { sep31TransactionStore.newRefundPayment() } answers { JdbcSep31RefundPayment() } @@ -106,7 +104,27 @@ class TransactionServiceTest { val gotGetTransactionResponse = transactionService.getTransactionResponse(TEST_TXN_ID) JSONAssert.assertEquals( - wantedGetTransactionResponse, + wantedGetSep31TransactionResponse, + gson.toJson(gotGetTransactionResponse), + LENIENT + ) + } + + @Test + fun `test get SEP24 transaction`() { + // Mock the store + every { sep31TransactionStore.findByTransactionId(any()) } returns null + every { sep24TransactionStore.newInstance() } returns JdbcSep24Transaction() + every { sep24TransactionStore.newRefunds() } returns JdbcSep24Refunds() + every { sep24TransactionStore.newRefundPayment() } answers { JdbcSep24RefundPayment() } + + val mockSep24Transaction = gson.fromJson(jsonSep24Transaction, JdbcSep24Transaction::class.java) + + every { sep24TransactionStore.findByTransactionId(TEST_TXN_ID) } returns mockSep24Transaction + val gotGetTransactionResponse = transactionService.getTransactionResponse(TEST_TXN_ID) + + JSONAssert.assertEquals( + wantedGetSep24TransactionResponse, gson.toJson(gotGetTransactionResponse), LENIENT ) @@ -211,7 +229,7 @@ class TransactionServiceTest { ] ) fun test_validateIfStatusIsSupported(sepTxnStatus: SepTransactionStatus) { - assertDoesNotThrow { transactionService.validateIfStatusIsSupported(sepTxnStatus.getStatus()) } + assertDoesNotThrow { transactionService.validateIfStatusIsSupported(sepTxnStatus.status) } } @Test @@ -368,7 +386,67 @@ class TransactionServiceTest { assertTrue(testSep31Transaction.updatedAt > testSep31Transaction.startedAt) } - val jsonSep31Transaction = + private val jsonSep24Transaction = + """ +{ + "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", + "status_eta": "120", + "kind": "withdrawal", + "refunded": true, + "refunds": { + "amount_refunded": "90.0000", + "amount_fee": "8.0000", + "payments": [ + { + "id": "1111", + "amount": "50.0000", + "fee": "4.0000" + }, + { + "id": "2222", + "amount": "40.0000", + "fee": "4.0000" + } + ] + }, + "client_domain": "test.com", + "status": "pending_receiver", + "amount_in": "100.0000", + "amount_in_asset": "iso4217:USD", + "amount_out": "98.0000000", + "amount_out_asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", + "amount_fee": "2.0000", + "amount_fee_asset": "iso4217:USD", + "kyc_verified": true, + "started_at": "2022-12-19T02:06:44.500182800Z", + "completed_at": "2022-12-19T02:09:44.500182800Z", + "stellar_transaction_id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "external_transaction_id": "external-tx-id", + "stellarTransactions": [ + { + "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "memo": "my-memo", + "memo_type": "text", + "created_at": "2022-12-19T02:08:44.500182800Z", + "envelope": "here_comes_the_envelope", + "payments": [ + { + "id": "4609238642995201", + "amount": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "payment_type": "payment", + "source_account": "GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ", + "destination_account": "GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7" + } + ] + } + ] +} """ + .trimIndent() + + private val jsonSep31Transaction = """ { "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", @@ -448,103 +526,187 @@ class TransactionServiceTest { """ .trimIndent() - val testPlatformtransactionData = + private val wantedGetSep31TransactionResponse = """ - { - "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", - "sep": "31", - "kind": "receive", - "status": "pending_receiver", - "amount_expected": { - "amount": "100", - "asset": "iso4217:USD" - }, - "amount_in": { - "amount": "100.0000", - "asset": "iso4217:USD" - }, - "amount_out": { - "amount": "98.0000000", - "asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" - }, - "amount_fee": { - "amount": "2.0000", - "asset": "iso4217:USD" - }, - "quote_id": "quote-id", - "message": "Please don\u0027t forget to foo bar", - "refunds": { - "amount_refunded": { - "amount": "90.0000", + { + "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", + "sep": "31", + "kind": "receive", + "status": "pending_receiver", + "amount_expected": { + "amount": "100", + "asset": "iso4217:USD" + }, + "amount_in": { + "amount": "100.0000", "asset": "iso4217:USD" }, + "amount_out": { + "amount": "98.0000000", + "asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, "amount_fee": { - "amount": "8.0000", + "amount": "2.0000", "asset": "iso4217:USD" }, - "payments": [ - { - "id": "1111", - "id_type": "stellar", - "amount": { - "amount": "50.0000", - "asset": "iso4217:USD" - }, - "fee": { - "amount": "4.0000", - "asset": "iso4217:USD" - } + "quote_id": "quote-id", + "message": "Please don\u0027t forget to foo bar", + "refunds": { + "amount_refunded": { + "amount": "90.0000", + "asset": "iso4217:USD" + }, + "amount_fee": { + "amount": "8.0000", + "asset": "iso4217:USD" }, - { - "id": "2222", - "id_type": "stellar", - "amount": { - "amount": "40.0000", - "asset": "iso4217:USD" - }, - "fee": { - "amount": "4.0000", - "asset": "iso4217:USD" - } - } - ] - }, - "stellar_transactions": [ - { - "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", - "memo": "my-memo", - "memo_type": "text", - "envelope": "here_comes_the_envelope", "payments": [ { - "id": "4609238642995201", + "id": "1111", + "id_type": "stellar", "amount": { - "amount": "100.0000", + "amount": "50.0000", "asset": "iso4217:USD" }, - "payment_type": "payment", - "source_account": "GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ", - "destination_account": "GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7" + "fee": { + "amount": "4.0000", + "asset": "iso4217:USD" + } + }, + { + "id": "2222", + "id_type": "stellar", + "amount": { + "amount": "40.0000", + "asset": "iso4217:USD" + }, + "fee": { + "amount": "4.0000", + "asset": "iso4217:USD" + } } ] - } - ], - "external_transaction_id": "external-tx-id", - "customers": { - "sender": { - "id": "6c1770b0-0ea4-11ed-861d-0242ac120002" }, - "receiver": { - "id": "31212353-f265-4dba-9eb4-0bbeda3ba7f2" + "stellar_transactions": [ + { + "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "memo": "my-memo", + "memo_type": "text", + "envelope": "here_comes_the_envelope", + "payments": [ + { + "id": "4609238642995201", + "amount": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "payment_type": "payment", + "source_account": "GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ", + "destination_account": "GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7" + } + ] + } + ], + "external_transaction_id": "external-tx-id", + "customers": { + "sender": { + "id": "6c1770b0-0ea4-11ed-861d-0242ac120002" + }, + "receiver": { + "id": "31212353-f265-4dba-9eb4-0bbeda3ba7f2" + } + }, + "creator": { + "id": "141ee445-f32c-4c38-9d25-f4475d6c5558" } - }, - "creator": { - "id": "141ee445-f32c-4c38-9d25-f4475d6c5558" } - } - """ .trimIndent() - val wantedGetTransactionResponse = testPlatformtransactionData + private val wantedGetSep24TransactionResponse = + """ + { + "id": "a4baff5f-778c-43d6-bbef-3e9fb41d096e", + "sep": "24", + "kind": "withdrawal", + "status": "pending_receiver", + "amount_expected": { + "asset": "" + }, + "amount_in": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "amount_out": { + "amount": "98.0000000", + "asset": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" + }, + "amount_fee": { + "amount": "2.0000", + "asset": "iso4217:USD" + }, + "kyc_verified": true, + "started_at": "2022-12-19T02:06:44.500182800Z", + "completed_at": "2022-12-19T02:09:44.500182800Z", + "refunds": { + "amount_refunded": { + "amount": "90.0000", + "asset": "iso4217:USD" + }, + "amount_fee": { + "amount": "8.0000", + "asset": "iso4217:USD" + }, + "payments": [ + { + "id": "1111", + "id_type": "stellar", + "amount": { + "amount": "50.0000", + "asset": "iso4217:USD" + }, + "fee": { + "amount": "4.0000", + "asset": "iso4217:USD" + } + }, + { + "id": "2222", + "id_type": "stellar", + "amount": { + "amount": "40.0000", + "asset": "iso4217:USD" + }, + "fee": { + "amount": "4.0000", + "asset": "iso4217:USD" + } + } + ] + }, + "stellar_transactions": [ + { + "id": "2b862ac297c93e2db43fc58d407cc477396212bce5e6d5f61789f963d5a11300", + "memo": "my-memo", + "memo_type": "text", + "created_at": "2022-12-19T02:08:44.500182800Z", + "envelope": "here_comes_the_envelope", + "payments": [ + { + "id": "4609238642995201", + "amount": { + "amount": "100.0000", + "asset": "iso4217:USD" + }, + "payment_type": "payment", + "source_account": "GAS4OW4HKJCC2D6VWUHVFR3MJRRVQBXBFQ3LCZJXBR7TWOOBJWE4SRWZ", + "destination_account": "GBQC7NCZMQIPWN6ASUJYIDKDPRK34IOIZNQE5WOHPQH536VMOMQVJTN7" + } + ] + } + ], + "external_transaction_id": "external-tx-id" + } + """ + .trimIndent() } From 1cf0f51d48edc01516aebc453909fc95a77b25a0 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Fri, 17 Feb 2023 10:01:51 -0800 Subject: [PATCH 0113/1439] Restructured docker-compose folder (#767) * Restructured docker-compose folder --- .../common-services/common-services.yaml | 0 .../common-services/reference-config.yaml | 0 docker-compose/e2e-tests/PLACEHOLDER | 1 + docker-compose/{anchor-get-started => }/sep24/anchor-config.yaml | 0 .../{anchor-get-started => }/sep24/docker-compose.yaml | 0 docker-compose/{anchor-get-started => }/sep31/anchor-config.yaml | 0 .../{anchor-get-started => }/sep31/docker-compose.yaml | 0 7 files changed, 1 insertion(+) rename docker-compose/{anchor-get-started => }/common-services/common-services.yaml (100%) rename docker-compose/{anchor-get-started => }/common-services/reference-config.yaml (100%) create mode 100644 docker-compose/e2e-tests/PLACEHOLDER rename docker-compose/{anchor-get-started => }/sep24/anchor-config.yaml (100%) rename docker-compose/{anchor-get-started => }/sep24/docker-compose.yaml (100%) rename docker-compose/{anchor-get-started => }/sep31/anchor-config.yaml (100%) rename docker-compose/{anchor-get-started => }/sep31/docker-compose.yaml (100%) diff --git a/docker-compose/anchor-get-started/common-services/common-services.yaml b/docker-compose/common-services/common-services.yaml similarity index 100% rename from docker-compose/anchor-get-started/common-services/common-services.yaml rename to docker-compose/common-services/common-services.yaml diff --git a/docker-compose/anchor-get-started/common-services/reference-config.yaml b/docker-compose/common-services/reference-config.yaml similarity index 100% rename from docker-compose/anchor-get-started/common-services/reference-config.yaml rename to docker-compose/common-services/reference-config.yaml diff --git a/docker-compose/e2e-tests/PLACEHOLDER b/docker-compose/e2e-tests/PLACEHOLDER new file mode 100644 index 0000000000..3d789f8b83 --- /dev/null +++ b/docker-compose/e2e-tests/PLACEHOLDER @@ -0,0 +1 @@ +to be deleted \ No newline at end of file diff --git a/docker-compose/anchor-get-started/sep24/anchor-config.yaml b/docker-compose/sep24/anchor-config.yaml similarity index 100% rename from docker-compose/anchor-get-started/sep24/anchor-config.yaml rename to docker-compose/sep24/anchor-config.yaml diff --git a/docker-compose/anchor-get-started/sep24/docker-compose.yaml b/docker-compose/sep24/docker-compose.yaml similarity index 100% rename from docker-compose/anchor-get-started/sep24/docker-compose.yaml rename to docker-compose/sep24/docker-compose.yaml diff --git a/docker-compose/anchor-get-started/sep31/anchor-config.yaml b/docker-compose/sep31/anchor-config.yaml similarity index 100% rename from docker-compose/anchor-get-started/sep31/anchor-config.yaml rename to docker-compose/sep31/anchor-config.yaml diff --git a/docker-compose/anchor-get-started/sep31/docker-compose.yaml b/docker-compose/sep31/docker-compose.yaml similarity index 100% rename from docker-compose/anchor-get-started/sep31/docker-compose.yaml rename to docker-compose/sep31/docker-compose.yaml From 7ab3a3c0f228bb6068d72c2fb31905b1ef6abbec Mon Sep 17 00:00:00 2001 From: Paulo Nascimento <18144290+paulormnas@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:32:22 -0300 Subject: [PATCH 0114/1439] fix: update the returns form SEP-24 configs test (#768) Co-authored-by: Paulo Nascimento --- .../org/stellar/anchor/platform/config/Sep24ConfigTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt index 8cd41fe978..056ce8d648 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/Sep24ConfigTest.kt @@ -23,8 +23,8 @@ class Sep24ConfigTest { @BeforeEach fun setUp() { secretConfig = mockk() - every { secretConfig.sep24MoreInfoUrlJwtSecret } returns "interactive url jwt secret" - every { secretConfig.sep24InteractiveUrlJwtSecret } returns "more_info url jwt secret" + every { secretConfig.sep24MoreInfoUrlJwtSecret } returns "more_info url jwt secret" + every { secretConfig.sep24InteractiveUrlJwtSecret } returns "interactive url jwt secret" config = PropertySep24Config(secretConfig) config.enabled = true From 4eadfd9f48397368f7eb6b1ae13253ffc1374b11 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 21 Feb 2023 12:08:26 -0800 Subject: [PATCH 0115/1439] [ANCHOR-177] Add reading assets from the file system and enable YAML format support (#769) * Add YAML format support for assets * Add docker-compose file demonstration for external linked TOML, YAML and JSON files. --- .../org/stellar/anchor/api/sep/AssetInfo.java | 4 +- .../anchor/asset/DefaultAssetService.java | 48 +++- .../stellar/anchor/config/AssetsConfig.java | 28 +- .../org/stellar/anchor/sep1/Sep1Service.java | 4 +- .../org/stellar/anchor/util/FileUtil.java | 6 + .../anchor/asset/DefaultAssetServiceTest.kt | 265 ++++++++++++++++++ .../asset/ResourceJsonAssetServiceTest.kt | 40 --- .../anchor/dto/sep38/InfoResponseTest.kt | 2 +- .../stellar/anchor/sep12/Sep12ServiceTest.kt | 2 +- .../stellar/anchor/sep24/Sep24ServiceTest.kt | 2 +- .../stellar/anchor/sep31/Sep31ServiceTest.kt | 6 +- .../stellar/anchor/sep38/Sep38ServiceTest.kt | 2 +- core/src/test/resources/test_assets.yaml | 147 ++++++++++ core/src/test/resources/test_assets.yaml.bad | 2 + docker-compose/sep24/anchor-config.yaml | 47 ---- docker-compose/sep24/assets.yaml | 15 + docker-compose/sep24/docker-compose.yaml | 6 +- docker-compose/sep24/stellar.toml | 14 + docker-compose/sep31/anchor-config.yaml | 92 +----- docker-compose/sep31/assets.json | 71 +++++ docker-compose/sep31/docker-compose.yaml | 6 +- docker-compose/sep31/stellar.toml | 16 ++ .../observer/PaymentObserverBeans.java | 2 +- .../platform/component/share/AssetBeans.java | 3 +- .../platform/config/PropertyAssetsConfig.java | 59 ++-- .../config/anchor-config-default-values.yaml | 10 +- .../PaymentObservingAccountsBeansTest.kt | 4 +- .../anchor/platform/config/AppConfigTest.kt | 4 +- .../platform/config/AssetsConfigTest.kt | 58 ++++ .../service/TransactionServiceTest.kt | 6 +- .../sep31/Sep31DepositInfoGeneratorTest.kt | 2 +- platform/src/test/resources/test_assets.yaml | 95 +++++++ .../resources/test_assets_mal_format.json | 1 + .../resources/test_assets_mal_format.yaml | 1 + 34 files changed, 833 insertions(+), 237 deletions(-) create mode 100644 core/src/test/kotlin/org/stellar/anchor/asset/DefaultAssetServiceTest.kt delete mode 100644 core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt create mode 100644 core/src/test/resources/test_assets.yaml create mode 100644 core/src/test/resources/test_assets.yaml.bad delete mode 100644 docker-compose/sep24/anchor-config.yaml create mode 100644 docker-compose/sep24/assets.yaml create mode 100644 docker-compose/sep24/stellar.toml create mode 100644 docker-compose/sep31/assets.json create mode 100644 docker-compose/sep31/stellar.toml create mode 100644 platform/src/test/kotlin/org/stellar/anchor/platform/config/AssetsConfigTest.kt create mode 100644 platform/src/test/resources/test_assets.yaml create mode 100644 platform/src/test/resources/test_assets_mal_format.json create mode 100644 platform/src/test/resources/test_assets_mal_format.yaml 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 ece061e921..47e0e6d46f 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 @@ -44,10 +44,10 @@ public String getAssetName() { public enum Schema { @SerializedName("stellar") - STELLAR("stellar"), + stellar("stellar"), @SerializedName("iso4217") - ISO4217("iso4217"); + iso4217("iso4217"); private final String name; diff --git a/core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java b/core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java index 3dea3c1969..dc2281f227 100644 --- a/core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java +++ b/core/src/main/java/org/stellar/anchor/asset/DefaultAssetService.java @@ -5,8 +5,10 @@ import com.google.gson.Gson; import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; import lombok.NoArgsConstructor; import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.api.exception.SepNotFoundException; @@ -14,6 +16,8 @@ import org.stellar.anchor.config.AssetsConfig; import org.stellar.anchor.util.FileUtil; import org.stellar.anchor.util.GsonUtils; +import org.yaml.snakeyaml.Yaml; +import shadow.org.apache.commons.io.FilenameUtils; @NoArgsConstructor public class DefaultAssetService implements AssetService { @@ -21,11 +25,32 @@ public class DefaultAssetService implements AssetService { Assets assets; public static DefaultAssetService fromAssetConfig(AssetsConfig assetsConfig) - throws InvalidConfigException { + throws InvalidConfigException, IOException { switch (assetsConfig.getType()) { case JSON: return fromJson(assetsConfig.getValue()); case YAML: + return fromYaml(assetsConfig.getValue()); + case FILE: + String filename = assetsConfig.getValue(); + try { + String content = FileUtil.read(Path.of(filename)); + switch (FilenameUtils.getExtension(filename).toLowerCase()) { + case "json": + return fromJson(content); + case "yaml": + case "yml": + return fromYaml(content); + default: + throw new InvalidConfigException( + String.format("%s is not a supported file format", filename)); + } + } catch (Exception ex) { + throw new InvalidConfigException( + List.of(String.format("Cannot read from asset file: %s", filename)), ex); + } + case URL: + // TODO: to be implemented. default: infoF("assets type {} is not supported", assetsConfig.getType()); throw new InvalidConfigException( @@ -33,28 +58,42 @@ public static DefaultAssetService fromAssetConfig(AssetsConfig assetsConfig) } } + public static DefaultAssetService fromYaml(String assetsYaml) throws InvalidConfigException { + // snakeyaml does not support mapping snake-cased fields to camelCased fields. + // So we are converting to JSON and use the gson library for conversion + Map map = new Yaml().load(assetsYaml); + return fromJson(gson.toJson(map)); + } + public static DefaultAssetService fromJson(String assetsJson) throws InvalidConfigException { DefaultAssetService assetService = new DefaultAssetService(); assetService.assets = gson.fromJson(assetsJson, Assets.class); if (assetService.assets == null || assetService.assets.getAssets() == null || assetService.assets.getAssets().size() == 0) { - error("Invalid asset defined. assets JSON=", assetsJson); + error("Invalid asset defined. content=", assetsJson); throw new InvalidConfigException( "Invalid assets defined in configuration. Please check the logs for details."); } return assetService; } - public static DefaultAssetService fromResource(String assetPath) + public static DefaultAssetService fromJsonResource(String resourcePath) + throws IOException, SepNotFoundException, InvalidConfigException { + return fromJson(FileUtil.getResourceFileAsString(resourcePath)); + } + + public static DefaultAssetService fromYamlResource(String resourcePath) throws IOException, SepNotFoundException, InvalidConfigException { - return fromJson(FileUtil.getResourceFileAsString(assetPath)); + return fromYaml(FileUtil.getResourceFileAsString(resourcePath)); } + @Override public List listAllAssets() { return new ArrayList<>(assets.getAssets()); } + @Override public AssetInfo getAsset(String code) { for (AssetInfo asset : assets.getAssets()) { if (asset.getCode().equals(code)) { @@ -64,6 +103,7 @@ public AssetInfo getAsset(String code) { return null; } + @Override public AssetInfo getAsset(String code, String issuer) { if (issuer == null) { return getAsset(code); diff --git a/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java b/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java index ee702fc691..fc626e77ef 100644 --- a/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java +++ b/core/src/main/java/org/stellar/anchor/config/AssetsConfig.java @@ -1,12 +1,38 @@ package org.stellar.anchor.config; +import static org.stellar.anchor.util.StringHelper.isEmpty; + +import com.google.gson.annotations.SerializedName; +import org.stellar.anchor.api.exception.InvalidConfigException; + public interface AssetsConfig { AssetConfigType getType(); String getValue(); enum AssetConfigType { + @SerializedName("json") JSON, - YAML + @SerializedName("yaml") + YAML, + @SerializedName("file") + FILE, + @SerializedName("url") + URL; + + public static AssetConfigType from(String name) throws InvalidConfigException { + if (isEmpty(name)) throw new InvalidConfigException("Invalid asset type: " + name); + switch (name.toLowerCase()) { + case "json": + return JSON; + case "yaml": + return YAML; + case "file": + return FILE; + case "url": + return URL; + } + throw new InvalidConfigException(String.format("Invalid sep1.type:[%s]", 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 d6a77eba9e..5a1667b74d 100644 --- a/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java +++ b/core/src/main/java/org/stellar/anchor/sep1/Sep1Service.java @@ -4,10 +4,10 @@ import static org.stellar.anchor.util.Log.debugF; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.config.Sep1Config; +import org.stellar.anchor.util.FileUtil; import org.stellar.anchor.util.Log; import org.stellar.anchor.util.NetUtil; @@ -31,7 +31,7 @@ public Sep1Service(Sep1Config sep1Config) throws IOException, InvalidConfigExcep break; case FILE: debugF("reading stellar.toml from {}", sep1Config.getValue()); - tomlValue = Files.readString(Path.of(sep1Config.getValue())); + tomlValue = FileUtil.read(Path.of(sep1Config.getValue())); break; case URL: debugF("reading stellar.toml from {}", sep1Config.getValue()); diff --git a/core/src/main/java/org/stellar/anchor/util/FileUtil.java b/core/src/main/java/org/stellar/anchor/util/FileUtil.java index ea717cf2c4..ba01777d67 100644 --- a/core/src/main/java/org/stellar/anchor/util/FileUtil.java +++ b/core/src/main/java/org/stellar/anchor/util/FileUtil.java @@ -6,6 +6,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.stream.Collectors; import org.stellar.anchor.api.exception.SepNotFoundException; @@ -24,4 +26,8 @@ public static String getResourceFileAsString(String fileName) } } } + + public static String read(Path path) throws IOException { + return Files.readString(path); + } } diff --git a/core/src/test/kotlin/org/stellar/anchor/asset/DefaultAssetServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/asset/DefaultAssetServiceTest.kt new file mode 100644 index 0000000000..bfc038d825 --- /dev/null +++ b/core/src/test/kotlin/org/stellar/anchor/asset/DefaultAssetServiceTest.kt @@ -0,0 +1,265 @@ +package org.stellar.anchor.asset + +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import org.junit.jupiter.api.Assertions.assertEquals +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.ValueSource +import org.skyscreamer.jsonassert.JSONAssert +import org.skyscreamer.jsonassert.JSONCompareMode.LENIENT +import org.stellar.anchor.api.exception.SepNotFoundException +import org.stellar.anchor.util.GsonUtils +import org.yaml.snakeyaml.scanner.ScannerException +import shadow.org.apache.commons.io.FilenameUtils + +internal class DefaultAssetServiceTest { + private val gson: Gson = GsonUtils.getInstance() + + @BeforeEach fun setup() {} + @ParameterizedTest + @ValueSource(strings = ["test_assets.json", "test_assets.yaml"]) + fun `test assets loading and listing`(filename: String) { + + lateinit var das: DefaultAssetService + when (FilenameUtils.getExtension(filename)) { + "json" -> das = DefaultAssetService.fromJsonResource("test_assets.json") + "yaml" -> das = DefaultAssetService.fromYamlResource("test_assets.yaml") + } + + JSONAssert.assertEquals(expectedAssetsJson, gson.toJson(das.assets), LENIENT) + + // check listing function. + val assets = das.listAllAssets() + + assertEquals(3, assets.size) + } + + @Test + fun `test asset JSON file not found`() { + assertThrows { DefaultAssetService.fromJsonResource("not_found.json") } + + assertThrows { + DefaultAssetService.fromJsonResource("classpath:/test_assets.json") + } + } + + @Test + fun `test bad JSON and YAML file format`() { + assertThrows { + DefaultAssetService.fromJsonResource("test_assets.json.bad") + } + + assertThrows { DefaultAssetService.fromYamlResource("test_assets.yaml.bad") } + } + + // This is supposed to match the result from loading test_assets.json file. + private val expectedAssetsJson = + """ + { + "assets": [ + { + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GA7FYRB5VREZKOBIIKHG5AVTPFGWUBPOBF7LTYG4GTMFVIOOD2DWAL7I", + "schema": "stellar", + "significant_decimals": 2, + "deposit": { + "enabled": true, + "min_amount": 1, + "max_amount": 10000 + }, + "withdraw": { + "enabled": true, + "min_amount": 1, + "max_amount": 10000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "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", + "optional": false + }, + "receiver_account_number": { + "description": "bank account number of the destination", + "optional": false + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "SEPA", + "SWIFT" + ], + "optional": false + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "stellar:JPYC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "code": "JPYC", + "issuer": "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", + "schema": "stellar", + "significant_decimals": 0, + "deposit": { + "enabled": true, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": false, + "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": true, + "sep12": { + "sender": { + "types": { + "sep31-sender": { + "description": "Japanese citizens" + } + } + }, + "receiver": { + "types": { + "sep31-receiver": { + "description": "Japanese citizens receiving USD" + } + } + } + }, + "fields": { + "transaction": { + "receiver_routing_number": { + "description": "routing number of the destination bank account", + "optional": false + }, + "receiver_account_number": { + "description": "bank account number of the destination", + "optional": false + }, + "type": { + "description": "type of deposit to make", + "choices": [ + "ACH", + "SWIFT", + "WIRE" + ], + "optional": false + } + } + } + }, + "sep38": { + "exchangeable_assets": [ + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "iso4217:USD" + ] + }, + "sep24_enabled": true, + "sep31_enabled": true, + "sep38_enabled": true + }, + { + "code": "USD", + "schema": "iso4217", + "deposit": { + "enabled": true, + "min_amount": 1, + "max_amount": 1000000 + }, + "withdraw": { + "enabled": false, + "min_amount": 1, + "max_amount": 1000000 + }, + "send": { + "fee_fixed": 0, + "fee_percent": 0, + "min_amount": 1, + "max_amount": 1000000 + }, + "sep38": { + "exchangeable_assets": [ + "stellar:JPYC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + ], + "country_codes": [ + "USA" + ], + "sell_delivery_methods": [ + { + "name": "WIRE", + "description": "Send USD directly to the Anchor\u0027s bank account." + } + ], + "buy_delivery_methods": [ + { + "name": "WIRE", + "description": "Have USD sent directly to your bank account." + } + ], + "decimals": 4 + }, + "sep24_enabled": false, + "sep31_enabled": false, + "sep38_enabled": true + } + ] + } + """ + .trimIndent() +} diff --git a/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt deleted file mode 100644 index 338c45fd9c..0000000000 --- a/core/src/test/kotlin/org/stellar/anchor/asset/ResourceJsonAssetServiceTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.stellar.anchor.asset - -import com.google.gson.JsonSyntaxException -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNull -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.stellar.anchor.TestConstants.Companion.TEST_ASSET -import org.stellar.anchor.TestConstants.Companion.TEST_ASSET_ISSUER_ACCOUNT_ID -import org.stellar.anchor.api.exception.SepNotFoundException - -internal class ResourceJsonAssetServiceTest { - @Test - fun `test assets listing`() { - val rjas = DefaultAssetService.fromResource("test_assets.json") - assertEquals(3, rjas.assets.getAssets().size) - - val assets = rjas.listAllAssets() - assertEquals(3, assets.size) - - val asset = rjas.getAsset(TEST_ASSET, TEST_ASSET_ISSUER_ACCOUNT_ID) - assertEquals(asset.code, TEST_ASSET) - assertEquals(asset.issuer, TEST_ASSET_ISSUER_ACCOUNT_ID) - - assertEquals(rjas.getAsset(TEST_ASSET).code, TEST_ASSET) - - assertNull(rjas.getAsset("NA")) - } - - @Test - fun `test asset JSON file not found`() { - assertThrows { DefaultAssetService.fromResource("test_assets.json.bad") } - - assertThrows { DefaultAssetService.fromResource("not_found.json") } - - assertThrows { - DefaultAssetService.fromResource("classpath:/test_assets.json") - } - } -} 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 0f8092af5d..f5e9029927 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 @@ -15,7 +15,7 @@ class InfoResponseTest { @BeforeEach fun setUp() { - val rjas = DefaultAssetService.fromResource("test_assets.json") + val rjas = DefaultAssetService.fromJsonResource("test_assets.json") assets = rjas.listAllAssets() assertEquals(3, assets.size) } 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 49e3877e09..8c56b7674a 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep12/Sep12ServiceTest.kt @@ -55,7 +55,7 @@ class Sep12ServiceTest { fun setup() { MockKAnnotations.init(this, relaxUnitFun = true) - val rjas = DefaultAssetService.fromResource("test_assets.json") + val rjas = DefaultAssetService.fromJsonResource("test_assets.json") val assets = rjas.listAllAssets() every { assetService.listAllAssets() } returns assets 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 07f52afde8..9b665b3f4d 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -72,7 +72,7 @@ internal class Sep24ServiceTest { @MockK(relaxed = true) lateinit var moreInfoUrlConstructor: MoreInfoUrlConstructor - private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") + private val assetService: AssetService = DefaultAssetService.fromJsonResource("test_assets.json") private lateinit var jwtService: JwtService private lateinit var sep24Service: Sep24Service 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 1b356fbb6d..110eb61c47 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep31/Sep31ServiceTest.kt @@ -266,7 +266,7 @@ class Sep31ServiceTest { """ } - private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") + private val assetService: AssetService = DefaultAssetService.fromJsonResource("test_assets.json") @MockK(relaxed = true) private lateinit var txnStore: Sep31TransactionStore @@ -350,7 +350,7 @@ class Sep31ServiceTest { @Test fun `test quotes supported and required validation`() { val assetServiceQuotesNotSupported: AssetService = - DefaultAssetService.fromResource( + DefaultAssetService.fromJsonResource( "test_assets.json.quotes_required_but_not_supported", ) val ex: AnchorException = assertThrows { @@ -870,7 +870,7 @@ class Sep31ServiceTest { } val assetServiceQuotesNotSupported: AssetService = - DefaultAssetService.fromResource( + DefaultAssetService.fromJsonResource( "test_assets.json.quotes_not_supported", ) 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 31da43bd1d..fcacde7f0f 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep38/Sep38ServiceTest.kt @@ -66,7 +66,7 @@ class Sep38ServiceTest { fun setUp() { MockKAnnotations.init(this, relaxUnitFun = true) - val assetService = DefaultAssetService.fromResource("test_assets.json") + val assetService = DefaultAssetService.fromJsonResource("test_assets.json") val assets = assetService.listAllAssets() val sep8Config = PropertySep38Config() this.sep38Service = Sep38Service(sep8Config, assetService, null, null, eventService) diff --git a/core/src/test/resources/test_assets.yaml b/core/src/test/resources/test_assets.yaml new file mode 100644 index 0000000000..60ab3d25f5 --- /dev/null +++ b/core/src/test/resources/test_assets.yaml @@ -0,0 +1,147 @@ +assets: + - schema: stellar + code: USDC + issuer: GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP + distribution_account: GA7FYRB5VREZKOBIIKHG5AVTPFGWUBPOBF7LTYG4GTMFVIOOD2DWAL7I + significant_decimals: 2 + deposit: + enabled: true + fee_minimum: 0 + fee_percent: 0 + min_amount: 1 + max_amount: 10000 + withdraw: + enabled: true + fee_fixed: 0 + fee_percent: 0 + min_amount: 1 + max_amount: 10000 + send: + fee_fixed: 0 + fee_percent: 0 + min_amount: 1 + max_amount: 1000000 + 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:JPYC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 + - iso4217:USD + sep24_enabled: true + sep31_enabled: true + sep38_enabled: true + - schema: stellar + code: JPYC + issuer: GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 + significant_decimals: 0 + deposit: + enabled: true + fee_minimum: 0 + fee_percent: 0 + min_amount: 1 + max_amount: 1000000 + withdraw: + enabled: false + 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: true + sep12: + sender: + types: + sep31-sender: + description: Japanese citizens + receiver: + types: + sep31-receiver: + description: Japanese 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: + - ACH + - SWIFT + - WIRE + sep38: + exchangeable_assets: + - stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP + - iso4217:USD + sep24_enabled: true + sep31_enabled: true + sep38_enabled: true + - schema: iso4217 + code: USD + deposit: + enabled: true + fee_minimum: 0 + fee_percent: 0 + min_amount: 1 + max_amount: 1000000 + withdraw: + enabled: false + 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 + sep38: + exchangeable_assets: + - stellar:JPYC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 + - stellar:USDC: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. + sep31_enabled: false + sep38_enabled: true diff --git a/core/src/test/resources/test_assets.yaml.bad b/core/src/test/resources/test_assets.yaml.bad new file mode 100644 index 0000000000..35862b6da7 --- /dev/null +++ b/core/src/test/resources/test_assets.yaml.bad @@ -0,0 +1,2 @@ +assets: +schema stellar { bad format diff --git a/docker-compose/sep24/anchor-config.yaml b/docker-compose/sep24/anchor-config.yaml deleted file mode 100644 index 8f19453d1c..0000000000 --- a/docker-compose/sep24/anchor-config.yaml +++ /dev/null @@ -1,47 +0,0 @@ -version: 1 - -sep1: - 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" - TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" - - [[CURRENCIES]] - code = "USDC" - issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - status = "test" - is_asset_anchored = true - anchor_asset_type = "fiat" - desc = "A test USDC issued by Stellar." - -assets: - type: json - value: | - { - "assets": [ - { - "schema": "stellar", - "code": "USDC", - "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - "significant_decimals": 2, - "deposit": { - "enabled": true, - "min_amount": 1, - "max_amount": 1000000 - }, - "withdraw": { - "enabled": true, - "min_amount": 1, - "max_amount": 1000000 - }, - "sep24_enabled": true - } - ] - } - diff --git a/docker-compose/sep24/assets.yaml b/docker-compose/sep24/assets.yaml new file mode 100644 index 0000000000..99439d3c6e --- /dev/null +++ b/docker-compose/sep24/assets.yaml @@ -0,0 +1,15 @@ +assets: + - schema: stellar + code: USDC + issuer: GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP + distribution_account: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF + significant_decimals: 2 + deposit: + enabled: true + min_amount: 1 + max_amount: 1000000 + withdraw: + enabled: true + min_amount: 1 + max_amount: 1000000 + sep24_enabled: true diff --git a/docker-compose/sep24/docker-compose.yaml b/docker-compose/sep24/docker-compose.yaml index c24929be1a..a48b3e7f1d 100644 --- a/docker-compose/sep24/docker-compose.yaml +++ b/docker-compose/sep24/docker-compose.yaml @@ -4,7 +4,6 @@ services: image: stellar/anchor-platform:edge command: --sep-server environment: - - stellar_anchor_config=file:/config/anchor-config.yaml # secrets - secret.data.username=postgres - secret.data.password=password @@ -25,8 +24,13 @@ services: - data.server=db:5432 - data.database=postgres - data.flyway_enabled=false + # assets + - assets.type=file + - assets.value=/config/assets.yaml # seps - sep1.enabled=true + - sep1.toml.type=file + - sep1.toml.value=/config/stellar.toml - sep10.enabled=true - sep10.home_domain=localhost:8080 - sep24.enabled=true diff --git a/docker-compose/sep24/stellar.toml b/docker-compose/sep24/stellar.toml new file mode 100644 index 0000000000..f6a69a5ddb --- /dev/null +++ b/docker-compose/sep24/stellar.toml @@ -0,0 +1,14 @@ +ACCOUNTS = [] +VERSION = "0.1.0" +NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" +SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" +WEB_AUTH_ENDPOINT = "http://localhost:8080/auth" +TRANSFER_SERVER_SEP0024 = "http://localhost:8080/sep24" + +[[CURRENCIES]] +code = "USDC" +issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" +status = "test" +is_asset_anchored = true +anchor_asset_type = "fiat" +desc = "A test USDC issued by Stellar." \ No newline at end of file diff --git a/docker-compose/sep31/anchor-config.yaml b/docker-compose/sep31/anchor-config.yaml index 3dae37569f..afa65cb21c 100644 --- a/docker-compose/sep31/anchor-config.yaml +++ b/docker-compose/sep31/anchor-config.yaml @@ -1,97 +1,7 @@ version: 1 -sep1: - 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" - 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." + assets: type: json value: | - { - "assets": [ - { - "schema": "stellar", - "code": "USDC", - "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - "significant_decimals": 2, - "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" - ] - }, - "sep31_enabled": true, - "sep38_enabled": true - } - ] - } - diff --git a/docker-compose/sep31/assets.json b/docker-compose/sep31/assets.json new file mode 100644 index 0000000000..064e53c336 --- /dev/null +++ b/docker-compose/sep31/assets.json @@ -0,0 +1,71 @@ +{ + "assets": [ + { + "schema": "stellar", + "code": "USDC", + "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", + "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", + "significant_decimals": 2, + "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" + ] + }, + "sep31_enabled": true, + "sep38_enabled": true + } + ] +} + diff --git a/docker-compose/sep31/docker-compose.yaml b/docker-compose/sep31/docker-compose.yaml index a2c40910f4..6e84c89803 100644 --- a/docker-compose/sep31/docker-compose.yaml +++ b/docker-compose/sep31/docker-compose.yaml @@ -4,7 +4,6 @@ services: image: stellar/anchor-platform:edge command: --sep-server environment: - - stellar_anchor_config=file:/config/anchor-config.yaml # secrets - secret.data.username=postgres - secret.data.password=password @@ -23,8 +22,13 @@ services: - data.server=db:5432 - data.database=postgres - data.flyway_enabled=false + # assets + - assets.type=file + - assets.value=/config/assets.json # seps - sep1.enabled=true + - sep1.toml.type=file + - sep1.toml.value=/config/stellar.toml - sep10.enabled=true - sep10.home_domain=localhost:8080 - sep12.enabled=true diff --git a/docker-compose/sep31/stellar.toml b/docker-compose/sep31/stellar.toml new file mode 100644 index 0000000000..72692db196 --- /dev/null +++ b/docker-compose/sep31/stellar.toml @@ -0,0 +1,16 @@ +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" +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." \ No newline at end of file diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java index a589fca049..9865812b1b 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/observer/PaymentObserverBeans.java @@ -37,7 +37,7 @@ public StellarPaymentObserver stellarPaymentObserver( } List stellarAssets = assetService.listAllAssets().stream() - .filter(asset -> asset.getSchema().equals(AssetInfo.Schema.STELLAR)) + .filter(asset -> asset.getSchema().equals(AssetInfo.Schema.stellar)) .collect(Collectors.toList()); if (stellarAssets.size() == 0) { throw new ServerErrorException("Asset service should contain at least one Stellar asset."); diff --git a/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java b/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java index 8d0cac609d..8008f2f859 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java +++ b/platform/src/main/java/org/stellar/anchor/platform/component/share/AssetBeans.java @@ -1,5 +1,6 @@ package org.stellar.anchor.platform.component.share; +import java.io.IOException; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -18,7 +19,7 @@ AssetsConfig assetsConfig() { } @Bean - AssetService assetService(AssetsConfig assetsConfig) throws InvalidConfigException { + AssetService assetService(AssetsConfig assetsConfig) throws InvalidConfigException, IOException { return DefaultAssetService.fromAssetConfig(assetsConfig); } } 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 index eeba05948e..acb62aea95 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java +++ b/platform/src/main/java/org/stellar/anchor/platform/config/PropertyAssetsConfig.java @@ -2,14 +2,12 @@ import static org.stellar.anchor.util.Log.error; -import com.google.gson.JsonSyntaxException; import lombok.Data; import org.jetbrains.annotations.NotNull; import org.springframework.validation.Errors; import org.springframework.validation.Validator; -import org.stellar.anchor.asset.Assets; +import org.stellar.anchor.asset.DefaultAssetService; import org.stellar.anchor.config.AssetsConfig; -import org.stellar.anchor.util.GsonUtils; import org.stellar.anchor.util.StringHelper; @Data @@ -24,45 +22,50 @@ public boolean supports(@NotNull Class clazz) { @Override public void validate(@NotNull Object target, @NotNull Errors errors) { - PropertyAssetsConfig config = (PropertyAssetsConfig) target; - checkType(config, errors); - checkValue(config, errors); - } + if (this.getType() == null) { + errors.reject("invalid-no-type-defined", "assets.type is empty. Please define."); + } - void checkValue(PropertyAssetsConfig config, Errors errors) { - if (StringHelper.isEmpty(config.getValue())) { + if (StringHelper.isEmpty(this.getValue())) { errors.reject("invalid-no-value-defined", "assets.value is empty. Please define."); } else { - switch (config.getType()) { + switch (this.getType()) { case JSON: try { - GsonUtils.getInstance().fromJson(config.getValue(), Assets.class); - } catch (JsonSyntaxException jsex) { - error("JSON parsing exception:", jsex); + DefaultAssetService.fromJson(this.getValue()); + } catch (Exception ex) { + error("Error loading asset JSON", ex); errors.reject( "invalid-asset-json-format", "assets.value does not contain a valid JSON string for assets"); } break; case YAML: + try { + DefaultAssetService.fromYaml(this.getValue()); + } catch (Exception ex) { + error("Error loading asset YAML", ex); + errors.reject( + "invalid-asset-yaml-format", + "assets.value does not contain a valid YAML string for assets"); + } + break; + case FILE: + try { + DefaultAssetService.fromAssetConfig(this); + } catch (Exception ex) { + error("Error loading asset file", ex); + errors.reject( + "assets-file-not-valid", "Cannot read from asset file: " + this.getValue()); + } + break; + case URL: default: + errors.reject( + "invalid-type-defined", + String.format("assets.type:%s is not supported.", this.getType())); break; } } } - - void checkType(PropertyAssetsConfig config, Errors errors) { - if (config.getType() == null) { - errors.reject("invalid-no-type-defined", "assets.type is empty. Please define."); - } - switch (config.getType()) { - case JSON: - break; - case YAML: - default: - errors.reject( - "invalid-type-defined", - String.format("assets.type:%s is invalid. Only JSON is supported.", config.getType())); - } - } } 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 ea633142fe..f3355ee8ec 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -196,7 +196,7 @@ sep1: ## @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 + ## `url`: value contains the url to the stellar toml file type: string ## @param: value ## @type: string @@ -451,7 +451,11 @@ events: ## Accepts file reference (eg. 'file:assets.yaml') or in-line definition. assets: ## @param: type - ## @supported_values: `json` + ## @supported_values: + ## `json`: value field contains the content of the assets in JSON format + ## `yaml`: value field contains the content of the assets in YAML format + ## `file`: value field contains the path to the assets file. The file name can be *.json or *.yaml. + ## `url`: value contains the url to the assets file. The file name can be *.json or *.yaml. ## Describe the asset definition. # type: json @@ -468,7 +472,7 @@ assets: ## }] ## } # - value: "" + value: | ################################ ## Data Configuration diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt index 76f17451f3..b7f8d8990e 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/PaymentObservingAccountsBeansTest.kt @@ -38,7 +38,7 @@ class PaymentObservingAccountsBeansTest { @Test fun test_stellarPaymentObserverService_failure() { - val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") + val assetService: AssetService = DefaultAssetService.fromJsonResource("test_assets.json") val paymentObserverBeans = PaymentObserverBeans() val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) @@ -131,7 +131,7 @@ class PaymentObservingAccountsBeansTest { fun test_givenGoodManager_whenConstruct_thenOk() { // success! val paymentObserverBeans = PaymentObserverBeans() - val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") + val assetService: AssetService = DefaultAssetService.fromJsonResource("test_assets.json") val mockPaymentListener = mockk() val mockPaymentListeners = listOf(mockPaymentListener) 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 bcf5c76ad4..3684f164de 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 @@ -11,8 +11,8 @@ import org.springframework.validation.BindException import org.springframework.validation.Errors class AppConfigTest { - lateinit var config: PropertyAppConfig - lateinit var errors: Errors + private lateinit var config: PropertyAppConfig + private lateinit var errors: Errors @BeforeEach fun setUp() { diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/config/AssetsConfigTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/config/AssetsConfigTest.kt new file mode 100644 index 0000000000..24ac6e7274 --- /dev/null +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/config/AssetsConfigTest.kt @@ -0,0 +1,58 @@ +package org.stellar.anchor.platform.config + +import java.nio.file.Paths +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import org.springframework.validation.BindException +import org.springframework.validation.Errors +import org.stellar.anchor.asset.DefaultAssetService +import org.stellar.anchor.config.AssetsConfig.AssetConfigType.* +import org.stellar.anchor.util.FileUtil + +class AssetsConfigTest { + private lateinit var config: PropertyAssetsConfig + private lateinit var errors: Errors + + @BeforeEach + fun setUp() { + config = PropertyAssetsConfig() + errors = BindException(config, "config") + } + + @Test + fun `Test good assets configuration files`() { + config.type = JSON + config.value = FileUtil.getResourceFileAsString("test_assets.json") + config.validate(config, errors) + assertFalse(errors.hasErrors()) + + config.type = YAML + config.value = FileUtil.getResourceFileAsString("test_assets.yaml") + config.validate(config, errors) + assertFalse(errors.hasErrors()) + } + + @Test + fun `Test missing configuration file`() { + config.type = FILE + config.value = "file_missing.yaml" + config.validate(config, errors) + assertTrue(errors.hasErrors()) + assertErrorCode(errors, "assets-file-not-valid") + } + + @ParameterizedTest + @ValueSource(strings = ["/test_assets_mal_format.yaml", "/test_assets_mal_format.json"]) + fun `Test mal-formatted configuration file`(resourceFile: String) { + config.type = FILE + val resource = DefaultAssetService::class.java.getResource(resourceFile) + config.value = Paths.get(resource!!.toURI()).toFile().absolutePath + config.validate(config, errors) + assertTrue(errors.hasErrors()) + assertErrorCode(errors, "assets-file-not-valid") + } +} 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 b00b4af219..539ce5c849 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 @@ -183,7 +183,7 @@ class TransactionServiceTest { assertEquals("'$fiatUSD' is not a supported asset.", ex.message) // fails if listAllAssets does not contain the desired asset - this.assetService = DefaultAssetService.fromResource("test_assets.json") + this.assetService = DefaultAssetService.fromJsonResource("test_assets.json") ex = assertThrows { transactionService.validateAsset("amount_in", mockAsset) } assertInstanceOf(BadRequestException::class.java, ex) assertEquals("'$fiatUSD' is not a supported asset.", ex.message) @@ -191,7 +191,7 @@ class TransactionServiceTest { @Test fun test_validateAsset() { - this.assetService = DefaultAssetService.fromResource("test_assets.json") + this.assetService = DefaultAssetService.fromJsonResource("test_assets.json") transactionService = TransactionService( sep24TransactionStore, @@ -331,7 +331,7 @@ class TransactionServiceTest { println(gson.toJson(testSep38Quote)) - this.assetService = DefaultAssetService.fromResource("test_assets.json") + this.assetService = DefaultAssetService.fromJsonResource("test_assets.json") transactionService = TransactionService( sep24TransactionStore, 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 24c51d7cab..aa0a58cb9c 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/sep31/Sep31DepositInfoGeneratorTest.kt @@ -47,7 +47,7 @@ class Sep31DepositInfoGeneratorTest { """ } - private val assetService: AssetService = DefaultAssetService.fromResource("test_assets.json") + private val assetService: AssetService = DefaultAssetService.fromJsonResource("test_assets.json") @MockK(relaxed = true) private lateinit var txnStore: Sep31TransactionStore @MockK(relaxed = true) private lateinit var appConfig: AppConfig diff --git a/platform/src/test/resources/test_assets.yaml b/platform/src/test/resources/test_assets.yaml new file mode 100644 index 0000000000..c7f043b0c4 --- /dev/null +++ b/platform/src/test/resources/test_assets.yaml @@ -0,0 +1,95 @@ +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 + 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:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 + - iso4217:USD + sep24_enabled: true + sep31_enabled: true + sep38_enabled: true + - schema: iso4217 + code: USD + deposit: + enabled: true + fee_minimum: 0 + fee_percent: 0 + min_amount: 1 + max_amount: 1000000 + withdraw: + enabled: false + 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 + sep38: + exchangeable_assets: + - stellar:JPYC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 + - stellar:USDC:GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 + 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. + sep31_enabled: false + sep38_enabled: true diff --git a/platform/src/test/resources/test_assets_mal_format.json b/platform/src/test/resources/test_assets_mal_format.json new file mode 100644 index 0000000000..aa9ebaec57 --- /dev/null +++ b/platform/src/test/resources/test_assets_mal_format.json @@ -0,0 +1 @@ +{}} \ No newline at end of file diff --git a/platform/src/test/resources/test_assets_mal_format.yaml b/platform/src/test/resources/test_assets_mal_format.yaml new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/platform/src/test/resources/test_assets_mal_format.yaml @@ -0,0 +1 @@ +{} \ No newline at end of file From 7d489f5e978c4a30c32dbfb278ec1f64dab5eeb0 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 22 Feb 2023 10:42:20 -0800 Subject: [PATCH 0116/1439] Fix database migration file of kyc_verified field (#771) --- docker-compose/sep24/docker-compose.yaml | 2 +- docker-compose/sep31/docker-compose.yaml | 2 +- .../src/main/resources/db/migration/V6__sep24_field_updates.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose/sep24/docker-compose.yaml b/docker-compose/sep24/docker-compose.yaml index a48b3e7f1d..93e8e0e536 100644 --- a/docker-compose/sep24/docker-compose.yaml +++ b/docker-compose/sep24/docker-compose.yaml @@ -23,7 +23,7 @@ services: - data.type=postgres - data.server=db:5432 - data.database=postgres - - data.flyway_enabled=false + - data.flyway_enabled=true # assets - assets.type=file - assets.value=/config/assets.yaml diff --git a/docker-compose/sep31/docker-compose.yaml b/docker-compose/sep31/docker-compose.yaml index 6e84c89803..6023d2b8b2 100644 --- a/docker-compose/sep31/docker-compose.yaml +++ b/docker-compose/sep31/docker-compose.yaml @@ -21,7 +21,7 @@ services: - data.type=postgres - data.server=db:5432 - data.database=postgres - - data.flyway_enabled=false + - data.flyway_enabled=true # assets - assets.type=file - assets.value=/config/assets.json diff --git a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql index b963309ab1..808b34c7b2 100644 --- a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql +++ b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql @@ -2,7 +2,7 @@ ALTER TABLE sep24_transaction ADD sep10_account_memo VARCHAR(255); ALTER TABLE sep24_transaction ADD amount_expected VARCHAR(255); -ALTER TABLE sep24_transaction ADD kyc_verified BOOLEAN SET default FALSE; +ALTER TABLE sep24_transaction ADD kyc_verified BOOLEAN; ALTER TABLE sep24_transaction ADD message VARCHAR(255); From c210f5f0bb5cf96cfb6041bac783e9ef0e9ae6ed Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 22 Feb 2023 14:14:34 -0800 Subject: [PATCH 0117/1439] [Anchor-182] Add sep24 features flag configuration (#772) --- .../anchor/api/sep/sep24/InfoResponse.java | 49 ++++---- .../stellar/anchor/config/Sep24Config.java | 17 ++- .../stellar/anchor/sep24/Sep24Service.java | 23 ++-- .../anchor/api/sep/sep24/Sep24DtoTests.kt | 113 ------------------ .../stellar/anchor/sep24/Sep24ServiceTest.kt | 4 +- .../org/stellar/anchor/platform/Sep24Tests.kt | 2 +- .../platform/config/PropertySep24Config.java | 7 +- .../config/anchor-config-default-values.yaml | 6 + .../config/anchor-config-schema-v1.yaml | 2 + 9 files changed, 70 insertions(+), 153 deletions(-) delete mode 100644 core/src/test/kotlin/org/stellar/anchor/api/sep/sep24/Sep24DtoTests.kt diff --git a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java index 74f1565dd9..2f67ca2c1c 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/sep/sep24/InfoResponse.java @@ -1,46 +1,39 @@ package org.stellar.anchor.api.sep.sep24; +import static org.stellar.anchor.api.sep.AssetInfo.AssetOperation; + import com.google.gson.annotations.SerializedName; -import java.util.HashMap; import java.util.Map; -import lombok.Data; -import org.stellar.anchor.api.sep.AssetInfo; - -@Data +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder public class InfoResponse { - Map deposit = new HashMap<>(); - Map withdraw = new HashMap<>(); - FeeResponse fee = new FeeResponse(); - - @SerializedName("features") - FeatureFlagResponse featureFlags = new FeatureFlagResponse(false, false); + Map deposit; + Map withdraw; + FeeResponse fee; + FeatureFlagResponse features; @SuppressWarnings("unused") - @Data + @Getter + @Setter + @AllArgsConstructor public static class FeeResponse { - final Boolean enabled; - - public FeeResponse() { - this.enabled = true; - } + Boolean enabled; } - @Data + @Getter + @Setter + @AllArgsConstructor public static class FeatureFlagResponse { @SerializedName("account_creation") Boolean accountCreation; @SerializedName("claimable_balances") Boolean claimableBalances; - - public FeatureFlagResponse() { - this.accountCreation = true; - this.claimableBalances = true; - } - - public FeatureFlagResponse(boolean accountCreation, boolean claimableBalances) { - this.accountCreation = accountCreation; - this.claimableBalances = claimableBalances; - } } } diff --git a/core/src/main/java/org/stellar/anchor/config/Sep24Config.java b/core/src/main/java/org/stellar/anchor/config/Sep24Config.java index 2feb54c467..f2b5d1b3cd 100644 --- a/core/src/main/java/org/stellar/anchor/config/Sep24Config.java +++ b/core/src/main/java/org/stellar/anchor/config/Sep24Config.java @@ -1,6 +1,21 @@ package org.stellar.anchor.config; -@SuppressWarnings("unused") +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; + public interface Sep24Config { boolean isEnabled(); + + Features getFeatures(); + + @Getter + @Setter + class Features { + @SerializedName("account_creation") + Boolean accountCreation; + + @SerializedName("claimable_balances") + Boolean claimableBalances; + } } diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 3955d3cb4f..9259950a3f 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -2,6 +2,7 @@ import static org.stellar.anchor.api.event.AnchorEvent.Type.TRANSACTION_CREATED; import static org.stellar.anchor.api.sep.SepTransactionStatus.INCOMPLETE; +import static org.stellar.anchor.api.sep.sep24.InfoResponse.*; import static org.stellar.anchor.sep24.Sep24Helper.*; import static org.stellar.anchor.sep24.Sep24Transaction.Kind.WITHDRAWAL; import static org.stellar.anchor.sep9.Sep9Fields.extractSep9Fields; @@ -371,16 +372,24 @@ public Sep24GetTransactionResponse findTransaction(Sep10Jwt token, GetTransactio public InfoResponse getInfo() { info("Getting Sep24 info"); List assets = assetService.listAllAssets(); - InfoResponse info = new InfoResponse(); - debugF("{} assets found", assets.size()); + + Map depositMap = new HashMap<>(); + Map withdrawMap = new HashMap<>(); for (AssetInfo asset : assets) { - if (asset.getDeposit().getEnabled()) - info.getDeposit().put(asset.getCode(), asset.getDeposit()); - if (asset.getWithdraw().getEnabled()) - info.getWithdraw().put(asset.getCode(), asset.getWithdraw()); + if (asset.getDeposit().getEnabled()) depositMap.put(asset.getCode(), asset.getDeposit()); + if (asset.getWithdraw().getEnabled()) withdrawMap.put(asset.getCode(), asset.getWithdraw()); } - return info; + + return InfoResponse.builder() + .deposit(depositMap) + .withdraw(withdrawMap) + .fee(new FeeResponse(false)) + .features( + new FeatureFlagResponse( + sep24Config.getFeatures().getAccountCreation(), + sep24Config.getFeatures().getClaimableBalances())) + .build(); } TransactionResponse fromTxn(Sep24Transaction txn, String lang) 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 deleted file mode 100644 index 3f2cd46361..0000000000 --- a/core/src/test/kotlin/org/stellar/anchor/api/sep/sep24/Sep24DtoTests.kt +++ /dev/null @@ -1,113 +0,0 @@ -package org.stellar.anchor.api.sep.sep24 - -import org.junit.jupiter.api.Test -import org.stellar.anchor.api.sep.AssetInfo - -internal class Sep24DtoTests { - @Test - fun `test to cover AssetInfo`() { - val ar = AssetInfo() - ar.getSignificantDecimals() - ar.setSignificantDecimals(0) - ar.getSend() - ar.setSend(AssetInfo.SendOperation()) - ar.getSep31Enabled() - ar.setSep31Enabled(true) - - val so = AssetInfo.SendOperation() - so.getFeeFixed() - so.setFeeFixed(0) - so.getFeePercent() - so.setFeePercent(0) - so.getMinAmount() - so.setMinAmount(0) - so.getMaxAmount() - so.setMaxAmount(0) - } - - @Test - fun `test to cover WithdrawDepositTransactionResponse`() { - val dtr = DepositTransactionResponse() - dtr.setDepositMemo("") - dtr.getDepositMemo() - dtr.setDepositMemoType("id") - dtr.getDepositMemoType() - dtr.setClaimableBalanceId("0") - dtr.getClaimableBalanceId() - dtr.canEqual(Object()) - dtr.hashCode() - - val wtr = WithdrawTransactionResponse() - wtr.getWithdrawMemo() - wtr.setWithdrawMemo("") - wtr.getWithdrawMemoType() - wtr.setWithdrawMemoType("") - wtr.getWithdrawAnchorAccount() - wtr.setWithdrawAnchorAccount("") - wtr.canEqual(Object()) - wtr.canEqual(Object()) - wtr.hashCode() - } - - @Test - fun `test to cover GetTransactionRequest`() { - val gtr = GetTransactionRequest("", "", "", "") - gtr.canEqual(Object()) - } - - @Test - fun `test to cover InfoResponse`() { - val ir = InfoResponse() - ir.getFeatureFlags() - ir.setFeatureFlags(InfoResponse.FeatureFlagResponse()) - - val fr = InfoResponse.FeeResponse() - fr.getEnabled() - fr.canEqual(Object()) - - val ffr = InfoResponse.FeatureFlagResponse() - ffr.getAccountCreation() - ffr.setAccountCreation(true) - ffr.getClaimableBalances() - ffr.setClaimableBalances(true) - } - - @Test - fun `test to cover InteractiveTransactionResponse`() { - val itr = InteractiveTransactionResponse("", "", "") - itr.getType() - itr.setType("") - itr.getId() - itr.setId("") - itr.getUrl() - itr.setUrl("") - itr.canEqual(Object()) - } - - @Test - fun `test to cover TransactionResponse`() { - val tr = TransactionResponse() - tr.getStatus_eta() - tr.setStatus_eta(1) - tr.getMoreInfoUrl() - tr.setMoreInfoUrl("") - tr.getAmountIn() - tr.setAmountIn("1") - tr.getAmountInAsset() - tr.setAmountInAsset("") - tr.getAmountOut() - tr.setAmountOut("1") - tr.getAmountOutAsset() - tr.setAmountOutAsset("") - tr.getAmountFee() - tr.setAmountFee("1") - tr.getAmountFeeAsset() - tr.setAmountFeeAsset("1") - tr.getStellarTransactionId() - tr.setStellarTransactionId("") - tr.getExternalTransactionId() - tr.setExternalTransactionId("") - tr.getMessage() - tr.setMessage("") - } -} 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 9b665b3f4d..10609c7d2e 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep24/Sep24ServiceTest.kt @@ -462,7 +462,9 @@ internal class Sep24ServiceTest { assertEquals(1, response.withdraw.size) assertNotNull(response.deposit["USDC"]) assertNotNull(response.withdraw["USDC"]) - assertTrue(response.fee.enabled) + assertFalse(response.fee.enabled) + assertFalse(response.features.accountCreation) + assertFalse(response.features.claimableBalances) } @Test diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt index 21bf69ad5c..bea83a0384 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep24Tests.kt @@ -357,7 +357,7 @@ private const val expectedSep24Info = } }, "fee": { - "enabled": true + "enabled": false }, "features": { "account_creation": false, 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 afce03942b..499989bbfc 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 @@ -28,12 +28,14 @@ public class PropertySep24Config implements Sep24Config, Validator { InteractiveUrlConfig interactiveUrl; MoreInfoUrlConfig moreInfoUrl; SecretConfig secretConfig; + Features features; public PropertySep24Config(SecretConfig secretConfig) { this.secretConfig = secretConfig; } - @Data + @Getter + @Setter @AllArgsConstructor @NoArgsConstructor public static class InteractiveUrlConfig { @@ -42,7 +44,8 @@ public static class InteractiveUrlConfig { List txnFields; } - @Data + @Getter + @Setter @AllArgsConstructor @NoArgsConstructor public static class MoreInfoUrlConfig { 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 f3355ee8ec..9f3775f668 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -306,6 +306,12 @@ sep24: txn_fields: transaction_id jwt_expiration: 600 + # Configures the features flag returned by the SEP-24 /info endpoint + # For details, please refer to https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0024.md#response-2 + features: + account_creation: false + claimable_balances: false + sep31: ## @param: enabled ## @type: bool 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 6a9daee2f7..0fc7de51df 100644 --- a/platform/src/main/resources/config/anchor-config-schema-v1.yaml +++ b/platform/src/main/resources/config/anchor-config-schema-v1.yaml @@ -78,6 +78,8 @@ sep10.omnibus_account_list: sep10.require_known_omnibus_account: sep12.enabled: sep24.enabled: +sep24.features.account_creation: +sep24.features.claimable_balances: sep24.interactive_url.base_url: sep24.interactive_url.jwt_expiration: sep24.interactive_url.txn_fields: From 7545b8f2c07d462dcdb31cc3551188c50bfaf698 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Mon, 27 Feb 2023 13:42:59 -0800 Subject: [PATCH 0118/1439] [ANCHOR-183] Fix sep10account_memo database field and lock the database column names (#776) --- .../api/platform/PlatformTransactionData.java | 2 +- gradle/libs.versions.toml | 2 +- .../integration-test.anchor-config.yaml | 8 ++------ .../platform/data/JdbcSep24Transaction.java | 17 +++++++++++++++++ .../platform/data/JdbcSep31Transaction.java | 9 +++++++++ .../anchor/platform/data/JdbcSep38Quote.java | 13 +++++++++++++ .../platform/data/JdbcSepTransaction.java | 12 ++++++++++++ .../platform/data/PaymentObservingAccount.java | 1 + .../db/migration/V6__sep24_field_updates.sql | 4 ---- 9 files changed, 56 insertions(+), 12 deletions(-) diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java index b620567594..280d4beaea 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java @@ -34,7 +34,7 @@ public class PlatformTransactionData { Amount amountFee; @SerializedName("kyc_verified") - Boolean kycVerified = false; + Boolean kycVerified; @SerializedName("quote_id") String quoteId; diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5b37aafc40..4f4302dea5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,7 +52,7 @@ toml4j = "0.7.2" # Plugin versions spotless = "6.9.1" -spring-boot = "2.6.8" +spring-boot = "2.6.14" spring-dependency-management = "1.1.0" [libraries] 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 315177144d..f55e7792a3 100644 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ b/integration-tests/src/test/resources/integration-test.anchor-config.yaml @@ -21,12 +21,8 @@ sep1: 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." + code = "USDC" + issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" [DOCUMENTATION] ORG_NAME = "Stellar Development Foundation" diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java index b974e42af2..86c46e69ed 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24Transaction.java @@ -30,15 +30,19 @@ public String getProtocol() { String kind; @SerializedName("status_eta") + @Column(name = "status_eta") String statusEta; @SerializedName("kyc_verified") + @Column(name = "kyc_verified") Boolean kycVerified; @SerializedName("more_info_url") + @Column(name = "more_info_url") String moreInfoUrl; @SerializedName("transaction_id") + @Column(name = "transaction_id") String transactionId; String message; @@ -67,6 +71,7 @@ public void setRefunds(Sep24Refunds refunds) { * will transfer) their issued asset to. */ @SerializedName("withdraw_anchor_account") + @Column(name = "withdraw_anchor_account") String withdrawAnchorAccount; /** The memo for deposit or withdraw */ @@ -74,6 +79,7 @@ public void setRefunds(Sep24Refunds refunds) { /** The memo type of the transaction */ @SerializedName("memo_type") + @Column(name = "memo_type") String memoType; /** @@ -86,6 +92,7 @@ public void setRefunds(Sep24Refunds refunds) { * from. */ @SerializedName("from_account") + @Column(name = "from_account") String fromAccount; /** @@ -97,34 +104,44 @@ public void setRefunds(Sep24Refunds refunds) { * bank account. */ @SerializedName("to_account") + @Column(name = "to_account") String toAccount; @SerializedName("request_asset_code") + @Column(name = "request_asset_code") String requestAssetCode; @SerializedName("request_asset_issuer") + @Column(name = "request_asset_issuer") String requestAssetIssuer; /** The SEP10 account used for authentication. */ @SerializedName("sep10_account") + @Column(name = "sep10account") String sep10Account; /** The SEP10 account memo used for authentication. */ @SerializedName("sep10_account_memo") + @Column(name = "sep10account_memo") String sep10AccountMemo; @SerializedName("client_domain") + @Column(name = "client_domain") String clientDomain; @SerializedName("claimable_balance_supported") + @Column(name = "claimable_balance_supported") Boolean claimableBalanceSupported; @SerializedName("amount_expected") + @Column(name = "amount_expected") String amountExpected; @SerializedName("refund_memo") + @Column(name = "refund_memo") String refundMemo; @SerializedName("refund_memo_type") + @Column(name = "refund_memo_type") String refundMemoType; } 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 cd59a70f22..1acabd7492 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 @@ -33,30 +33,39 @@ public String getProtocol() { @Id String id; @SerializedName("status_eta") + @Column(name = "status_eta") Long statusEta; @SerializedName("stellar_account_id") + @Column(name = "stellar_account_id") String stellarAccountId; @SerializedName("stellar_memo") + @Column(name = "stellar_memo") String stellarMemo; @SerializedName("stellar_memo_type") + @Column(name = "stellar_memo_type") String stellarMemoType; @SerializedName("quote_id") + @Column(name = "quote_id") String quoteId; @SerializedName("client_domain") + @Column(name = "client_domain") String clientDomain; @SerializedName("sender_id") + @Column(name = "sender_id") String senderId; @SerializedName("receiver_id") + @Column(name = "receiver_id") String receiverId; @SerializedName("required_info_message") + @Column(name = "required_info_message") String requiredInfoMessage; @Convert(converter = StellarIdConverter.class) diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java index 4a4d120604..bcb4a6536c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep38Quote.java @@ -15,44 +15,57 @@ public class JdbcSep38Quote implements Sep38Quote { @Id String id; @SerializedName("expires_at") + @Column(name = "expires_at") Instant expiresAt; String price; @SerializedName("total_price") + @Column(name = "total_price") String totalPrice; @SerializedName("sell_asset") + @Column(name = "sell_asset") String sellAsset; @SerializedName("sell_amount") + @Column(name = "sell_amount") String sellAmount; @SerializedName("sell_delivery_method") + @Column(name = "sell_delivery_method") String sellDeliveryMethod; @SerializedName("buy_asset") + @Column(name = "buy_asset") String buyAsset; @SerializedName("buy_amount") + @Column(name = "buy_amount") String buyAmount; @SerializedName("buy_delivery_method") + @Column(name = "buy_delivery_method") String buyDeliveryMethod; @SerializedName("created_at") + @Column(name = "created_at") Instant createdAt; @SerializedName("creator_account_id") + @Column(name = "creator_account_id") String creatorAccountId; @SerializedName("creator_memo") + @Column(name = "creator_memo") String creatorMemo; @SerializedName("creator_memo_type") + @Column(name = "creator_memo_type") String creatorMemoType; @SerializedName("transaction_id") + @Column(name = "transaction_id") String transactionId; @Convert(converter = RateFeeConverter.class) diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java index f8145f3a30..553b25135c 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSepTransaction.java @@ -22,39 +22,51 @@ public abstract class JdbcSepTransaction { String status; @SerializedName("updated_at") + @Column(name = "updated_at") Instant updatedAt; @SerializedName("amount_in") + @Column(name = "amount_in") String amountIn; @SerializedName("amount_in_asset") + @Column(name = "amount_in_asset") String amountInAsset; @SerializedName("amount_out") + @Column(name = "amount_out") String amountOut; @SerializedName("amount_out_asset") + @Column(name = "amount_out_asset") String amountOutAsset; @SerializedName("amount_fee") + @Column(name = "amount_fee") String amountFee; @SerializedName("amount_fee_asset") + @Column(name = "amount_fee_asset") String amountFeeAsset; @SerializedName("started_at") + @Column(name = "started_at") Instant startedAt; @SerializedName("completed_at") + @Column(name = "completed_at") Instant completedAt; @SerializedName("transfer_received_at") + @Column(name = "transfer_received_at") Instant transferReceivedAt; @SerializedName("stellar_transaction_id") + @Column(name = "stellar_transaction_id") String stellarTransactionId; @SerializedName("external_transaction_id") + @Column(name = "external_transaction_id") String externalTransactionId; @Column(columnDefinition = "json") 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 index aae84e376d..002636cd0a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/PaymentObservingAccount.java @@ -25,5 +25,6 @@ public PaymentObservingAccount(String account, Instant lastObserved) { @Id String account; + @Column(name = "last_observed") Instant lastObserved; } diff --git a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql index 808b34c7b2..01838cd3c7 100644 --- a/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql +++ b/platform/src/main/resources/db/migration/V6__sep24_field_updates.sql @@ -1,5 +1,3 @@ -ALTER TABLE sep24_transaction ADD sep10_account_memo VARCHAR(255); - ALTER TABLE sep24_transaction ADD amount_expected VARCHAR(255); ALTER TABLE sep24_transaction ADD kyc_verified BOOLEAN; @@ -40,8 +38,6 @@ ALTER TABLE sep24_transaction DROP COLUMN asset_issuer; ALTER TABLE sep24_transaction DROP COLUMN muxed_account; -ALTER TABLE sep24_transaction DROP COLUMN sep10account_memo; - ALTER TABLE sep24_transaction DROP COLUMN completed_at; ALTER TABLE sep24_transaction DROP COLUMN started_at; From d1d811b5aa347b408b3beaecc04088ab57bc2ba5 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Thu, 2 Mar 2023 09:05:46 -0800 Subject: [PATCH 0119/1439] Refactor Github Workflows --- .github/workflows/basic_tests.yml | 91 ------------------- .github/workflows/branch_develop.yml | 51 ----------- .github/workflows/branch_release.yml | 46 ---------- .github/workflows/codeql_analysis.yml | 81 ----------------- .github/workflows/published.yml | 44 --------- ...end_tests.yml => sub_end_to_end_tests.yml} | 12 +-- .../workflows/sub_gradle_test_and_build.yml | 66 ++++++++++++++ .../workflows/sub_stellar_anchor_tests.yml | 44 +++++++++ .github/workflows/wf_pull_request.yml | 26 ++++++ .../wf_release_created_or_updated.yml | 58 ++++++++++++ .github/workflows/wk_push_to_develop.yml | 79 ++++++++++++++++ 11 files changed, 276 insertions(+), 322 deletions(-) delete mode 100644 .github/workflows/basic_tests.yml delete mode 100644 .github/workflows/branch_develop.yml delete mode 100644 .github/workflows/branch_release.yml delete mode 100644 .github/workflows/codeql_analysis.yml delete mode 100644 .github/workflows/published.yml rename .github/workflows/{end_to_end_tests.yml => sub_end_to_end_tests.yml} (75%) create mode 100644 .github/workflows/sub_gradle_test_and_build.yml create mode 100644 .github/workflows/sub_stellar_anchor_tests.yml create mode 100644 .github/workflows/wf_pull_request.yml create mode 100644 .github/workflows/wf_release_created_or_updated.yml create mode 100644 .github/workflows/wk_push_to_develop.yml diff --git a/.github/workflows/basic_tests.yml b/.github/workflows/basic_tests.yml deleted file mode 100644 index e9d573a016..0000000000 --- a/.github/workflows/basic_tests.yml +++ /dev/null @@ -1,91 +0,0 @@ -# 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: - 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 - -jobs: - build_and_test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v2 - 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 - - - name: Print Test Results - if: success() || failure() - run: | - 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 "\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 "\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 - - sep_validation_suite: - needs: [build_and_test] - runs-on: ubuntu-latest - name: Validate SEPs (1, 10, 12, 24, 31, 38) - env: - PR_NUMBER: ${{github.event.pull_request.number}} - BRANCH_NAME: ${{github.ref}} # e.g. refs/heads/main - NODE_TLS_REJECT_UNAUTHORIZED: 0 - steps: - - uses: actions/checkout@v3 - - # Find the server endpoint home domain to run the SEP tests. - - name: Find Home Domain (Preview or Dev) - 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 - 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" - - - name: Install Node - uses: actions/setup-node@v2 - with: - node-version: 14 - - - name: Run Validation Tool - env: - HOME_DOMAIN: ${{ steps.endpoint-finder.outputs.HOME_DOMAIN }} - run: | - npm install -g @stellar/anchor-tests - stellar-anchor-tests --home-domain $HOME_DOMAIN --seps 1 10 12 24 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') || contains(needs.*.result, 'cancelled') - run: exit 1 diff --git a/.github/workflows/branch_develop.yml b/.github/workflows/branch_develop.yml deleted file mode 100644 index 94be4b1926..0000000000 --- a/.github/workflows/branch_develop.yml +++ /dev/null @@ -1,51 +0,0 @@ -# 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 # when commits are pushed or merged onto `develop` branch - -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 - needs: [] - runs-on: ubuntu-latest - 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/branch_release.yml b/.github/workflows/branch_release.yml deleted file mode 100644 index 2e5e5caf74..0000000000 --- a/.github/workflows/branch_release.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Push/Merge to any `release` Branches - -on: - workflow_dispatch: - push: - branches: - - 'release/**' - - 'releases/**' - -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 - needs: [tests] - runs-on: ubuntu-latest - 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 }}-rc - 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/codeql_analysis.yml b/.github/workflows/codeql_analysis.yml deleted file mode 100644 index 541c11c035..0000000000 --- a/.github/workflows/codeql_analysis.yml +++ /dev/null @@ -1,81 +0,0 @@ -# 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: - workflow_dispatch: - 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}}" diff --git a/.github/workflows/published.yml b/.github/workflows/published.yml deleted file mode 100644 index d1371f4aee..0000000000 --- a/.github/workflows/published.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Release Published - -on: - workflow_dispatch: - release: - types: - - published - -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} - needs: [tests] - runs-on: ubuntu-latest - 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.event.release.tag_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/end_to_end_tests.yml b/.github/workflows/sub_end_to_end_tests.yml similarity index 75% rename from .github/workflows/end_to_end_tests.yml rename to .github/workflows/sub_end_to_end_tests.yml index f12026c899..f41865c766 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/sub_end_to_end_tests.yml @@ -1,15 +1,9 @@ -name: End-2-End Integration Tests +name: end-2-end Integration Tests on: + # allows this workflow to be called from another workflow workflow_dispatch: - workflow_call: # Allows this workflow to be called from another workflow - push: - branches: - - main -# - develop - - 'release/**' - - 'releases/**' -# - 'hotfix/**' + workflow_call: jobs: end_to_end_tests: diff --git a/.github/workflows/sub_gradle_test_and_build.yml b/.github/workflows/sub_gradle_test_and_build.yml new file mode 100644 index 0000000000..30a65c067f --- /dev/null +++ b/.github/workflows/sub_gradle_test_and_build.yml @@ -0,0 +1,66 @@ +name: Gradle Build and Test + +on: + # allows this workflow to be called from another workflow + workflow_dispatch: + workflow_call: + +jobs: + gradle_test_and_build: + name: Gradle Test and Build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Spotless Check + run: ./gradlew spotlessCheck || echo "❌ Your code is not properly formatted. You can run './gradlew spotlessApply' to format it. 👀" + + - name: Test and Build + run: ./gradlew build + + - name: Print Test Results + if: failure() + run: | + 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 "\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 "\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 + + analyze: + name: CodeQL Analysis + runs-on: ubuntu-22.04 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java', 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/sub_stellar_anchor_tests.yml b/.github/workflows/sub_stellar_anchor_tests.yml new file mode 100644 index 0000000000..246fba3080 --- /dev/null +++ b/.github/workflows/sub_stellar_anchor_tests.yml @@ -0,0 +1,44 @@ +name: Validate SEPs (1, 10, 12, 24, 31, 38) + +on: + # allows this workflow to be called from another workflow + workflow_dispatch: + workflow_call: + +jobs: + sep_validation_suite: + runs-on: ubuntu-22.04 + name: Validate SEPs (1, 10, 12, 24, 31, 38) + env: + PR_NUMBER: ${{github.event.pull_request.number}} + BRANCH_NAME: ${{github.ref}} # e.g. refs/heads/main + NODE_TLS_REJECT_UNAUTHORIZED: 0 + steps: + - uses: actions/checkout@v3 + + # Find the server endpoint home domain to run the SEP tests. + - name: Find Home Domain (preview or develop or main) + id: endpoint-finder + run: | + 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/develop ]]; then + export HOME_DOMAIN=https://anchor-sep-server-dev.stellar.org + 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" + + - name: Install NodeJs + uses: actions/setup-node@v2 + with: + node-version: 14 + + - name: Run Validation Tool + env: + HOME_DOMAIN: ${{ steps.endpoint-finder.outputs.HOME_DOMAIN }} + run: | + npm install -g @stellar/anchor-tests + stellar-anchor-tests --home-domain $HOME_DOMAIN --seps 1 10 12 24 31 38 --sep-config platform/src/test/resources/stellar-anchor-tests-sep-config.json diff --git a/.github/workflows/wf_pull_request.yml b/.github/workflows/wf_pull_request.yml new file mode 100644 index 0000000000..389dd89202 --- /dev/null +++ b/.github/workflows/wf_pull_request.yml @@ -0,0 +1,26 @@ +# This workflow will build a Java project with Gradle. +# This workflow is triggered: +# 1. Called from other workflows. +# 2. On all pull request events +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +name: Pull Request Workflow + +on: + pull_request: + +jobs: + gradle_test_and_build: + uses: ./.github/workflows/sub_gradle_test_and_build.yml + + sep_validation_suite: + needs: [ gradle_test_and_build ] + uses: ./.github/workflows/sub_stellar_anchor_tests.yml + + complete: + if: always() + needs: [gradle_test_and_build, sep_validation_suite] + runs-on: ubuntu-22.04 + steps: + - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: exit 1 diff --git a/.github/workflows/wf_release_created_or_updated.yml b/.github/workflows/wf_release_created_or_updated.yml new file mode 100644 index 0000000000..6ccd8428f1 --- /dev/null +++ b/.github/workflows/wf_release_created_or_updated.yml @@ -0,0 +1,58 @@ +name: Release Published + +on: + workflow_dispatch: + release: + types: [ created, edited ] + +jobs: + end_to_end_tests: + name: Run end-to-end Tests + uses: ./.github/workflows/sub_end_to_end_tests.yml + secrets: inherit + + build_and_push_docker_image: + name: Push to DockerHub (tag=stellar/anchor-platform:${{ github.event.release.tag_name }}) + needs: [ end_to_end_tests ] + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - name: Get anchor platform project version + shell: bash + id: get_version + run: echo ::set-output name=VERSION::$(${{github.workspace}}/gradlew -q printVersionName) + + - name: Fail release version and the project version do not match + if: ${{ '${{ github.event.release.tag_name }}' != '${{ steps.get_version.outputs.VERSION }}' }} + run: exit 1 + + - name: Get current date + id: get_date + run: echo "::set-output name=DATE::$(date +'%Y-%m-%d')" + + - name: Calculate Github 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:${{ github.event.release.tag_name }}-rc,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/wk_push_to_develop.yml b/.github/workflows/wk_push_to_develop.yml new file mode 100644 index 0000000000..6097c12c28 --- /dev/null +++ b/.github/workflows/wk_push_to_develop.yml @@ -0,0 +1,79 @@ +# 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: + push: + # when commits are pushed or merged onto `develop` branch + branches: [ develop ] + +jobs: + gradle_test_and_build: + uses: ./.github/workflows/sub_gradle_test_and_build.yml + + # TODO: enable the SEP validation when the develop deployment is fixed. +# sep_validation_suite: +# needs: [ gradle_test_and_build ] +# uses: ./.github/workflows/sub_stellar_anchor_tests.yml + + # TODO: enable end_to_end_tests after it is fixed. +# end_to_end_tests: +# name: Run end-to-end Tests +# uses: ./.github/workflows/sub_end_to_end_tests.yml +# secrets: inherit + + build_and_push_docker_image: + # stellar/anchor-platform:edge-${{ steps.get_date.outputs.date }}-${{ steps.get_sha.outputs.SHA }} + # and stellar/anchor-platform:edge + name: Push to DockerHub (tag=stellar/anchor-platform:edge) + # needs: [ gradle_test_and_build ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Login to DockerHub + uses: docker/login-action@bb984efc561711aaa26e433c32c3521176eae55b + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Get current date + id: get_date + run: echo "::set-output name=DATE::$(date +'%Y-%m-%d')" + + - name: Calculate Github SHA + shell: bash + id: get_sha + run: echo ::set-output name=SHA::$(git rev-parse --short ${{ github.sha }} ) + + - name: Build and push to DockerHub with tags `edge` and `edge-{date}-{sha}` + uses: docker/build-push-action@7f9d37fa544684fb73bfe4835ed7214c255ce02b + with: + push: true + tags: stellar/anchor-platform:edge,stellar/anchor-platform:edge-${{ steps.get_date.outputs.DATE }}-${{ steps.get_sha.outputs.SHA }} + file: Dockerfile + + # TODO: enable purge-image when we got the dockerhub token + # purge-image: + # name: Purge Docker edge Images (tags=edge-*) + # runs-on: ubuntu-22.04 + # steps: + # - name: Delete image + # uses: bots-house/ghcr-delete-image-action@v1.1.0 + # with: + # owner: stellarproducteng + # name: ${{ secrets.DOCKERHUB_USERNAME }} + # token: ${{ secrets.DOCKERHUB_TOKEN }} + # tag: edge-* + + complete: + if: always() + # TODO: add sep_validation_suite when Jenkins develop build is complete + needs: [ gradle_test_and_build, 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 From c078118b20fbb88623ad4d72e5c26ea24c110911 Mon Sep 17 00:00:00 2001 From: Gleb Date: Thu, 2 Mar 2023 14:17:11 -0800 Subject: [PATCH 0120/1439] Update toAccount (#783) --- core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java index 9259950a3f..10f4e55ce1 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -148,6 +148,7 @@ public InteractiveTransactionResponse withdraw( .fromAccount(sourceAccount) // TODO - jamie to add unique address generator .withdrawAnchorAccount(asset.getDistributionAccount()) + .toAccount(asset.getDistributionAccount()) .clientDomain(token.getClientDomain()); // TODO - jamie to look into memo vs withdrawal_memo From 984006ea95014093d2014a36cc02128f00ebc65b Mon Sep 17 00:00:00 2001 From: Gleb Date: Fri, 3 Mar 2023 11:20:04 -0800 Subject: [PATCH 0121/1439] [ANCHOR-103] Kotlin reference server (#709) * ANCHOR-103 deposit Remove test Add withdrawal Remove usage of interactive query params * Trigger * Add memo support * Add configuration * Add configurable configuration location * Fix config * Build fixes * Support PROXY mode * Update for the new spec * Add CORS Add CORS * Fix import * Trigger * fix * Small fix * Rename endpoint for user input * Small fix * Add missed SerialName * Small fixes * Fix after rebase * Fix config * Fixes for stellarTransactionId and Customers * Update reference server to latest dev AP * Fixes * Update reference server * Update distibution account * Update reference server * Replace test secret key * Update deposit service * Update after PR review * Rename package --- ... - Business Server - ServiceRunner.run.xml | 5 +- ...r - ServiceRunner [Example config].run.xml | 2 +- docker-compose.yaml | 2 + gradle/libs.versions.toml | 15 +- .../platform/AnchorPlatformIntegrationTest.kt | 1 + .../stellar/anchor/platform/SepTestSuite.kt | 5 + kotlin-reference-server/build.gradle.kts | 16 ++ .../com/example/RefenreceServerStart.kt | 6 - .../kotlin/com/example/ReferenceServer.kt | 28 --- .../main/kotlin/com/example/plugins/Sep24.kt | 34 ---- .../stellar/reference/RefenreceServerStart.kt | 5 + .../org/stellar/reference/ReferenceServer.kt | 69 +++++++ .../main/kotlin/org/stellar/reference/Util.kt | 3 + .../org/stellar/reference/data/Config.kt | 20 +++ .../kotlin/org/stellar/reference/data/Data.kt | 75 ++++++++ .../org/stellar/reference/data/Response.kt | 7 + .../org/stellar/reference/jwt/Decoder.kt | 36 ++++ .../org/stellar/reference/plugins/Route.kt | 170 ++++++++++++++++++ .../stellar/reference/plugins/TestRoute.kt | 103 +++++++++++ .../stellar/reference/sep24/DepositService.kt | 109 +++++++++++ .../stellar/reference/sep24/Sep24Helper.kt | 149 +++++++++++++++ .../reference/sep24/WithdrawalService.kt | 98 ++++++++++ .../src/main/resources/default-config.yaml | 13 ++ .../src/main/resources/docker-config.yaml | 2 + .../platform/utils/TransactionHelper.java | 1 + .../main/resources/example.anchor-config.yaml | 11 +- .../service/TransactionServiceTest.kt | 6 - .../anchor/platform/ServiceRunner.java | 20 ++- 28 files changed, 919 insertions(+), 92 deletions(-) delete mode 100644 kotlin-reference-server/src/main/kotlin/com/example/RefenreceServerStart.kt delete mode 100644 kotlin-reference-server/src/main/kotlin/com/example/ReferenceServer.kt delete mode 100644 kotlin-reference-server/src/main/kotlin/com/example/plugins/Sep24.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/RefenreceServerStart.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/ReferenceServer.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/Util.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/data/Config.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/data/Data.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/data/Response.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/jwt/Decoder.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Route.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/TestRoute.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/Sep24Helper.kt create mode 100644 kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/WithdrawalService.kt create mode 100644 kotlin-reference-server/src/main/resources/default-config.yaml create mode 100644 kotlin-reference-server/src/main/resources/docker-config.yaml diff --git a/.run/v2.0 - Business Server - ServiceRunner.run.xml b/.run/v2.0 - Business Server - ServiceRunner.run.xml index ed32e81850..aeb91a003d 100644 --- a/.run/v2.0 - Business Server - ServiceRunner.run.xml +++ b/.run/v2.0 - Business Server - ServiceRunner.run.xml @@ -1,5 +1,8 @@ - + + + +

Otherwise, returns max(most recent cursor - MAX_RESULTS, last stored cursor) + * 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 strLastStored = paymentStreamerCursorStore.load(); + String strLastStored = loadPagingToken(); Log.debug("Fetching latest cursor from Stellar network"); String strLatest = fetchLatestCursor(); Log.infoF("The latest cursor fetched from Stellar network is: {}", strLatest); - if (StringHelper.isEmpty(strLastStored)) { + if (isEmpty(strLastStored)) { return strLatest; } else { long lastStored = Long.parseLong(strLastStored); @@ -300,7 +333,7 @@ String fetchLatestCursor() { void handleEvent(OperationResponse operationResponse) { if (!operationResponse.isTransactionSuccessful()) { - paymentStreamerCursorStore.save(operationResponse.getPagingToken()); + savePagingToken(operationResponse.getPagingToken()); return; } @@ -324,7 +357,7 @@ void handleEvent(OperationResponse operationResponse) { } if (observedPayment == null) { - paymentStreamerCursorStore.save(operationResponse.getPagingToken()); + savePagingToken(operationResponse.getPagingToken()); } else { try { if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getTo())) { @@ -335,15 +368,9 @@ void handleEvent(OperationResponse operationResponse) { if (paymentObservingAccountsManager.lookupAndUpdate(observedPayment.getFrom()) && !observedPayment.getTo().equals(observedPayment.getFrom())) { - final ObservedPayment finalObservedPayment = observedPayment; - paymentListeners.forEach( - observer -> { - try { - observer.onSent(finalObservedPayment); - } catch (AnchorException | IOException e) { - throw new RuntimeException(e); - } - }); + for (PaymentListener listener : paymentListeners) { + listener.onSent(observedPayment); + } } publishingBackoffTimer.reset(); @@ -351,10 +378,13 @@ void handleEvent(OperationResponse operationResponse) { } 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 payment listeners.", ex); + errorEx("Failed to send event to payment listeners.", ex); setStatus(PUBLISHER_ERROR); + } catch (TransactionException tex) { + errorEx("Cannot save the cursor to database", tex); + setStatus(DATABASE_ERROR); } catch (Throwable t) { - Log.errorEx("Something went wrong in the observer while sending the event", t); + errorEx("Something went wrong in the observer while sending the event", t); setStatus(PUBLISHER_ERROR); } } @@ -364,15 +394,36 @@ void handleFailure(Optional exception) { // The SSEStreamer has internal errors. We will give up and let the container // manager to // restart. - Log.errorEx("stellar payment observer stream error: ", exception.get()); + errorEx("stellar payment observer stream error: ", exception.get()); // Mark the observer unhealthy setStatus(STREAM_ERROR); } + String loadPagingToken() { + String token = paymentStreamerCursorStore.load(); + databaseBackoffTimer.reset(); + return token; + } + + void savePagingToken(String token) { + paymentStreamerCursorStore.save(token); + databaseBackoffTimer.reset(); + } + void setStatus(ObserverStatus status) { - debugF("Setting status to {}", status); - this.status = status; + if (this.status != status) { + if (this.status.isSettable(status)) { + debugF("Setting status to {}", status); + this.status = status; + } else { + warnF("Cannot set status to {} while the current status is {}", status, this.status); + } + } + } + + boolean isHealthy() { + return (status == RUNNING); } @Override @@ -399,6 +450,7 @@ public HealthCheckResult check() { case STREAM_ERROR: case SILENCE_ERROR: case PUBLISHER_ERROR: + case DATABASE_ERROR: status = YELLOW; break; case NEEDS_SHUTDOWN: @@ -410,7 +462,6 @@ public HealthCheckResult check() { status = GREEN; break; } - StreamHealth.StreamHealthBuilder healthBuilder = StreamHealth.builder(); healthBuilder.account(mapStreamToAccount.get(stream)); // populate executorService information @@ -493,10 +544,43 @@ class StreamHealth { } enum ObserverStatus { + // healthy RUNNING, - STREAM_ERROR, - SILENCE_ERROR, + // errors + DATABASE_ERROR, PUBLISHER_ERROR, + SILENCE_ERROR, + STREAM_ERROR, + // shutdown NEEDS_SHUTDOWN, - SHUTDOWN, + SHUTDOWN; + + static final Map> stateTransition = new HashMap<>(); + + // Build the state transition + static { + addStateTransition( + RUNNING, + DATABASE_ERROR, + PUBLISHER_ERROR, + SILENCE_ERROR, + STREAM_ERROR, + NEEDS_SHUTDOWN, + SHUTDOWN); + addStateTransition(DATABASE_ERROR, RUNNING, NEEDS_SHUTDOWN, SHUTDOWN); + addStateTransition(PUBLISHER_ERROR, RUNNING, NEEDS_SHUTDOWN, SHUTDOWN); + addStateTransition(SILENCE_ERROR, RUNNING, NEEDS_SHUTDOWN, SHUTDOWN); + addStateTransition(STREAM_ERROR, RUNNING, NEEDS_SHUTDOWN, SHUTDOWN); + addStateTransition(NEEDS_SHUTDOWN, SHUTDOWN); + addStateTransition(SHUTDOWN, SHUTDOWN); + } + + static void addStateTransition(ObserverStatus source, ObserverStatus... dests) { + stateTransition.put(source, Set.of(dests)); + } + + public boolean isSettable(ObserverStatus dest) { + Set dests = stateTransition.get(this); + return dests != null && dests.contains(dest); + } } 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 162f7a8383..9d5f767516 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 @@ -197,7 +197,7 @@ public void onReceived(ObservedPayment payment) throws AnchorException, IOExcept } @Override - public void onSent(ObservedPayment payment) throws AnchorException, IOException {} + public void onSent(ObservedPayment payment) {} public void handleSep24Transaction(ObservedPayment payment, JdbcSep24Transaction txn) throws AnchorException, IOException { From ed3d8a5cfebcf0453e57709ca6886ecba6306a23 Mon Sep 17 00:00:00 2001 From: Gleb Date: Tue, 21 Mar 2023 17:25:45 -0700 Subject: [PATCH 0126/1439] Small fixes (#795) * Small fixes * Fixes --- .../org/stellar/reference/sep24/DepositService.kt | 13 ++++++++++++- .../anchor/platform/service/TransactionService.java | 11 +++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt index 630f45aa8c..469acab7c6 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/sep24/DepositService.kt @@ -3,6 +3,7 @@ package org.stellar.reference.sep24 import java.math.BigDecimal import mu.KotlinLogging import org.stellar.reference.data.* +import org.stellar.sdk.responses.operations.PaymentOperationResponse private val log = KotlinLogging.logger {} @@ -88,6 +89,16 @@ class DepositService(cfg: Config) { asset: String, amount: BigDecimal ) { + val operationId: Long = + sep24.server + .operations() + .forTransaction(stellarTransactionId) + .execute() + .records + .filterIsInstance() + .first() + .id + sep24.patchTransaction( PatchTransactionTransaction( transactionId, @@ -99,7 +110,7 @@ class DepositService(cfg: Config) { stellarTransactionId, payments = listOf( - StellarPayment(id = stellarTransactionId, Amount(amount.toPlainString(), asset)) + StellarPayment(id = operationId.toString(), Amount(amount.toPlainString(), asset)) ) ) ) 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 027fe17372..922a8b5444 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 @@ -7,6 +7,7 @@ import static org.stellar.anchor.util.BeanHelper.updateField; import static org.stellar.anchor.util.MathHelper.decimal; import static org.stellar.anchor.util.MathHelper.equalsAsDecimals; +import static org.stellar.anchor.util.MemoHelper.makeMemo; import static org.stellar.anchor.util.MemoHelper.memoTypeAsString; import static org.stellar.sdk.xdr.MemoType.MEMO_HASH; @@ -40,6 +41,7 @@ import org.stellar.anchor.util.Log; import org.stellar.anchor.util.SepHelper; import org.stellar.anchor.util.StringHelper; +import org.stellar.sdk.Memo; public class TransactionService { private final Sep38QuoteStore quoteStore; @@ -201,9 +203,14 @@ void updateSepTransaction(PlatformTransactionData patch, JdbcSepTransaction txn) case "24": JdbcSep24Transaction sep24Txn = (JdbcSep24Transaction) txn; + Memo memo = makeMemo(patch.getMemo(), patch.getMemoType()); + + if (memo != null) { + txnUpdated = updateField(patch, sep24Txn, "memo", txnUpdated); + txnUpdated = updateField(patch, sep24Txn, "memoType", txnUpdated); + } + txnUpdated = updateField(patch, sep24Txn, "message", txnUpdated); - txnUpdated = updateField(patch, sep24Txn, "memo", txnUpdated); - txnUpdated = updateField(patch, sep24Txn, "memoType", txnUpdated); txnUpdated = updateField(patch, sep24Txn, "kycVerified", txnUpdated); // update refunds From 3a3ef0b3bf5900e613406328b0330c04d40613b4 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Wed, 22 Mar 2023 16:31:49 -0700 Subject: [PATCH 0127/1439] [ANCHOR-214] [ANCHOR-215] Add docker compose to junit and cleanup docker-compose files (#798) * Add kotlin-wallet-sdk to library * Add docker-compose-rule to AnchorPlatformIntegrationTest * Clean up un-used docker-compose, assets, stellar.toml resources * Removed files that are used for documentation but not used in the code --- ...egrationTest - with docker-compose.run.xml | 31 ++ .../reference/AnchorReferenceServer.java | 12 +- build.gradle.kts | 2 + docker-compose.yaml | 59 --- docker-compose/e2e-tests/PLACEHOLDER | 1 - docker-compose/e2e-tests/docker-compose.yaml | 98 ----- docker-compose/sep24/assets.yaml | 15 - docker-compose/sep24/docker-compose.yaml | 76 ---- docker-compose/sep24/stellar.toml | 17 - docker-compose/sep31/anchor-config.yaml | 7 - docker-compose/sep31/assets.json | 71 ---- docker-compose/sep31/docker-compose.yaml | 66 ---- docker-compose/sep31/stellar.toml | 16 - gradle/libs.versions.toml | 6 + integration-tests/build.gradle.kts | 8 +- integration-tests/docker-compose-configs/.env | 3 - .../docker-compose-configs/Dockerfile | 17 - .../docker-compose-config.override.yaml | 46 --- .../anchor-platform-config.yaml | 344 ------------------ .../anchor-reference-server-config.yaml | 44 --- .../assets-test.json | 213 ----------- .../docker-compose-config.override.yaml | 36 -- .../stellar.toml | 26 -- .../docker-compose-config.override.yaml | 43 --- .../anchor-reference-server-config.yaml | 49 --- .../docker-compose-config.override.yaml | 48 --- .../docker-compose.base.yaml | 107 ------ .../docker-entrypoint.sh | 10 - .../platform/AnchorPlatformIntegrationTest.kt | 88 +++-- .../platform/ApiKeyAuthIntegrationTest.kt | 150 ++++---- .../anchor/platform/CallbackApiTests.kt | 179 +++++---- .../stellar/anchor/platform/ResourceHelper.kt | 23 ++ .../resources/anchor-config-integration.yaml | 309 ---------------- .../test/resources/common/docker-compose.yaml | 42 +-- .../resources/common}/reference-config.yaml | 8 +- .../integration-test.anchor-config.yaml | 266 -------------- .../src/test/resources/integration.env | 4 - .../test/resources/test-default/assets.yaml | 142 ++++++++ .../test-default/docker-compose.yaml | 19 + .../src/test/resources/test-default/env | 36 ++ .../resources/test-default/stellar.toml} | 10 +- .../stellar/reference/RefenreceServerStart.kt | 4 +- .../org/stellar/reference/ReferenceServer.kt | 59 +-- .../plugins/{Route.kt => Sep24Route.kt} | 0 .../{TestRoute.kt => Sep24TestRoute.kt} | 0 .../platform/AbstractPlatformServer.java | 9 +- .../anchor/platform/AnchorPlatformServer.java | 12 +- .../platform/EventProcessingServer.java | 12 +- .../platform/StellarObservingServer.java | 10 +- .../configurator/ConfigEnvironment.java | 13 +- platform/src/main/resources/assets-prod.json | 82 ----- platform/src/main/resources/assets-test.json | 213 ----------- .../config/anchor-config-default-values.yaml | 2 +- platform/src/main/resources/example.env | 27 -- .../src/main/resources/sep1/stellar-wks.toml | 26 -- .../anchor/platform/ServiceRunner.java | 21 +- 56 files changed, 597 insertions(+), 2640 deletions(-) create mode 100644 .run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml delete mode 100644 docker-compose.yaml delete mode 100644 docker-compose/e2e-tests/PLACEHOLDER delete mode 100644 docker-compose/e2e-tests/docker-compose.yaml delete mode 100644 docker-compose/sep24/assets.yaml delete mode 100644 docker-compose/sep24/docker-compose.yaml delete mode 100644 docker-compose/sep24/stellar.toml delete mode 100644 docker-compose/sep31/anchor-config.yaml delete mode 100644 docker-compose/sep31/assets.json delete mode 100644 docker-compose/sep31/docker-compose.yaml delete mode 100644 docker-compose/sep31/stellar.toml delete mode 100644 integration-tests/docker-compose-configs/.env delete mode 100644 integration-tests/docker-compose-configs/Dockerfile delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-reference-server-config.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/assets-test.json delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/docker-compose-config.override.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-default-configs/stellar.toml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-reference-server-config.yaml delete mode 100644 integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml delete mode 100644 integration-tests/docker-compose-configs/docker-compose.base.yaml delete mode 100644 integration-tests/docker-compose-configs/docker-entrypoint.sh create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/ResourceHelper.kt delete mode 100644 integration-tests/src/test/resources/anchor-config-integration.yaml rename docker-compose/common-services/common-services.yaml => integration-tests/src/test/resources/common/docker-compose.yaml (52%) rename {docker-compose/common-services => integration-tests/src/test/resources/common}/reference-config.yaml (91%) delete mode 100644 integration-tests/src/test/resources/integration-test.anchor-config.yaml delete mode 100644 integration-tests/src/test/resources/integration.env create mode 100644 integration-tests/src/test/resources/test-default/assets.yaml create mode 100644 integration-tests/src/test/resources/test-default/docker-compose.yaml create mode 100644 integration-tests/src/test/resources/test-default/env rename integration-tests/src/{main/resources/sep1/test-stellar.toml => test/resources/test-default/stellar.toml} (68%) rename kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/{Route.kt => Sep24Route.kt} (100%) rename kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/{TestRoute.kt => Sep24TestRoute.kt} (100%) delete mode 100644 platform/src/main/resources/assets-prod.json delete mode 100644 platform/src/main/resources/assets-test.json delete mode 100644 platform/src/main/resources/example.env delete mode 100644 platform/src/main/resources/sep1/stellar-wks.toml diff --git a/.run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml b/.run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml new file mode 100644 index 0000000000..4e20e538e4 --- /dev/null +++ b/.run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceServer.java b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceServer.java index b72d85151f..f0d03a1952 100644 --- a/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceServer.java +++ b/anchor-reference-server/src/main/java/org/stellar/anchor/reference/AnchorReferenceServer.java @@ -22,7 +22,9 @@ public class AnchorReferenceServer implements WebMvcConfigurer { static final String REFERENCE_SERVER_PORT = "REFERENCE_SERVER_PORT"; static final String REFERENCE_SERVER_CONTEXT_PATH = "REFERENCE_SERVER_CONTEXT_PATH"; - public static void start(int port, String contextPath) { + static ConfigurableApplicationContext ctx; + + public static ConfigurableApplicationContext start(int port, String contextPath) { SpringApplicationBuilder builder = new SpringApplicationBuilder(AnchorReferenceServer.class) .bannerMode(OFF) @@ -32,7 +34,13 @@ public static void start(int port, String contextPath) { SpringApplication app = builder.build(); app.addInitializers(new PropertySourceInitializer()); - app.run(); + return ctx = app.run(); + } + + public static void stop() { + if (ctx != null) { + SpringApplication.exit(ctx); + } } public static class PropertySourceInitializer diff --git a/build.gradle.kts b/build.gradle.kts index f3f058d619..eff7ba8638 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -67,6 +67,8 @@ subprojects { implementation(rootProject.libs.bundles.kafka) implementation(rootProject.libs.spring.kafka) implementation(rootProject.libs.log4j.template.json) + implementation(rootProject.libs.stellar.wallet.sdk) + // Although the following libraries are transitive dependencies, we are including them here to override the version // for security vulnerabilities. diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 8e149d0ab2..0000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,59 +0,0 @@ -version: '2.4' -services: - anchor-platform: - image: stellar/anchor-platform:edge - 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 - - KT_REFERENCE_SERVER_CONFIG=/kt-ref-config/docker-config.yaml - - LOG_APPENDER=console_appender - - SECRET_DATA_USERNAME=postgres - - SECRET_DATA_PASSWORD=password - - SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret - - SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret - - SECRET_SEP10_JWT_SECRET=secret - - SECRET_SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - depends_on: - - db - - kafka - volumes: - - ./platform/src/main/resources:/config - - ./anchor-reference-server/src/main/resources:/reference-config - - ./kotlin-reference-server/src/main/resources:/kt-ref-config - ports: - - "8080:8080" - - "8081:8081" - - "8091:8091" - - db: - image: postgres:latest - ports: - - "5431:5432" # use 5431 so this doesn't conflict if you have a local postgres instance running - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=password - - zookeeper: - platform: linux/x86_64 - image: confluentinc/cp-zookeeper:latest - environment: - ZOOKEEPER_CLIENT_PORT: 2181 - ZOOKEEPER_TICK_TIME: 2000 - ports: - - 2181:2181 - - kafka: - platform: linux/x86_64 - image: confluentinc/cp-kafka:latest - depends_on: - - zookeeper - ports: - - 29092:29092 - environment: - KAFKA_BROKER_ID: 1 - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT - KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 diff --git a/docker-compose/e2e-tests/PLACEHOLDER b/docker-compose/e2e-tests/PLACEHOLDER deleted file mode 100644 index 3d789f8b83..0000000000 --- a/docker-compose/e2e-tests/PLACEHOLDER +++ /dev/null @@ -1 +0,0 @@ -to be deleted \ No newline at end of file diff --git a/docker-compose/e2e-tests/docker-compose.yaml b/docker-compose/e2e-tests/docker-compose.yaml deleted file mode 100644 index b4467dc05b..0000000000 --- a/docker-compose/e2e-tests/docker-compose.yaml +++ /dev/null @@ -1,98 +0,0 @@ -version: '2.4' -services: - platform: - image: stellar/anchor-platform:edge - build: - context: ../../ - dockerfile: integration-tests/docker-compose-configs/Dockerfile - command: --sep-server - environment: - # secrets - - secret.data.username=postgres - - secret.data.password=password - - secret.platform_api.auth_secret=myAnchorToPlatformSecret - - secret.callback_api.auth_secret=myPlatformToAnchorSecret - - secret.sep10.jwt_secret=secret_sep10_secret - - secret.sep10.signing_seed=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - - secret.sep24.interactive_url.jwt_secret=secret_sep24_interactive_url_jwt_secret - - secret.sep24.more_info_url.jwt_secret=secret_sep24_more_info_url_jwt_secret - # logging - - logging.stellar_level=DEBUG - # events - - events.enabled=true - - events.publisher.type=kafka - - events.publisher.kafka.bootstrap_server=kafka:29092 - # data - - data.type=postgres - - data.server=db:5432 - - data.database=postgres - - data.flyway_enabled=true - # assets - - assets.type=file - - assets.value=/config/assets.yaml - # seps - - sep1.enabled=true - - sep1.toml.type=file - - sep1.toml.value=/config/stellar.toml - - sep10.enabled=true - - sep10.home_domain=localhost:8080 - - sep24.enabled=true - - sep24.interactive_url.base_url=https://stellar.moneygram.com - - sep24.more_info_url.base_url=http://localhost:8080/sep24/transaction/more_info - depends_on: - - db - - kafka - - reference_server - volumes: - - ../sep24:/config - ports: - - "8080:8080" # sep-server - - "8082:8082" # Java management server - extra_hosts: - - "host.docker.internal:host-gateway" - - reference_server: - extends: - file: ../common-services/common-services.yaml - service: reference_server - extra_hosts: - - "host.docker.internal:host-gateway" - - observer: - extends: - file: ../common-services/common-services.yaml - service: observer - - kafka: - extends: - file: ../common-services/common-services.yaml - service: kafka - extra_hosts: - - "host.docker.internal:host-gateway" - - zookeeper: - extends: - file: ../common-services/common-services.yaml - service: zookeeper - extra_hosts: - - "host.docker.internal:host-gateway" - - db: - extends: - file: ../common-services/common-services.yaml - service: db - extra_hosts: - - "host.docker.internal:host-gateway" - - app: - build: - context: ../../ - dockerfile: end-to-end-tests/Dockerfile - env_file: - - ../../end-to-end-tests/.env - depends_on: - - platform - extra_hosts: - - "host.docker.internal:host-gateway" - volumes: - - ../../end-to-end-tests/:/app diff --git a/docker-compose/sep24/assets.yaml b/docker-compose/sep24/assets.yaml deleted file mode 100644 index 99439d3c6e..0000000000 --- a/docker-compose/sep24/assets.yaml +++ /dev/null @@ -1,15 +0,0 @@ -assets: - - schema: stellar - code: USDC - issuer: GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP - distribution_account: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF - significant_decimals: 2 - deposit: - enabled: true - min_amount: 1 - max_amount: 1000000 - withdraw: - enabled: true - min_amount: 1 - max_amount: 1000000 - sep24_enabled: true diff --git a/docker-compose/sep24/docker-compose.yaml b/docker-compose/sep24/docker-compose.yaml deleted file mode 100644 index 5ac161f19b..0000000000 --- a/docker-compose/sep24/docker-compose.yaml +++ /dev/null @@ -1,76 +0,0 @@ -version: '2.4' -services: - platform: - image: stellar/anchor-platform:edge - command: --sep-server - environment: - # secrets - - secret.data.username=postgres - - secret.data.password=password - - secret.platform_api.auth_secret=myAnchorToPlatformSecret - - secret.callback_api.auth_secret=myPlatformToAnchorSecret - - secret.sep10.jwt_secret=secret_sep10_secret - - secret.sep10.signing_seed=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - - secret.sep24.interactive_url.jwt_secret=secret_sep24_interactive_url_jwt_secret - - secret.sep24.more_info_url.jwt_secret=secret_sep24_more_info_url_jwt_secret - # logging - - logging.stellar_level=DEBUG - # events - - events.enabled=true - - events.publisher.type=kafka - - events.publisher.kafka.bootstrap_server=kafka:29092 - # data - - data.type=postgres - - data.server=db:5432 - - data.database=postgres - - data.flyway_enabled=true - # assets - - assets.type=file - - assets.value=/config/assets.yaml - # seps - - sep1.enabled=true - - sep1.toml.type=file - - sep1.toml.value=/config/stellar.toml - - sep10.enabled=true - - sep10.home_domain=localhost:8080 - - sep24.enabled=true - - sep24.interactive_url.base_url=https://stellar.moneygram.com - - sep24.more_info_url.base_url=http://localhost:8080/sep24/transaction/more_info - depends_on: - - db - - kafka - - reference_server - volumes: - - .:/config - ports: - - "8080:8080" # sep-server - - "8082:8082" # Java management server - extra_hosts: - - "host.docker.internal:host-gateway" - - observer: - extends: - file: ../common-services/common-services.yaml - service: observer - - reference_server: - extends: - file: ../common-services/common-services.yaml - service: reference_server - - kafka: - extends: - file: ../common-services/common-services.yaml - service: kafka - - zookeeper: - extends: - file: ../common-services/common-services.yaml - service: zookeeper - - db: - extends: - file: ../common-services/common-services.yaml - service: db - - diff --git a/docker-compose/sep24/stellar.toml b/docker-compose/sep24/stellar.toml deleted file mode 100644 index 411b99df32..0000000000 --- a/docker-compose/sep24/stellar.toml +++ /dev/null @@ -1,17 +0,0 @@ -ACCOUNTS = [] -VERSION = "0.1.0" -NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" -SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" -WEB_AUTH_ENDPOINT = "http://host.docker.internal:8080/auth" -TRANSFER_SERVER_SEP0024 = "http://host.docker.internal:8080/sep24" -KYC_SERVER = "https://host.docker.internal/sep12" -DIRECT_PAYMENT_SERVER = "https://host.docker.internal/sep31" -ANCHOR_QUOTE_SERVER = "https://host.docker.internal/sep38" - -[[CURRENCIES]] -code = "USDC" -issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" -status = "test" -is_asset_anchored = true -anchor_asset_type = "fiat" -desc = "A test USDC issued by Stellar." \ No newline at end of file diff --git a/docker-compose/sep31/anchor-config.yaml b/docker-compose/sep31/anchor-config.yaml deleted file mode 100644 index afa65cb21c..0000000000 --- a/docker-compose/sep31/anchor-config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -version: 1 - - - -assets: - type: json - value: | diff --git a/docker-compose/sep31/assets.json b/docker-compose/sep31/assets.json deleted file mode 100644 index 064e53c336..0000000000 --- a/docker-compose/sep31/assets.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "assets": [ - { - "schema": "stellar", - "code": "USDC", - "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - "significant_decimals": 2, - "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" - ] - }, - "sep31_enabled": true, - "sep38_enabled": true - } - ] -} - diff --git a/docker-compose/sep31/docker-compose.yaml b/docker-compose/sep31/docker-compose.yaml deleted file mode 100644 index 6023d2b8b2..0000000000 --- a/docker-compose/sep31/docker-compose.yaml +++ /dev/null @@ -1,66 +0,0 @@ -version: '2.4' -services: - platform: - image: stellar/anchor-platform:edge - command: --sep-server - environment: - # secrets - - secret.data.username=postgres - - secret.data.password=password - - secret.platform_api.auth_secret=myAnchorToPlatformSecret - - secret.callback_api.auth_secret=myPlatformToAnchorSecret - - secret.sep10.jwt_secret=secret_sep10_secret - - secret.sep10.signing_seed=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X - # logging - - logging.stellar_level=DEBUG - # events - - events.enabled=true - - events.publisher.type=kafka - - events.publisher.kafka.bootstrap_server=kafka:29092 - # data - - data.type=postgres - - data.server=db:5432 - - data.database=postgres - - data.flyway_enabled=true - # assets - - assets.type=file - - assets.value=/config/assets.json - # seps - - sep1.enabled=true - - sep1.toml.type=file - - sep1.toml.value=/config/stellar.toml - - sep10.enabled=true - - sep10.home_domain=localhost:8080 - - sep12.enabled=true - - sep31.enabled=true - - sep38.enabled=true - depends_on: - - db - - kafka - - reference_server - volumes: - - .:/config - ports: - - "8080:8080" # sep-server - - "8082:8082" # Java management server - - reference_server: - extends: - file: ../common-services/common-services.yaml - service: reference_server_a - - kafka: - extends: - file: ../common-services/common-services.yaml - service: kafka_a - - zookeeper: - extends: - file: ../common-services/common-services.yaml - service: zookeeper_a - - db: - extends: - file: ../common-services/common-services.yaml - service: db_a - diff --git a/docker-compose/sep31/stellar.toml b/docker-compose/sep31/stellar.toml deleted file mode 100644 index 72692db196..0000000000 --- a/docker-compose/sep31/stellar.toml +++ /dev/null @@ -1,16 +0,0 @@ -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" -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." \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f3de04c00a..67cfdd550f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,8 @@ commons-codec = "1.15" commons-io = "2.11.0" commons-validator = "1.7" commons-text = "1.10.0" +docker-compose-rule = "1.8.0" +dotenv = "2.3.2" findbugs-jsr305 = "3.0.2" flyway-core = "8.5.13" google-gson = "2.8.9" @@ -49,6 +51,7 @@ aws-java-sdk-s3 = "1.12.342" sqlite-jdbc = "3.34.0" slf4j = "1.7.35" slf4j2 = "2.0.5" +stellar-wallet-sdk = "0.5.0" toml4j = "0.7.2" # Plugin versions @@ -68,6 +71,8 @@ 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" } +docker-compose-rule = {module = "com.palantir.docker.compose:docker-compose-junit-jupiter", version.ref = "docker-compose-rule" } +dotenv = { module = "io.github.cdimascio:dotenv-java", version.ref = "dotenv" } 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" } @@ -122,6 +127,7 @@ 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" } slf4j2-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j2" } +stellar-wallet-sdk = { module = "org.stellar:wallet-sdk", version.ref = "stellar-wallet-sdk"} 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" } diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index 3500fbe096..f68962fd09 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -11,28 +11,34 @@ 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( + 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) + implementation(libs.dotenv) implementation(libs.java.stellar.sdk) implementation(libs.google.gson) implementation(libs.okhttp3) implementation(libs.log4j2.api) implementation(libs.log4j2.core) implementation(libs.log4j2.slf4j) + implementation(libs.docker.compose.rule) // project dependencies implementation(project(":api-schema")) implementation(project(":core")) implementation(project(":platform")) implementation(project(":anchor-reference-server")) + implementation(project(":kotlin-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") + testImplementation(libs.docker.compose.rule) + testImplementation(libs.dotenv) } tasks { bootJar { enabled = false } } diff --git a/integration-tests/docker-compose-configs/.env b/integration-tests/docker-compose-configs/.env deleted file mode 100644 index 53882b63ea..0000000000 --- a/integration-tests/docker-compose-configs/.env +++ /dev/null @@ -1,3 +0,0 @@ -E2E_SECRET= -OMNIBUS_ALLOWLIST_KEYS= -SECRET_SEP10_SIGNING_SEED= \ No newline at end of file diff --git a/integration-tests/docker-compose-configs/Dockerfile b/integration-tests/docker-compose-configs/Dockerfile deleted file mode 100644 index 4babd040be..0000000000 --- a/integration-tests/docker-compose-configs/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -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 - -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/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml deleted file mode 100644 index 0f61277cac..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-allowlist/docker-compose-config.override.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# Configuration Description - enable to omnibusAllowList feature to check if an account is whitelisted for SEP-10 -version: '2.4' -services: - anchor-platform-server: - volumes: - # add mounts for the new config directory - - ./anchor-platform-allowlist:/config_override - environment: - - SEP10_OMNIBUS_ACCOUNT_LIST=${OMNIBUS_ALLOWLIST_KEYS?err} - - SEP10_REQUIRE_KNOWN_OMNIBUS_ACCOUNT=true - 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 deleted file mode 100644 index bcac78754d..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-platform-config.yaml +++ /dev/null @@ -1,344 +0,0 @@ -version: 1 - -logging: - level: INFO - stellar_level: DEBUG - -callback_api: - base_url: http://host.docker.internal:8081 - auth: - type: none - -platform_api: - auth: - type: none - base_url: http://host.docker.internal:8080 - -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 - -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 - - server: host.docker.internal:5440 - - database: postgres - - - ## @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 \ No newline at end of file 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 deleted file mode 100644 index a6b547e7fd..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/anchor-reference-server-config.yaml +++ /dev/null @@ -1,44 +0,0 @@ -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: - enabled: true - 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 deleted file mode 100644 index b24db2d6ca..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/assets-test.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "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 deleted file mode 100644 index d3101b9b2f..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/docker-compose-config.override.yaml +++ /dev/null @@ -1,36 +0,0 @@ -# Configuration Description - default Anchor Platform configuration, also used as a template for new configurations -version: '2.4' -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 deleted file mode 100644 index 90a6969293..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-default-configs/stellar.toml +++ /dev/null @@ -1,26 +0,0 @@ -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-sep24-withdrawal/docker-compose-config.override.yaml b/integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml deleted file mode 100644 index ef8ab1b8b3..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-sep24-withdrawal/docker-compose-config.override.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# Configuration Description - enable to omnibusAllowList feature to check if an account is whitelisted for SEP-10 -version: '2.4' -services: - anchor-platform-server: - volumes: - # add mounts for the new config directory - - ./anchor-platform-allowlist:/config_override - ports: - # override ports, do not append - - "8080:8080" - - anchor-reference-server: - volumes: - # add mounts for the new config directory - - ./anchor-platform-sep24-withdrawal:/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 sep24_withdrawal_flow \ No newline at end of file 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 deleted file mode 100644 index f0b60bf1b7..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-unique-address/anchor-reference-server-config.yaml +++ /dev/null @@ -1,49 +0,0 @@ -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: none - platformToAnchorSecret: myPlatformToAnchorSecret - anchorToPlatformSecret: myAnchorToPlatformSecret - expirationMilliseconds: 30000 - -event: - listenerType: kafka - -kafka.listener: - enabled: true - 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 deleted file mode 100644 index 92eebd5392..0000000000 --- a/integration-tests/docker-compose-configs/anchor-platform-unique-address/docker-compose-config.override.yaml +++ /dev/null @@ -1,48 +0,0 @@ -# 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.4' -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" - environment: - - SEP31_DEPOSIT_INFO_GENERATOR_TYPE=api - - 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 deleted file mode 100644 index b89fa89838..0000000000 --- a/integration-tests/docker-compose-configs/docker-compose.base.yaml +++ /dev/null @@ -1,107 +0,0 @@ -version: '2.4' -services: - anchor-platform-server: - image: anchor-platform - build: - context: ../../ - dockerfile: integration-tests/docker-compose-configs/Dockerfile - 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 - - ./anchor-platform-default-configs:/config_defaults - tmpfs: - - /config - depends_on: - - kafka - - db - 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 - - 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 - - ./anchor-platform-default-configs:/config_defaults - tmpfs: - - /config - depends_on: - - db - - kafka - extra_hosts: - - "host.docker.internal:host-gateway" - - anchor-reference-server: - image: anchor-platform - command: --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: - - 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 - 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 - extra_hosts: - - "host.docker.internal:host-gateway" - - 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} - 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 deleted file mode 100644 index f00d4a4fce..0000000000 --- a/integration-tests/docker-compose-configs/docker-entrypoint.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/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/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformIntegrationTest.kt index 6006ee9908..7d7b88ddfc 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 @@ -1,6 +1,10 @@ package org.stellar.anchor.platform +import com.palantir.docker.compose.DockerComposeExtension +import com.palantir.docker.compose.connection.waiting.HealthChecks import org.junit.jupiter.api.* +import org.springframework.boot.SpringApplication +import org.springframework.context.ConfigurableApplicationContext import org.stellar.anchor.util.Sep1Helper import org.stellar.anchor.util.Sep1Helper.TomlContent @@ -14,45 +18,57 @@ class AnchorPlatformIntegrationTest { const val SEP_SERVER_PORT = 8080 const val OBSERVER_HEALTH_SERVER_PORT = 8083 const val SEP10_JWT_SECRET = "secret" - const val SEP24_INTERACTIVE_URL_JWT_SECRET = "sep24 interactive url secret" - const val SEP24_MORE_INFO_URL_JWT_SECRET = "sep24 more_info url secret" + const val SEP24_INTERACTIVE_URL_JWT_SECRET = "secret_sep24_interactive_url_jwt_secret" + const val SEP24_MORE_INFO_URL_JWT_SECRET = "secret_sep24_more_info_url_jwt_secret" + + private val shouldStartDockerCompose = + System.getenv().getOrDefault("START_DOCKER_COMPOSE", "false").toBoolean() + private val shouldStartServers = + System.getenv().getOrDefault("START_ALL_SERVERS", "true").toBoolean() init { val props = System.getProperties() props.setProperty("REFERENCE_SERVER_CONFIG", "classpath:/anchor-reference-server.yaml") } + val docker: DockerComposeExtension = + DockerComposeExtension.builder() + .saveLogsTo("build/docker-logs/anchor-platform-integration-test") + .file("src/test/resources/test-default//docker-compose.yaml") + .waitingForService("kafka", HealthChecks.toHaveAllPortsOpen()) + .waitingForService("db", HealthChecks.toHaveAllPortsOpen()) + .pullOnStartup(true) + .build() + + val runningServers = mutableListOf() + @BeforeAll @JvmStatic - fun startServers() { - val envMap = - mapOf( - "stellar_anchor_config" to "classpath:integration-test.anchor-config.yaml", - "sep_server.port" to SEP_SERVER_PORT, - "sep_server.context_path" to "/", - "payment_observer.port" to OBSERVER_HEALTH_SERVER_PORT, - "payment_observer.context_path" to "/", - "secret.sep10.signing_seed" to "SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF", - "secret.sep10.jwt_secret" to SEP10_JWT_SECRET, - "secret.sep24.interactive_url.jwt_secret" to SEP24_INTERACTIVE_URL_JWT_SECRET, - "secret.sep24.more_info_url.jwt_secret" to SEP24_MORE_INFO_URL_JWT_SECRET, - "secret.data.username" to "user1", - "secret.data.password" to "password", - "secret.callback_api.auth_secret" to "callback_jwt_secret", - "secret.platform_api.auth_secret" to "platform_jwt_secret", - // The events and kafka should be tested in e2e tests. - "events.enabled" to false - ) - - ServiceRunner.startAnchorReferenceServer() + fun beforeAllTests() { + if (shouldStartDockerCompose) startDocker() + if (shouldStartServers) startServers() + } + + private fun startDocker() { + docker.beforeAll(null) + } + + private fun startServers() { + val envMap = readResourceAsMap("test-default/env") + + envMap["data.type"] = "h2" + envMap["events.enabled"] = "false" + envMap["assets.value"] = getResourceFilePath("test-default/assets.yaml") + envMap["sep1.toml.value"] = getResourceFilePath("test-default/stellar.toml") + ServiceRunner.startKotlinReferenceServer(false) - ServiceRunner.startStellarObserver(envMap) - ServiceRunner.startSepServer(envMap) + runningServers.add(ServiceRunner.startAnchorReferenceServer()) + runningServers.add(ServiceRunner.startStellarObserver(envMap)) + runningServers.add(ServiceRunner.startSepServer(envMap)) toml = - Sep1Helper.parse( - resourceAsString("http://localhost:$SEP_SERVER_PORT/.well-known/stellar.toml") - ) + Sep1Helper.parse( + resourceAsString("http://localhost:$SEP_SERVER_PORT/.well-known/stellar.toml")) Sep10Tests.setup() @@ -66,6 +82,22 @@ class AnchorPlatformIntegrationTest { Sep38Tests.setup() PlatformApiTests.setup() } + + @AfterAll + @JvmStatic + fun afterAllTests() { + if (shouldStartServers) stopServers() + if (shouldStartDockerCompose) stopDocker() + } + + fun stopServers() { + runningServers.forEach { SpringApplication.exit(it) } + org.stellar.reference.stop() + } + + fun stopDocker() { + docker.afterAll(null) + } } @Test 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 7e2d5c273b..3a046a08dd 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 @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource +import org.springframework.boot.SpringApplication import org.springframework.context.ConfigurableApplicationContext import org.stellar.anchor.api.sep.sep38.GetPriceResponse import org.stellar.anchor.api.sep.sep38.RateFee @@ -33,90 +34,85 @@ class ApiKeyAuthIntegrationTest { companion object { private const val ANCHOR_TO_PLATFORM_SECRET = "myAnchorToPlatformSecret" private const val PLATFORM_TO_ANCHOR_SECRET = "myPlatformToAnchorSecret" - private const val PLATFORM_SERVER_PORT = 8888 + private const val PLATFORM_SERVER_PORT = 8080 } private val gson = GsonUtils.getInstance() private val httpClient: OkHttpClient = - OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.MINUTES) - .readTimeout(10, TimeUnit.MINUTES) - .writeTimeout(10, TimeUnit.MINUTES) - .build() + OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.MINUTES) + .readTimeout(10, TimeUnit.MINUTES) + .writeTimeout(10, TimeUnit.MINUTES) + .build() private lateinit var platformServerContext: ConfigurableApplicationContext - private lateinit var mockAnchor: MockWebServer + private lateinit var mockBusinessServer: MockWebServer @BeforeAll fun setup() { // mock Anchor backend - mockAnchor = MockWebServer() - mockAnchor.start() - val mockAnchorUrl = mockAnchor.url("").toString() + mockBusinessServer = MockWebServer() + mockBusinessServer.start() + + val envMap = readResourceAsMap("test-default/env") + envMap["data.type"] = "h2" + envMap["events.enabled"] = "false" + envMap["assets.value"] = getResourceFilePath("test-default/assets.yaml") + envMap["sep1.toml.value"] = getResourceFilePath("test-default/stellar.toml") + + envMap["callback_api.base_url"] = mockBusinessServer.url("").toString() + envMap["platform_api.auth.type"] = "api_key" + envMap["secret.platform_api.auth_secret"] = ANCHOR_TO_PLATFORM_SECRET + envMap["callback_api.auth.type"] = "api_key" + envMap["secret.callback_api.auth_secret"] = PLATFORM_TO_ANCHOR_SECRET // Start platform - platformServerContext = - AnchorPlatformServer.start( - mapOf( - "sep_server.port" to PLATFORM_SERVER_PORT, - "sep_server.context_path" to "/", - "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 - ) - ) + platformServerContext = AnchorPlatformServer().start(envMap) } @AfterAll fun teardown() { + SpringApplication.exit(platformServerContext) + mockBusinessServer.shutdown() clearAllMocks() unmockkAll() } @ParameterizedTest @CsvSource( - value = - [ - "GET,/transactions", - "PATCH,/transactions", - "GET,/transactions/my_id", - "GET,/exchange/quotes", - "GET,/exchange/quotes/id" - ] - ) + value = + [ + "GET,/transactions", + "PATCH,/transactions", + "GET,/transactions/my_id", + "GET,/exchange/quotes", + "GET,/exchange/quotes/id"]) fun test_incomingPlatformAuth_emptyApiKey_authFails(method: String, endpoint: String) { val httpRequest = - Request.Builder() - .url("http://localhost:$PLATFORM_SERVER_PORT$endpoint") - .header("Content-Type", "application/json") - .method(method, getDummyRequestBody(method)) - .build() + Request.Builder() + .url("http://localhost:$PLATFORM_SERVER_PORT$endpoint") + .header("Content-Type", "application/json") + .method(method, getDummyRequestBody(method)) + .build() val response = httpClient.newCall(httpRequest).execute() assertEquals(403, response.code) } @ParameterizedTest @CsvSource( - value = - [ - "GET,/transactions", - "PATCH,/transactions", - "GET,/transactions/my_id", - "GET,/exchange/quotes", - "GET,/exchange/quotes/id" - ] - ) + value = + [ + "GET,/transactions", + "PATCH,/transactions", + "GET,/transactions/my_id", + "GET,/exchange/quotes", + "GET,/exchange/quotes/id"]) fun test_incomingPlatformAuth_apiKey_authPasses(method: String, endpoint: String) { val httpRequest = - Request.Builder() - .url("http://localhost:$PLATFORM_SERVER_PORT$endpoint") - .header("Content-Type", "application/json") - .header("X-Api-Key", ANCHOR_TO_PLATFORM_SECRET) - .method(method, getDummyRequestBody(method)) - .build() + Request.Builder() + .url("http://localhost:$PLATFORM_SERVER_PORT$endpoint") + .header("Content-Type", "application/json") + .header("X-Api-Key", ANCHOR_TO_PLATFORM_SECRET) + .method(method, getDummyRequestBody(method)) + .build() val response = httpClient.newCall(httpRequest).execute() assertNotEquals(403, response.code) } @@ -124,11 +120,11 @@ class ApiKeyAuthIntegrationTest { @Test fun test_ApiAuthIsBeingAddedInPlatformToAnchorRequests() { // check if at least one outgoing call is carrying the auth header. - mockAnchor.enqueue( - MockResponse() - .addHeader("Content-Type", "application/json") - .setBody( - """{ + mockBusinessServer.enqueue( + MockResponse() + .addHeader("Content-Type", "application/json") + .setBody( + """{ "rate": { "price": "1.00", "total_price": "1.00", @@ -140,44 +136,42 @@ class ApiKeyAuthIntegrationTest { } } }""" - .trimMargin() - ) - ) + .trimMargin())) val sep38Service = platformServerContext.getBean(Sep38Service::class.java) val getPriceRequest = - Sep38GetPriceRequest.builder() - .sellAssetName("iso4217:USD") - .sellAmount("100") - .buyAssetName("stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP") - .context(Sep38Context.SEP31) - .build() + Sep38GetPriceRequest.builder() + .sellAssetName("iso4217:USD") + .sellAmount("100") + .buyAssetName("stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP") + .context(Sep38Context.SEP31) + .build() val gotResponse = sep38Service.getPrice(getPriceRequest) val wantResponse = - GetPriceResponse.builder() - .price("1.00") - .totalPrice("1.00") - .sellAmount("100") - .buyAmount("100") - .fee(RateFee("0.00", "iso4217:USD")) - .build() + GetPriceResponse.builder() + .price("1.00") + .totalPrice("1.00") + .sellAmount("100") + .buyAmount("100") + .fee(RateFee("0.00", "iso4217:USD")) + .build() assertEquals(wantResponse, gotResponse) - val request = mockAnchor.takeRequest() + val request = mockBusinessServer.takeRequest() assertEquals("GET", request.method) assertEquals("application/json", request.headers["Content-Type"]) assertEquals(PLATFORM_TO_ANCHOR_SECRET, request.headers["X-Api-Key"]) assertNull(request.headers["Authorization"]) val wantEndpoint = - """/rate + """/rate ?type=indicative_price &context=sep31 &sell_asset=iso4217%3AUSD &sell_amount=100 &buy_asset=stellar%3AUSDC%3AGDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP """ - .replace("\n ", "") + .replace("\n ", "") MatcherAssert.assertThat(request.path, CoreMatchers.endsWith(wantEndpoint)) assertEquals("", request.body.readUtf8()) } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt index f9b9a06790..545bb60f01 100644 --- a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt @@ -10,8 +10,6 @@ import java.util.concurrent.TimeUnit import okhttp3.OkHttpClient import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Order -import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.skyscreamer.jsonassert.JSONAssert import org.stellar.anchor.api.callback.GetFeeRequest @@ -33,52 +31,46 @@ class CallbackApiTests { private const val JWT_EXPIRATION_MILLISECONDS: Long = 10000 private const val FIAT_USD = "iso4217:USD" private const val STELLAR_USD = - "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" + "stellar:USDC:GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" private val httpClient: OkHttpClient = - OkHttpClient.Builder() - .connectTimeout(10, TimeUnit.MINUTES) - .readTimeout(10, TimeUnit.MINUTES) - .writeTimeout(10, TimeUnit.MINUTES) - .build() + OkHttpClient.Builder() + .connectTimeout(10, TimeUnit.MINUTES) + .readTimeout(10, TimeUnit.MINUTES) + .writeTimeout(10, TimeUnit.MINUTES) + .build() private val platformToAnchorJwtService = JwtService(PLATFORM_TO_ANCHOR_SECRET, null, null) private val authHelper = - AuthHelper.forJwtToken( - platformToAnchorJwtService, - JWT_EXPIRATION_MILLISECONDS, - "http://localhost:${AnchorPlatformIntegrationTest.SEP_SERVER_PORT}" - ) + AuthHelper.forJwtToken( + platformToAnchorJwtService, + JWT_EXPIRATION_MILLISECONDS, + "http://localhost:${AnchorPlatformIntegrationTest.SEP_SERVER_PORT}") private val gson: Gson = GsonUtils.getInstance() private val rci = - RestCustomerIntegration( - "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", - httpClient, - authHelper, - gson - ) + RestCustomerIntegration( + "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", + httpClient, + authHelper, + gson) private val rriClient = - RestRateIntegration( - "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", - httpClient, - authHelper, - gson - ) + RestRateIntegration( + "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", + httpClient, + authHelper, + gson) private val rfiClient = - RestFeeIntegration( - "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", - httpClient, - authHelper, - gson - ) + RestFeeIntegration( + "http://localhost:${AnchorPlatformIntegrationTest.REFERENCE_SERVER_PORT}", + httpClient, + authHelper, + gson) fun setup() {} - @Test - @Order(21) fun testCustomerIntegration() { assertThrows { rci.getCustomer(Sep12GetCustomerRequest.builder().id("1").build()) @@ -87,43 +79,39 @@ class CallbackApiTests { fun testRate_indicativePrices() { val result = - rriClient.getRate( - GetRateRequest.builder() - .type(GetRateRequest.Type.INDICATIVE_PRICES) - .sellAsset(FIAT_USD) - .sellAmount("100") - .buyAsset(STELLAR_USD) - .build() - ) + rriClient.getRate( + GetRateRequest.builder() + .type(GetRateRequest.Type.INDICATIVE_PRICES) + .sellAsset(FIAT_USD) + .sellAmount("100") + .buyAsset(STELLAR_USD) + .build()) Assertions.assertNotNull(result) val wantBody = - """{ + """{ "rate":{ "price":"1.02", "sell_amount": "100", "buy_amount": "98.0392" } }""" - .trimMargin() + .trimMargin() JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(result), true) } - @Test - @Order(23) fun testRate_indicativePrice() { val result = - rriClient.getRate( - GetRateRequest.builder() - .type(GetRateRequest.Type.INDICATIVE_PRICE) - .context(Sep38Context.SEP31) - .sellAsset(FIAT_USD) - .sellAmount("100") - .buyAsset(STELLAR_USD) - .build() - ) + rriClient.getRate( + GetRateRequest.builder() + .type(GetRateRequest.Type.INDICATIVE_PRICE) + .context(Sep38Context.SEP31) + .sellAsset(FIAT_USD) + .sellAmount("100") + .buyAsset(STELLAR_USD) + .build()) Assertions.assertNotNull(result) val wantBody = - """{ + """{ "rate":{ "total_price":"1.0303032801", "price":"1.0200002473", @@ -142,25 +130,22 @@ class CallbackApiTests { } } }""" - .trimMargin() + .trimMargin() JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(result), true) } - @Test - @Order(24) fun testRate_firm() { val rate = - rriClient - .getRate( - GetRateRequest.builder() - .type(GetRateRequest.Type.FIRM) - .context(Sep38Context.SEP31) - .sellAsset(FIAT_USD) - .buyAsset(STELLAR_USD) - .buyAmount("100") - .build() - ) - .rate + rriClient + .getRate( + GetRateRequest.builder() + .type(GetRateRequest.Type.FIRM) + .context(Sep38Context.SEP31) + .sellAsset(FIAT_USD) + .buyAsset(STELLAR_USD) + .buyAmount("100") + .build()) + .rate Assertions.assertNotNull(rate) // check if id is a valid UUID @@ -173,12 +158,12 @@ class CallbackApiTests { } val wantExpiresAt = - ZonedDateTime.now(ZoneId.of("UTC")) - .plusDays(1) - .withHour(12) - .withMinute(0) - .withSecond(0) - .withNano(0) + ZonedDateTime.now(ZoneId.of("UTC")) + .plusDays(1) + .withHour(12) + .withMinute(0) + .withSecond(0) + .withNano(0) assertEquals(wantExpiresAt.toInstant(), gotExpiresAt) // check if rate was persisted by getting the rate with ID @@ -187,7 +172,7 @@ class CallbackApiTests { assertEquals("1.02", gotQuote.rate.price) val wantBody = - """{ + """{ "rate":{ "id": "$id", "total_price":"1.03", @@ -208,53 +193,53 @@ class CallbackApiTests { } } }""" - .trimMargin() + .trimMargin() JSONAssert.assertEquals(wantBody, org.stellar.anchor.platform.gson.toJson(gotQuote), true) } - @Test - @Order(25) fun testGetFee() { // Create sender customer val senderCustomerRequest = - GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) + GsonUtils.getInstance().fromJson(testCustomer1Json, Sep12PutCustomerRequest::class.java) val senderCustomer = sep12Client.putCustomer(senderCustomerRequest) // Create receiver customer val receiverCustomerRequest = - GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) + GsonUtils.getInstance().fromJson(testCustomer2Json, Sep12PutCustomerRequest::class.java) val receiverCustomer = sep12Client.putCustomer(receiverCustomerRequest) val result = - rfiClient.getFee( - GetFeeRequest.builder() - .sendAmount("10") - .sendAsset("USDC") - .receiveAsset("USDC") - .senderId(senderCustomer!!.id) - .receiverId(receiverCustomer!!.id) - .clientId("") - .build() - ) + rfiClient.getFee( + GetFeeRequest.builder() + .sendAmount("10") + .sendAsset("USDC") + .receiveAsset("USDC") + .senderId(senderCustomer!!.id) + .receiverId(receiverCustomer!!.id) + .clientId("") + .build()) Assertions.assertNotNull(result) JSONAssert.assertEquals( - org.stellar.anchor.platform.gson.toJson(result), - """{ + org.stellar.anchor.platform.gson.toJson(result), + """{ "fee": { "asset": "USDC", "amount": "0.30" } }""", - true - ) + true) } } } fun callbackApiTestAll() { CallbackApiTests.setup() - println("Performing Callback API tests...") - CallbackApiTests.setup() + + CallbackApiTests.testCustomerIntegration() + CallbackApiTests.testRate_indicativePrices() + CallbackApiTests.testRate_indicativePrice() + CallbackApiTests.testRate_firm() + CallbackApiTests.testGetFee() } diff --git a/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ResourceHelper.kt b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ResourceHelper.kt new file mode 100644 index 0000000000..8647d8ce5d --- /dev/null +++ b/integration-tests/src/test/kotlin/org/stellar/anchor/platform/ResourceHelper.kt @@ -0,0 +1,23 @@ +package org.stellar.anchor.platform + +import io.github.cdimascio.dotenv.internal.DotenvParser +import io.github.cdimascio.dotenv.internal.DotenvReader +import java.io.File +import java.net.URL + +fun readResourceAsMap(resourcePath: String): MutableMap { + val resourceUrl: URL? = {}::class.java.classLoader.getResource(resourcePath) + val resourceFile = File(resourceUrl!!.toURI()) + + return DotenvParser( + DotenvReader(resourceFile.parentFile.absolutePath, resourceFile.name), false, false) + .parse() + .associate { it.key to it.value } + .toMutableMap() +} + +fun getResourceFilePath(resourcePath: String): String { + val resourceUrl: URL? = {}::class.java.classLoader.getResource(resourcePath) + val resourceFile = File(resourceUrl!!.toURI()) + return resourceFile.absolutePath +} diff --git a/integration-tests/src/test/resources/anchor-config-integration.yaml b/integration-tests/src/test/resources/anchor-config-integration.yaml deleted file mode 100644 index c86b310334..0000000000 --- a/integration-tests/src/test/resources/anchor-config-integration.yaml +++ /dev/null @@ -1,309 +0,0 @@ -####################################################################### -## 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/docker-compose/common-services/common-services.yaml b/integration-tests/src/test/resources/common/docker-compose.yaml similarity index 52% rename from docker-compose/common-services/common-services.yaml rename to integration-tests/src/test/resources/common/docker-compose.yaml index 08f941da4a..97e536939f 100644 --- a/docker-compose/common-services/common-services.yaml +++ b/integration-tests/src/test/resources/common/docker-compose.yaml @@ -5,53 +5,22 @@ services: command: --anchor-reference-server environment: - REFERENCE_SERVER_CONFIG_ENV=file:/config/reference-config.yaml - depends_on: - - db - - kafka volumes: - .:/config ports: - - "8081:8081" # reference-server - extra_hosts: - - "host.docker.internal:host-gateway" - - observer: - image: stellar/anchor-platform:edge - build: - context: ../../ - dockerfile: integration-tests/docker-compose-configs/Dockerfile - command: "--stellar-observer" - environment: - - secret.sep10.signing_seed=SAKXNWVTRVR4SJSHZUDB2CLJXEQHRT62MYQWA2HBB7YBOTCFJJJ55BZF - - 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 - # assets - - assets.type=file - - assets.value=/config/assets.yaml - volumes: - # add mounts for the new config directory - - ../sep24/:/config - depends_on: - - db - - kafka - - reference_server + - "8081:8081" extra_hosts: - "host.docker.internal:host-gateway" kafka: platform: linux/x86_64 image: confluentinc/cp-kafka:6.1.9 - depends_on: - - zookeeper ports: - "29092:29092" environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:29092 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:29092 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 @@ -72,10 +41,9 @@ services: db: image: postgres:latest ports: - - "5440:5440" + - "5432:5432" environment: - - POSTGRES_USERa=postgres + - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - - PGPORT=5432 extra_hosts: - - "host.docker.internal:host-gateway" \ No newline at end of file + - "host.docker.internal:host-gateway" diff --git a/docker-compose/common-services/reference-config.yaml b/integration-tests/src/test/resources/common/reference-config.yaml similarity index 91% rename from docker-compose/common-services/reference-config.yaml rename to integration-tests/src/test/resources/common/reference-config.yaml index 68eac450cd..d446b72e13 100644 --- a/docker-compose/common-services/reference-config.yaml +++ b/integration-tests/src/test/resources/common/reference-config.yaml @@ -8,10 +8,10 @@ server: # anchor.settings: version: 0.0.1 - platformApiEndpoint: http://host.docker.internal:8080 + platformApiEndpoint: http://localhost:8080 # The URL where the anchor reference server can be accessed by the platform. - hostUrl: http://host.docker.internal:8081 + hostUrl: http://localhost:8081 # The Stellar wallet to which the customer will send the Stellar assets. distributionWallet: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF @@ -51,7 +51,7 @@ kafka.listener: transactionError: ap_event_transaction_error amqp.listener: - endpoint: host.docker.internal:5672 + endpoint: localhost:5672 # # Spring Data JDBC settings for H2 # @@ -60,4 +60,4 @@ 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 +spring.mvc.converters.preferred-json-mapper: gson \ No newline at end of file diff --git a/integration-tests/src/test/resources/integration-test.anchor-config.yaml b/integration-tests/src/test/resources/integration-test.anchor-config.yaml deleted file mode 100644 index f55e7792a3..0000000000 --- a/integration-tests/src/test/resources/integration-test.anchor-config.yaml +++ /dev/null @@ -1,266 +0,0 @@ -version: 1 - -logging: - level: INFO - stellar_level: DEBUG - -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 = "USDC" - issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" - - [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" - -callback_api: - auth: - type: none - -platform_api: - auth: - type: none - -sep10: - enabled: true - -sep12: - enabled: true - -sep24: - enabled: true - -sep31: - enabled: true - -sep38: - enabled: true - -events: - enabled: true - -assets: - type: json - value: | - { - "assets": [ - { - "schema": "stellar", - "code": "USDC", - "issuer": "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP", - "distribution_account": "GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF", - "significant_decimals": 2, - "deposit" : { - "enabled": true, - "min_amount": 1, - "max_amount": 1000000 - }, - "withdraw": { - "enabled": true, - "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, - "min_amount": 1, - "max_amount": 1000000 - }, - "withdraw": { - "enabled": true, - "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": true, - "sep31_enabled": true, - "sep38_enabled": true - }, - { - "schema": "iso4217", - "code": "USD", - "deposit" : { - "enabled": true, - "min_amount": 0, - "max_amount": 10000 - }, - "withdraw": { - "enabled": true, - "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 deleted file mode 100644 index c2b91186df..0000000000 --- a/integration-tests/src/test/resources/integration.env +++ /dev/null @@ -1,4 +0,0 @@ -JWT_SECRET=secret -SEP10_SIGNING_SEED=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X -PLATFORM_TO_ANCHOR_SECRET=myPlatformToAnchorSecret -ANCHOR_TO_PLATFORM_SECRET=myAnchorToPlatformSecret diff --git a/integration-tests/src/test/resources/test-default/assets.yaml b/integration-tests/src/test/resources/test-default/assets.yaml new file mode 100644 index 0000000000..1ee60d4339 --- /dev/null +++ b/integration-tests/src/test/resources/test-default/assets.yaml @@ -0,0 +1,142 @@ +assets: + - schema: stellar + code: USDC + issuer: GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP + distribution_account: GBN4NNCDGJO4XW4KQU3CBIESUJWFVBUZPOKUZHT7W7WRB7CWOA7BXVQF + significant_decimals: 2 + deposit: + enabled: true + min_amount: 1 + max_amount: 1000000 + withdraw: + enabled: true + 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 + min_amount: 1 + max_amount: 1000000 + withdraw: + enabled: true + 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: true + sep31_enabled: true + sep38_enabled: true + - schema: iso4217 + code: USD + deposit: + enabled: true + min_amount: 0 + max_amount: 10000 + withdraw: + enabled: true + 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/test-default/docker-compose.yaml b/integration-tests/src/test/resources/test-default/docker-compose.yaml new file mode 100644 index 0000000000..4077e241c3 --- /dev/null +++ b/integration-tests/src/test/resources/test-default/docker-compose.yaml @@ -0,0 +1,19 @@ +version: '2.4' +services: + kafka: + extends: + file: ../common/docker-compose.yaml + service: kafka + depends_on: + - zookeeper + + zookeeper: + extends: + file: ../common/docker-compose.yaml + service: zookeeper + + db: + extends: + file: ../common/docker-compose.yaml + service: db + diff --git a/integration-tests/src/test/resources/test-default/env b/integration-tests/src/test/resources/test-default/env new file mode 100644 index 0000000000..d34a347994 --- /dev/null +++ b/integration-tests/src/test/resources/test-default/env @@ -0,0 +1,36 @@ +# secrets +secret.data.username=postgres +secret.data.password=password +secret.platform_api.auth_secret=myAnchorToPlatformSecret +secret.callback_api.auth_secret=myPlatformToAnchorSecret +secret.sep10.jwt_secret=secret_sep10_secret +secret.sep10.signing_seed=SAX3AH622R2XT6DXWWSRIDCMMUCCMATBZ5U6XKJWDO7M2EJUBFC3AW5X +secret.sep24.interactive_url.jwt_secret=secret_sep24_interactive_url_jwt_secret +secret.sep24.more_info_url.jwt_secret=secret_sep24_more_info_url_jwt_secret +# logging +logging.stellar_level=DEBUG +# events +events.enabled=true +events.publisher.type=kafka +events.publisher.kafka.bootstrap_server=localhost:29092 +# data +data.type=postgres +data.server=localhost:5432 +data.database=postgres +data.flyway_enabled=true +# assets +assets.type=file +assets.value=/config/assets.yaml +# seps +sep1.enabled=true +sep1.toml.type=file +sep1.toml.value=/config/stellar.toml +sep10.enabled=true +sep10.home_domain=localhost:8080 +sep12.enabled=true +sep31.enabled=true +sep38.enabled=true + +sep24.enabled=true +sep24.interactive_url.base_url=https://stellar.moneygram.com +sep24.more_info_url.base_url=http://localhost:8080/sep24/transaction/more_info diff --git a/integration-tests/src/main/resources/sep1/test-stellar.toml b/integration-tests/src/test/resources/test-default/stellar.toml similarity index 68% rename from integration-tests/src/main/resources/sep1/test-stellar.toml rename to integration-tests/src/test/resources/test-default/stellar.toml index 6b99515738..8436b4f015 100644 --- a/integration-tests/src/main/resources/sep1/test-stellar.toml +++ b/integration-tests/src/test/resources/test-default/stellar.toml @@ -1,4 +1,4 @@ -ACCOUNTS = [ "GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE" ] +ACCOUNTS = ["GCSGSR6KQQ5BP2FXVPWRL6SWPUSFWLVONLIBJZUKTVQB5FYJFVL6XOXE"] VERSION = "0.1.0" SIGNING_KEY = "GCHLHDBOKG2JWMJQBTLSL5XG6NO7ESXI2TAQKZXCXWXB5WI2X6W233PR" NETWORK_PASSPHRASE = "Test SDF Network ; September 2015" @@ -10,12 +10,8 @@ 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." +code = "USDC" +issuer = "GDQOE23CFSUMSVQK4Y5JHPPYK73VYCNHZHA7ENKCV37P6SUEO6XQBKPP" [DOCUMENTATION] ORG_NAME = "Stellar Development Foundation" diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/RefenreceServerStart.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/RefenreceServerStart.kt index 0ff6995692..e7c05cb42a 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/RefenreceServerStart.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/RefenreceServerStart.kt @@ -2,4 +2,6 @@ package org.stellar.reference // Used in ServiceRunner @JvmOverloads // Annotation required to call from Java with optional argument -fun start(waitServer: Boolean = false) = main(arrayOf(waitServer.toString())) +fun start(waitServer: Boolean = false) = startServer(waitServer) +fun stop() = stopServer() + diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/ReferenceServer.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/ReferenceServer.kt index 794b36058f..0689bc0427 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/ReferenceServer.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/ReferenceServer.kt @@ -22,36 +22,53 @@ import org.stellar.reference.sep24.Sep24Helper import org.stellar.reference.sep24.WithdrawalService val log = KotlinLogging.logger {} +lateinit var referenceKotlinSever: NettyApplicationEngine fun main(args: Array) { + startServer(args.getOrNull(0)?.toBooleanStrictOrNull() ?: true) +} + +fun startServer(wait: Boolean) { log.info { "Starting Kotlin reference server" } - // Support for configurable config location file. - val locationCfg = - ConfigLoaderBuilder.default() - .addPropertySource(PropertySource.environment()) - .build() - .loadConfig() + // read config + val cfg = readCfg() - val builder = ConfigLoaderBuilder.default() + // start server + referenceKotlinSever = + embeddedServer(Netty, port = cfg.sep24.port) { + install(ContentNegotiation) { json() } + configureRouting(cfg) + install(CORS) { + anyHost() + allowHeader(HttpHeaders.Authorization) + allowHeader(HttpHeaders.ContentType) + } + } + .start(wait) +} - // If value is set, read config from the file. - locationCfg.fold({}, { builder.addFileSource(it.ktReferenceServerConfig) }) +fun readCfg(): Config { + // Load location config + val locationCfg = + ConfigLoaderBuilder.default() + .addPropertySource(PropertySource.environment()) + .build() + .loadConfig() - builder.addResourceSource("/default-config.yaml").addPropertySource(PropertySource.environment()) + val cfgBuilder = ConfigLoaderBuilder.default() + // Add environment variables as a property source. + cfgBuilder.addPropertySource(PropertySource.environment()) + // Add config file as a property source if valid + locationCfg.fold({}, { cfgBuilder.addFileSource(it.ktReferenceServerConfig) }) + // Add default config file as a property source. + cfgBuilder.addResourceSource("/default-config.yaml") - val cfg = builder.build().loadConfigOrThrow() + return cfgBuilder.build().loadConfigOrThrow() +} - embeddedServer(Netty, port = cfg.sep24.port) { - install(ContentNegotiation) { json() } - configureRouting(cfg) - install(CORS) { - anyHost() - allowHeader(HttpHeaders.Authorization) - allowHeader(HttpHeaders.ContentType) - } - } - .start(args.getOrNull(0)?.toBooleanStrictOrNull() ?: true) +fun stopServer() { + if (::referenceKotlinSever.isInitialized) (referenceKotlinSever).stop(1000, 1000) } fun Application.configureRouting(cfg: Config) { diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Route.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Sep24Route.kt similarity index 100% rename from kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Route.kt rename to kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Sep24Route.kt diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/TestRoute.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Sep24TestRoute.kt similarity index 100% rename from kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/TestRoute.kt rename to kotlin-reference-server/src/main/kotlin/org/stellar/reference/plugins/Sep24TestRoute.kt diff --git a/platform/src/main/java/org/stellar/anchor/platform/AbstractPlatformServer.java b/platform/src/main/java/org/stellar/anchor/platform/AbstractPlatformServer.java index 6271fcd357..3638a6dec4 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AbstractPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AbstractPlatformServer.java @@ -4,12 +4,7 @@ import org.stellar.anchor.platform.configurator.ConfigEnvironment; abstract class AbstractPlatformServer { - static void buildEnvironment(Map environment) { - if (environment != null) { - for (String name : environment.keySet()) { - System.setProperty(name, String.valueOf(environment.get(name))); - } - ConfigEnvironment.rebuild(); - } + void buildEnvironment(Map envMap) { + ConfigEnvironment.rebuild(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 fcad18b674..47e15a3350 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/AnchorPlatformServer.java @@ -26,7 +26,8 @@ }) @EnableConfigurationProperties public class AnchorPlatformServer extends AbstractPlatformServer implements WebMvcConfigurer { - public static ConfigurableApplicationContext start(Map environment) { + private ConfigurableApplicationContext ctx; + public ConfigurableApplicationContext start(Map environment) { buildEnvironment(environment); SpringApplicationBuilder builder = @@ -35,6 +36,13 @@ public static ConfigurableApplicationContext start(Map environme springApplication.addInitializers(SecretManager.getInstance()); springApplication.addInitializers(SepConfigManager.getInstance()); - return springApplication.run(); + return ctx = springApplication.run(); + } + + public void stop() { + if (ctx != null) { + SpringApplication.exit(ctx); + ctx = null; + } } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/EventProcessingServer.java b/platform/src/main/java/org/stellar/anchor/platform/EventProcessingServer.java index 73bfdf3ce3..50dd945487 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/EventProcessingServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/EventProcessingServer.java @@ -20,13 +20,13 @@ @EntityScan(basePackages = {"org.stellar.anchor.platform.data"}) @ComponentScan( basePackages = { - "org.stellar.anchor.platform.controller.eventprocessor", "org.stellar.anchor.platform.component.eventprocessor", "org.stellar.anchor.platform.component.share" }) @EnableConfigurationProperties public class EventProcessingServer extends AbstractPlatformServer implements WebMvcConfigurer { - public static ConfigurableApplicationContext start(Map environment) { + private ConfigurableApplicationContext ctx; + public ConfigurableApplicationContext start(Map environment) { buildEnvironment(environment); SpringApplicationBuilder builder = @@ -35,6 +35,12 @@ public static ConfigurableApplicationContext start(Map environme springApplication.addInitializers(SecretManager.getInstance()); springApplication.addInitializers(EventProcessorConfigManager.getInstance()); - return springApplication.run(); + return ctx = springApplication.run(); } + + public void stop() { + if (ctx != null) { + SpringApplication.exit(ctx); + } + } } diff --git a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java index 9bf816abe6..a476562c93 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java +++ b/platform/src/main/java/org/stellar/anchor/platform/StellarObservingServer.java @@ -26,7 +26,8 @@ }) @EnableConfigurationProperties public class StellarObservingServer extends AbstractPlatformServer implements WebMvcConfigurer { - public static ConfigurableApplicationContext start(Map environment) { + private ConfigurableApplicationContext ctx; + public ConfigurableApplicationContext start(Map environment) { buildEnvironment(environment); SpringApplicationBuilder builder = @@ -35,6 +36,11 @@ public static ConfigurableApplicationContext start(Map environme springApplication.addInitializers(SecretManager.getInstance()); springApplication.addInitializers(ObserverConfigManager.getInstance()); - return springApplication.run(); + return ctx = springApplication.run(); + } + public void stop() { + if (ctx != null) { + SpringApplication.exit(ctx); + } } } 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 2b92b22dee..8a269f1319 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 @@ -14,9 +14,15 @@ public class ConfigEnvironment { rebuild(); } - public static void rebuild() { + public static void rebuild(Map extra) { env = new HashMap<>(); + if (extra != null) { + for (Map.Entry entry : extra.entrySet()) { + env.put(toPosixForm(entry.getKey()), entry.getValue()); + } + } + // Read all env variables and convert everything to POSIX form for (Map.Entry entry : System.getenv().entrySet()) { env.put(toPosixForm(entry.getKey()), entry.getValue()); @@ -28,6 +34,11 @@ public static void rebuild() { } } + + public static void rebuild() { + rebuild(null); + } + /** * This class seems redundant but it is necessary for JUnit test for creating configuration with * environment variables. diff --git a/platform/src/main/resources/assets-prod.json b/platform/src/main/resources/assets-prod.json deleted file mode 100644 index a7d7cff36a..0000000000 --- a/platform/src/main/resources/assets-prod.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "assets": [ - { - "schema": "stellar", - "code": "USDC", - "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", - "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 - }, - "sep38": { - "exchangeable_assets": [ - "iso4217:USD" - ] - }, - "sep31_enabled": false, - "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:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" - ], - "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 WIRE sent directly to your bank account." - } - ] - }, - "sep31_enabled": false, - "sep38_enabled": true - } - ] -} diff --git a/platform/src/main/resources/assets-test.json b/platform/src/main/resources/assets-test.json deleted file mode 100644 index b24db2d6ca..0000000000 --- a/platform/src/main/resources/assets-test.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "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/config/anchor-config-default-values.yaml b/platform/src/main/resources/config/anchor-config-default-values.yaml index 9f3775f668..73014ca9f7 100644 --- a/platform/src/main/resources/config/anchor-config-default-values.yaml +++ b/platform/src/main/resources/config/anchor-config-default-values.yaml @@ -453,7 +453,7 @@ events: ######################### ## Assets Configuration ######################### -## Assets are empty by default. Please see `integration-test.anchor-config.yaml` for reference. +## Assets are empty by default. ## Accepts file reference (eg. 'file:assets.yaml') or in-line definition. assets: ## @param: type diff --git a/platform/src/main/resources/example.env b/platform/src/main/resources/example.env deleted file mode 100644 index 58d6f5c8be..0000000000 --- a/platform/src/main/resources/example.env +++ /dev/null @@ -1,27 +0,0 @@ -#################################################################################################### -## 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 -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 - -# REQUIRED - JWT secrets used to communicate between Anchor and Platform. -SECRET_CALLBACK_API_AUTH_SECRET=myPlatformToAnchorSecret -SECRET_PLATFORM_API_AUTH_SECRET=myAnchorToPlatformSecret - -# OPTIONAL - used for storage definition (database) -SECRET_DATA_USERNAME=admin -SECRET_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/main/resources/sep1/stellar-wks.toml b/platform/src/main/resources/sep1/stellar-wks.toml deleted file mode 100644 index 667995401e..0000000000 --- a/platform/src/main/resources/sep1/stellar-wks.toml +++ /dev/null @@ -1,26 +0,0 @@ -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." \ 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 387238525a..98b3571c6f 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 @@ -58,19 +58,20 @@ public static void main(String[] args) { } } - static ConfigurableApplicationContext startSepServer(Map env) { - return AnchorPlatformServer.start(env); + public static ConfigurableApplicationContext startSepServer(Map env) { + return new AnchorPlatformServer().start(env); } - static ConfigurableApplicationContext startStellarObserver(Map env) { - return StellarObservingServer.start(env); + public static ConfigurableApplicationContext startStellarObserver(Map env) { + return new StellarObservingServer().start(env); } - static ConfigurableApplicationContext startEventProcessor(Map env) { - return EventProcessingServer.start(env); + + public static ConfigurableApplicationContext startEventProcessor(Map env) { + return new EventProcessingServer().start(env); } - static void startAnchorReferenceServer() { + public static ConfigurableApplicationContext startAnchorReferenceServer() { String strPort = System.getProperty("ANCHOR_REFERENCE_SERVER_PORT"); int port = DEFAULT_ANCHOR_REFERENCE_SERVER_PORT; @@ -79,13 +80,15 @@ static void startAnchorReferenceServer() { port = Integer.parseInt(strPort); } - AnchorReferenceServer.start(port, "/"); + return AnchorReferenceServer.start(port, "/"); } - static void startKotlinReferenceServer(boolean wait) { + + public static void startKotlinReferenceServer(boolean wait) { RefenreceServerStartKt.start(wait); } + static void printUsage(Options options) { HelpFormatter helper = new HelpFormatter(); helper.setOptionComparator(null); From c7497fc495f8e8dc0aa5d352398a8a0692b69bd8 Mon Sep 17 00:00:00 2001 From: Gleb Date: Mon, 27 Mar 2023 10:22:36 -0700 Subject: [PATCH 0128/1439] Update git hook (#803) --- scripts/pre-commit.sh | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh index 68355274bc..6bafeffe82 100755 --- a/scripts/pre-commit.sh +++ b/scripts/pre-commit.sh @@ -1,15 +1,7 @@ #!/bin/sh -# find staged files -stagedFiles=$(git diff --staged --name-only) +R='\033[0;31m' +CLEAN='\033[0;0m' -# run spotlessApply -echo "Running spotlessApply. Formatting code..." -./gradlew spotlessApply - -# add staged files -for file in $stagedFiles; do - if test -f "$file"; then - git add $file - fi -done +echo "Running spotlessCheck." +./gradlew spotlessCheck || (./gradlew spotlessApply && (echo -e "${R}Code was not formatted. Please add staged files and try again${CLEAN}" && exit 1)) From dd79a71a79eadce85ced10a2b715c63a7b68f270 Mon Sep 17 00:00:00 2001 From: Jamie Li Date: Tue, 28 Mar 2023 14:58:07 -0700 Subject: [PATCH 0129/1439] [ANCHOR-217] Deposit end-2-end tests in Kotlin (#799) 1. Build the test framework that runs on profiles that can be defined to test different AP configurations. 2. Add docker-compose to GH actions. 3. Output test stdout in Gradle build so that we may see what went wrong. 4. Integrate Kotlin Wallet SDK 5. Implement deposit e2e test in Kotlin. --- .../workflows/sub_gradle_test_and_build.yml | 40 +- ...PlatformEnd2EndTest - scripts only.run.xml | 33 ++ .run/v2.0 - AnchorPlatformEnd2EndTest.run.xml | 31 ++ ...formIntegrationTest - scripts only.run.xml | 33 ++ ...0 - AnchorPlatformIntegrationTest.run.xml} | 4 +- ...Run Development Docker and Servers.run.xml | 23 + Dockerfile | 3 +- build.gradle.kts | 24 +- .../org/stellar/anchor/util/SepHelperTest.kt | 6 +- .../Communication/Callbacks API.yml | 1 + integration-tests/build.gradle.kts | 3 + .../platform/AbstractIntegrationTest.kt | 73 +++ .../platform/AnchorPlatformEnd2EndTest.kt | 29 + .../platform/AnchorPlatformIntegrationTest.kt | 110 +--- .../platform/ApiKeyAuthIntegrationTest.kt | 112 ++-- .../anchor/platform/CallbackApiTests.kt | 245 --------- .../stellar/anchor/platform/DevEnvRunner.kt | 26 + .../anchor/platform/PlatformApiTests.kt | 40 -- .../stellar/anchor/platform/ResourceHelper.kt | 23 +- .../org/stellar/anchor/platform/Sep10Tests.kt | 68 --- .../org/stellar/anchor/platform/Sep12Tests.kt | 111 ---- .../org/stellar/anchor/platform/Sep31Tests.kt | 508 ----------------- .../org/stellar/anchor/platform/Sep38Tests.kt | 97 ---- .../stellar/anchor/platform/SepTestSuite.kt | 90 --- .../anchor/platform/StellarObserverTests.kt | 68 --- .../stellar/anchor/platform/TestAccounts.kt | 10 + .../anchor/platform/TestProfileRunner.kt | 83 +++ .../anchor/platform/test/CallbackApiTests.kt | 246 +++++++++ .../anchor/platform/test/PlatformApiTests.kt | 28 + .../{ => test}/ReferenceServerTests.kt | 3 +- .../anchor/platform/test/Sep10Tests.kt | 62 +++ .../anchor/platform/test/Sep12Tests.kt | 106 ++++ .../anchor/platform/test/Sep24End2EndTests.kt | 85 +++ .../anchor/platform/{ => test}/Sep24Tests.kt | 229 ++++---- .../anchor/platform/test/Sep31Tests.kt | 513 ++++++++++++++++++ .../anchor/platform/test/Sep38Tests.kt | 92 ++++ .../platform/test/StellarObserverTests.kt | 64 +++ .../test/resources/common/docker-compose.yaml | 33 +- .../{test-default => config}/assets.yaml | 0 .../java-reference-server-config.yaml} | 0 .../{test-default => config}/stellar.toml | 0 ...-compose.yaml => docker-compose-test.yaml} | 9 +- .../src/test/resources/docker-compose.yaml | 51 ++ .../resources/profiles/default/config.env | 35 ++ .../test/resources/profiles/default/test.env | 11 + .../env => profiles/sep24/config.env} | 3 +- .../test/resources/profiles/sep24/test.env | 11 + .../stellar/reference/RefenreceServerStart.kt | 5 +- .../org/stellar/reference/ReferenceServer.kt | 42 +- .../src/main/resources/docker-config.yaml | 2 - .../anchor/platform/AnchorPlatformServer.java | 1 + .../platform/EventProcessingServer.java | 9 +- .../platform/StellarObservingServer.java | 2 + .../configurator/ConfigEnvironment.java | 1 - .../anchor/platform/ServiceRunner.java | 9 +- 55 files changed, 1969 insertions(+), 1577 deletions(-) create mode 100644 .run/v2.0 - AnchorPlatformEnd2EndTest - scripts only.run.xml create mode 100644 .run/v2.0 - AnchorPlatformEnd2EndTest.run.xml create mode 100644 .run/v2.0 - AnchorPlatformIntegrationTest - scripts only.run.xml rename .run/{v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml => v2.0 - AnchorPlatformIntegrationTest.run.xml} (90%) create mode 100644 .run/v2.0 - Run Development Docker and Servers.run.xml create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/AbstractIntegrationTest.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/AnchorPlatformEnd2EndTest.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/CallbackApiTests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/DevEnvRunner.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/PlatformApiTests.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep10Tests.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep12Tests.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep31Tests.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/Sep38Tests.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/SepTestSuite.kt delete mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/StellarObserverTests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/TestAccounts.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/TestProfileRunner.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/CallbackApiTests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/PlatformApiTests.kt rename integration-tests/src/test/kotlin/org/stellar/anchor/platform/{ => test}/ReferenceServerTests.kt (89%) create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/Sep10Tests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/Sep12Tests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/Sep24End2EndTests.kt rename integration-tests/src/test/kotlin/org/stellar/anchor/platform/{ => test}/Sep24Tests.kt (51%) create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/Sep31Tests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/Sep38Tests.kt create mode 100644 integration-tests/src/test/kotlin/org/stellar/anchor/platform/test/StellarObserverTests.kt rename integration-tests/src/test/resources/{test-default => config}/assets.yaml (100%) rename integration-tests/src/test/resources/{common/reference-config.yaml => config/java-reference-server-config.yaml} (100%) rename integration-tests/src/test/resources/{test-default => config}/stellar.toml (100%) rename integration-tests/src/test/resources/{test-default/docker-compose.yaml => docker-compose-test.yaml} (52%) create mode 100644 integration-tests/src/test/resources/docker-compose.yaml create mode 100644 integration-tests/src/test/resources/profiles/default/config.env create mode 100644 integration-tests/src/test/resources/profiles/default/test.env rename integration-tests/src/test/resources/{test-default/env => profiles/sep24/config.env} (93%) create mode 100644 integration-tests/src/test/resources/profiles/sep24/test.env delete mode 100644 kotlin-reference-server/src/main/resources/docker-config.yaml diff --git a/.github/workflows/sub_gradle_test_and_build.yml b/.github/workflows/sub_gradle_test_and_build.yml index 30a65c067f..0b9b4f6744 100644 --- a/.github/workflows/sub_gradle_test_and_build.yml +++ b/.github/workflows/sub_gradle_test_and_build.yml @@ -10,30 +10,38 @@ jobs: name: Gradle Test and Build runs-on: ubuntu-22.04 steps: + # Start the docker containers - uses: actions/checkout@v3 + - name: Build the stack + env: + TEST_PROFILE_NAME: default + run: docker-compose -f integration-tests/src/test/resources/docker-compose-test.yaml up -d --build + + # Set up JDK 11 - name: Set up JDK 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: java-version: '11' distribution: 'adopt' - - name: Spotless Check - run: ./gradlew spotlessCheck || echo "❌ Your code is not properly formatted. You can run './gradlew spotlessApply' to format it. 👀" - - - name: Test and Build - run: ./gradlew build + # Check the docker containers + - name: Check running containers + run: docker ps - - name: Print Test Results - if: failure() + # This is to add to DNS entries to access the services started by docker-compose. + - name: Add 'db' and 'kafka' to /etc/hosts run: | - 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 "\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 "\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 + sudo echo "127.0.0.1 db" | sudo tee -a /etc/hosts + sudo echo "127.0.0.1 kafka" | sudo tee -a /etc/hosts + sudo nslookup db + sudo nslookup kafka + + # Gradle test is now printing the test messages to GitHub Actions log. It is not necessary to print the reports. + - name: Test and Build + env: + TEST_PROFILE_NAME: default + run_docker: false + run: ./gradlew integration-tests:test --stacktrace analyze: name: CodeQL Analysis diff --git a/.run/v2.0 - AnchorPlatformEnd2EndTest - scripts only.run.xml b/.run/v2.0 - AnchorPlatformEnd2EndTest - scripts only.run.xml new file mode 100644 index 0000000000..6376b13b31 --- /dev/null +++ b/.run/v2.0 - AnchorPlatformEnd2EndTest - scripts only.run.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.run/v2.0 - AnchorPlatformEnd2EndTest.run.xml b/.run/v2.0 - AnchorPlatformEnd2EndTest.run.xml new file mode 100644 index 0000000000..24beb39805 --- /dev/null +++ b/.run/v2.0 - AnchorPlatformEnd2EndTest.run.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.run/v2.0 - AnchorPlatformIntegrationTest - scripts only.run.xml b/.run/v2.0 - AnchorPlatformIntegrationTest - scripts only.run.xml new file mode 100644 index 0000000000..ff4105f642 --- /dev/null +++ b/.run/v2.0 - AnchorPlatformIntegrationTest - scripts only.run.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml b/.run/v2.0 - AnchorPlatformIntegrationTest.run.xml similarity index 90% rename from .run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml rename to .run/v2.0 - AnchorPlatformIntegrationTest.run.xml index 4e20e538e4..b24fab0a8c 100644 --- a/.run/v2.0 - AnchorPlatformIntegrationTest - with docker-compose.run.xml +++ b/.run/v2.0 - AnchorPlatformIntegrationTest.run.xml @@ -1,5 +1,5 @@ - + @@ -22,7 +22,7 @@ + +