diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d69fc65004..e58fbbd2fe 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation(libs.apache.commons.lang3) implementation(libs.bcastle) + implementation(libs.bcutil) implementation(libs.commons.beanutils) implementation(libs.commons.codec) implementation(libs.commons.io) diff --git a/core/src/main/java/org/stellar/anchor/auth/Sep10Jwt.java b/core/src/main/java/org/stellar/anchor/auth/Sep10Jwt.java index c990686c37..83b38d3e1d 100644 --- a/core/src/main/java/org/stellar/anchor/auth/Sep10Jwt.java +++ b/core/src/main/java/org/stellar/anchor/auth/Sep10Jwt.java @@ -9,9 +9,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.stellar.sdk.AccountConverter; -import org.stellar.sdk.KeyPair; -import org.stellar.sdk.xdr.MuxedAccount; +import org.stellar.sdk.MuxedAccount; @Getter @Setter @@ -111,13 +109,11 @@ void updateAccountAndMemo() { this.accountMemo = null; try { - MuxedAccount maybeMuxedAccount = AccountConverter.enableMuxed().encode(sub); - MuxedAccount.MuxedAccountMed25519 muxedAccount = maybeMuxedAccount.getMed25519(); - if (muxedAccount != null) { + MuxedAccount maybeMuxedAccount = new MuxedAccount(sub); + if (maybeMuxedAccount.getMuxedId() != null) { this.muxedAccount = sub; - byte[] pubKeyBytes = muxedAccount.getEd25519().getUint256(); - this.account = KeyPair.fromPublicKey(pubKeyBytes).getAccountId(); - this.muxedAccountId = muxedAccount.getId().getUint64().getNumber().longValue(); + this.account = maybeMuxedAccount.getAccountId(); + this.muxedAccountId = maybeMuxedAccount.getMuxedId().longValue(); } } catch (Exception ignored) { } diff --git a/core/src/main/java/org/stellar/anchor/horizon/Horizon.java b/core/src/main/java/org/stellar/anchor/horizon/Horizon.java index d9727f2005..d648264363 100644 --- a/core/src/main/java/org/stellar/anchor/horizon/Horizon.java +++ b/core/src/main/java/org/stellar/anchor/horizon/Horizon.java @@ -2,16 +2,18 @@ import static org.stellar.anchor.api.asset.AssetInfo.NATIVE_ASSET_CODE; -import java.io.IOException; -import java.util.Arrays; import java.util.List; import lombok.Getter; import org.stellar.anchor.config.AppConfig; import org.stellar.anchor.util.AssetHelper; import org.stellar.sdk.AssetTypeCreditAlphaNum; import org.stellar.sdk.Server; +import org.stellar.sdk.TrustLineAsset; +import org.stellar.sdk.exception.NetworkException; +import org.stellar.sdk.requests.PaymentsRequestBuilder; import org.stellar.sdk.responses.AccountResponse; import org.stellar.sdk.responses.operations.OperationResponse; +import org.stellar.sdk.xdr.AssetType; /** The horizon-server. */ public class Horizon { @@ -30,7 +32,7 @@ public Server getServer() { return this.horizonServer; } - public boolean isTrustlineConfigured(String account, String asset) throws IOException { + public boolean isTrustlineConfigured(String account, String asset) throws NetworkException { String assetCode = AssetHelper.getAssetCode(asset); if (NATIVE_ASSET_CODE.equals(assetCode)) { return true; @@ -38,13 +40,15 @@ public boolean isTrustlineConfigured(String account, String asset) throws IOExce String assetIssuer = AssetHelper.getAssetIssuer(asset); AccountResponse accountResponse = getServer().accounts().account(account); - return Arrays.stream(accountResponse.getBalances()) + return accountResponse.getBalances().stream() .anyMatch( balance -> { - if (balance.getAssetType().equals("credit_alphanum4") - || balance.getAssetType().equals("credit_alphanum12")) { + TrustLineAsset trustLineAsset = balance.getTrustLineAsset(); + if (trustLineAsset.getAssetType() == AssetType.ASSET_TYPE_CREDIT_ALPHANUM4 + || trustLineAsset.getAssetType() == AssetType.ASSET_TYPE_CREDIT_ALPHANUM12) { AssetTypeCreditAlphaNum creditAsset = - (AssetTypeCreditAlphaNum) balance.getAsset().get(); + (AssetTypeCreditAlphaNum) trustLineAsset.getAsset(); + assert creditAsset != null; return creditAsset.getCode().equals(assetCode) && creditAsset.getIssuer().equals(assetIssuer); } @@ -52,7 +56,14 @@ public boolean isTrustlineConfigured(String account, String asset) throws IOExce }); } - public List getStellarTxnOperations(String stellarTxnId) throws IOException { + /** + * Get payment operations for a transaction. + * + * @param stellarTxnId the transaction id + * @return the operations + * @throws NetworkException request failed, see {@link PaymentsRequestBuilder#execute()} + */ + public List getStellarTxnOperations(String stellarTxnId) { return getServer() .payments() .includeTransactions(true) diff --git a/core/src/main/java/org/stellar/anchor/sep10/Sep10Helper.java b/core/src/main/java/org/stellar/anchor/sep10/Sep10Helper.java index e4eb73d494..62c792e255 100644 --- a/core/src/main/java/org/stellar/anchor/sep10/Sep10Helper.java +++ b/core/src/main/java/org/stellar/anchor/sep10/Sep10Helper.java @@ -10,7 +10,6 @@ import org.stellar.anchor.api.exception.InvalidConfigException; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.util.Sep1Helper; -import org.stellar.sdk.FormatException; import org.stellar.sdk.KeyPair; public class Sep10Helper { @@ -39,7 +38,7 @@ public static String fetchSigningKeyFromClientDomain(String clientDomain, boolea debugF("Validating client_domain signing key: {}", clientSigningKey); KeyPair.fromAccountId(clientSigningKey); return clientSigningKey; - } catch (IllegalArgumentException | FormatException e) { + } catch (IllegalArgumentException e) { infoF("SIGNING_KEY {} is not a valid Stellar account Id.", clientSigningKey); throw new SepException( String.format("SIGNING_KEY %s is not a valid Stellar account Id.", clientSigningKey)); 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 338e9da673..f8f1c8a4e7 100644 --- a/core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java +++ b/core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java @@ -11,7 +11,6 @@ import io.jsonwebtoken.Jws; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Metrics; -import java.io.IOException; import java.time.Instant; import java.util.Arrays; import java.util.HashSet; @@ -36,7 +35,10 @@ import org.stellar.anchor.util.Log; import org.stellar.sdk.*; import org.stellar.sdk.Sep10Challenge.ChallengeTransaction; -import org.stellar.sdk.requests.ErrorResponse; +import org.stellar.sdk.exception.InvalidSep10ChallengeException; +import org.stellar.sdk.exception.NetworkException; +import org.stellar.sdk.operations.ManageDataOperation; +import org.stellar.sdk.operations.Operation; import org.stellar.sdk.responses.AccountResponse; /** The Sep-10 protocol service. */ @@ -114,7 +116,7 @@ public ChallengeResponse createChallenge( } public ValidationResponse validateChallenge(ValidationRequest request) - throws IOException, InvalidSep10ChallengeException, SepValidationException { + throws SepValidationException { info("Validating SEP-10 challenge."); ChallengeTransaction challenge = parseChallenge(request); @@ -176,8 +178,7 @@ public ChallengeResponse createChallengeResponse( } } - Transaction newChallenge(ChallengeRequest request, String clientSigningKey, Memo memo) - throws InvalidSep10ChallengeException { + Transaction newChallenge(ChallengeRequest request, String clientSigningKey, Memo memo) { KeyPair signer = KeyPair.fromSecretSeed(secretConfig.getSep10SigningSeed()); long now = Instant.now().getEpochSecond(); @@ -380,7 +381,7 @@ void validateAccountFormat(ChallengeRequest request) throws SepException { // Validate account try { KeyPair.fromAccountId(request.getAccount()); - } catch (Exception ex) { + } catch (IllegalArgumentException ex) { infoF("client wallet account ({}) is invalid", request.getAccount()); throw new SepValidationException("Invalid account."); } @@ -388,7 +389,7 @@ void validateAccountFormat(ChallengeRequest request) throws SepException { void validateChallengeRequest( ValidationRequest request, AccountResponse account, String clientDomain) - throws InvalidSep10ChallengeException, IOException, SepValidationException { + throws SepValidationException { // fetch the signers from the transaction Set signers = fetchSigners(account); // the signatures must be greater than the medium threshold of the account. @@ -415,7 +416,7 @@ void validateChallengeRequest( Set fetchSigners(AccountResponse account) { // Find the signers of the client account. - return Arrays.stream(account.getSigners()) + return account.getSigners().stream() .filter(as -> as.getType().equals("ed25519_public_key")) .map(as -> new Sep10Challenge.Signer(as.getKey(), as.getWeight())) .collect(Collectors.toSet()); @@ -423,7 +424,7 @@ Set fetchSigners(AccountResponse account) { AccountResponse fetchAccount( ValidationRequest request, ChallengeTransaction challenge, String clientDomain) - throws InvalidSep10ChallengeException, IOException, SepValidationException { + throws SepValidationException { // Check the client's account AccountResponse account; try { @@ -432,7 +433,7 @@ AccountResponse fetchAccount( traceF("challenge account: {}", account); sep10ChallengeValidatedCounter.increment(); return account; - } catch (ErrorResponse | IOException ex) { + } catch (NetworkException ex) { infoF("Account {} does not exist in the Stellar Network"); // account not found // The client account does not exist, using the client's master key to verify. @@ -494,8 +495,7 @@ String fetchClientDomain(ChallengeTransaction challenge) { return clientDomain; } - ChallengeTransaction parseChallenge(ValidationRequest request) - throws IOException, InvalidSep10ChallengeException, SepValidationException { + ChallengeTransaction parseChallenge(ValidationRequest request) throws SepValidationException { if (request == null || request.getTransaction() == null) { throw new SepValidationException("{transaction} is required."); @@ -554,13 +554,17 @@ private String authUrl() { * @param network The network to connect to for verifying and retrieving. * @return The extracted home domain from the Manage Data operation within the SEP-10 challenge * transaction. - * @throws IOException If read XDR string fails, the exception will be thrown. * @throws SepValidationException If the transaction is not a valid SEP-10 challenge transaction. */ String extractHomeDomainFromChallengeXdr(String challengeXdr, Network network) - throws IOException, SepValidationException { - AbstractTransaction parsed = - Transaction.fromEnvelopeXdr(AccountConverter.enableMuxed(), challengeXdr, network); + throws SepValidationException { + AbstractTransaction parsed; + try { + parsed = Transaction.fromEnvelopeXdr(challengeXdr, network); + } catch (IllegalArgumentException e) { + throw new SepValidationException("Invalid challenge transaction."); + } + if (!(parsed instanceof Transaction)) { throw new SepValidationException("Transaction cannot be a fee bump transaction"); } @@ -603,8 +607,7 @@ public synchronized Transaction newChallenge( TimeBounds timebounds, String clientDomain, String clientSigningKey, - Memo memo) - throws InvalidSep10ChallengeException { + Memo memo) { return Sep10Challenge.newChallenge( signer, network, @@ -622,8 +625,7 @@ public synchronized ChallengeTransaction readChallengeTransaction( String serverAccountId, Network network, String domainName, - String webAuthDomain) - throws InvalidSep10ChallengeException, IOException { + String webAuthDomain) { return Sep10Challenge.readChallengeTransaction( challengeXdr, serverAccountId, network, domainName, webAuthDomain); } @@ -634,8 +636,7 @@ public synchronized void verifyChallengeTransactionSigners( Network network, String domainName, String webAuthDomain, - Set signers) - throws InvalidSep10ChallengeException, IOException { + Set signers) { Sep10Challenge.verifyChallengeTransactionSigners( challengeXdr, serverAccountId, network, domainName, webAuthDomain, signers); } @@ -647,8 +648,7 @@ public synchronized void verifyChallengeTransactionThreshold( String domainName, String webAuthDomain, int threshold, - Set signers) - throws InvalidSep10ChallengeException, IOException { + Set signers) { Sep10Challenge.verifyChallengeTransactionThreshold( challengeXdr, serverAccountId, network, domainName, webAuthDomain, threshold, signers); } 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 ba7d3d8268..ee91aefc66 100644 --- a/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java +++ b/core/src/main/java/org/stellar/anchor/sep24/Sep24Service.java @@ -188,7 +188,7 @@ public InteractiveTransactionResponse withdraw( try { debugF("checking if withdraw source account:{} is valid", sourceAccount); KeyPair.fromAccountId(sourceAccount); - } catch (Exception ex) { + } catch (IllegalArgumentException ex) { infoF("invalid account format: {}", sourceAccount); throw new SepValidationException(String.format("invalid account: %s", sourceAccount), ex); } @@ -382,7 +382,7 @@ public InteractiveTransactionResponse deposit(Sep10Jwt token, Map val public void validateAccount(String account) throws AnchorException { try { KeyPair.fromAccountId(account); - } catch (RuntimeException ex) { + } catch (IllegalArgumentException ex) { throw new SepValidationException(String.format("invalid account %s", account)); } } diff --git a/core/src/main/java/org/stellar/anchor/util/AssetHelper.java b/core/src/main/java/org/stellar/anchor/util/AssetHelper.java index 2aee8029d0..b61ec66ca4 100644 --- a/core/src/main/java/org/stellar/anchor/util/AssetHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/AssetHelper.java @@ -37,7 +37,7 @@ public static boolean isNonNativeAsset(String assetCode, String assetIssuer) { try { KeyPair.fromAccountId(assetIssuer); return true; - } catch (Exception ex) { + } catch (IllegalArgumentException ex) { return false; } } 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 dc80d3e528..c181cbcfc1 100644 --- a/core/src/main/java/org/stellar/anchor/util/SepHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/SepHelper.java @@ -11,9 +11,9 @@ 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.AccountConverter; import org.stellar.sdk.KeyPair; -import org.stellar.sdk.xdr.*; +import org.stellar.sdk.MuxedAccount; +import org.stellar.sdk.xdr.MemoType; public class SepHelper { /** @@ -59,15 +59,11 @@ public static String memoTypeString(MemoType memoType) { public static String getAccountMemo(String strAccount) throws InvalidStellarAccountException { String[] tokens = strAccount.split(":"); switch (tokens.length) { + // TODO: Should we add a catch here to throw an InvalidStellarAccountException exception in + // case of invalid address? 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]); + new MuxedAccount(tokens[0]); return null; case 2: KeyPair.fromAccountId(tokens[0]); 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 56ac1c302c..f23648b48b 100644 --- a/core/src/test/kotlin/org/stellar/anchor/horizon/HorizonTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/horizon/HorizonTest.kt @@ -2,7 +2,6 @@ package org.stellar.anchor.horizon import io.mockk.every import io.mockk.mockk -import java.util.* import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -10,8 +9,10 @@ import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.stellar.anchor.config.AppConfig +import org.stellar.sdk.Asset import org.stellar.sdk.AssetTypeCreditAlphaNum import org.stellar.sdk.Server +import org.stellar.sdk.TrustLineAsset import org.stellar.sdk.requests.AccountsRequestBuilder import org.stellar.sdk.responses.AccountResponse import org.stellar.sdk.responses.AccountResponse.Balance @@ -84,20 +85,21 @@ internal class HorizonTest { every { appConfig.stellarNetworkPassphrase } returns TEST_HORIZON_PASSPHRASE every { server.accounts() } returns accountsRequestBuilder every { accountsRequestBuilder.account(account) } returns accountResponse - every { balance1.getAssetType() } returns "credit_alphanum4" - every { balance1.getAsset() } returns Optional.of(asset1) - every { balance2.getAssetType() } returns "credit_alphanum12" - every { balance2.getAsset() } returns Optional.of(asset2) - every { asset1.getCode() } returns "USDC" - every { asset1.getIssuer() } returns "issuerAccount1" - every { asset2.getCode() } returns "USDC" - every { asset2.getIssuer() } returns "issuerAccount2" - every { accountResponse.getBalances() } returns arrayOf(balance1, balance2) + + every { asset1.code } returns "USDC" + every { asset1.issuer } returns "issuerAccount1" + every { asset2.code } returns "USDC" + every { asset2.issuer } returns "issuerAccount2" + + every { balance1.trustLineAsset } returns + TrustLineAsset(Asset.createNonNativeAsset(asset1.code, asset1.issuer)) + every { balance2.trustLineAsset } returns + TrustLineAsset(Asset.createNonNativeAsset(asset2.code, asset2.issuer)) + every { accountResponse.balances } returns listOf(balance1, balance2) val horizon = mockk() every { horizon.server } returns server every { horizon.isTrustlineConfigured(account, asset) } answers { callOriginal() } - assertTrue(horizon.isTrustlineConfigured(account, asset)) } @@ -112,24 +114,25 @@ internal class HorizonTest { val balance1: Balance = mockk() val balance2: Balance = mockk() val balance3: Balance = mockk() - val asset1: AssetTypeCreditAlphaNum = mockk() + // val asset1: AssetTypeNative = mockk() val asset2: AssetTypeCreditAlphaNum = mockk() val asset3: AssetTypeCreditAlphaNum = mockk() every { server.accounts() } returns accountsRequestBuilder every { accountsRequestBuilder.account(account) } returns accountResponse - every { balance1.getAssetType() } returns "credit_alphanum8" - every { balance1.getAsset() } returns Optional.of(asset1) - every { balance2.getAssetType() } returns "credit_alphanum4" - every { balance2.getAsset() } returns Optional.of(asset2) - every { balance3.getAssetType() } returns "credit_alphanum4" - every { balance3.getAsset() } returns Optional.of(asset3) - every { asset1.getCode() } returns "USDC" - every { asset1.getIssuer() } returns "issuerAccount1" - every { asset2.getCode() } returns "SRT" - every { asset2.getIssuer() } returns "issuerAccount1" - every { asset3.getCode() } returns "USDC" - every { asset3.getIssuer() } returns "issuerAccount2" - every { accountResponse.getBalances() } returns arrayOf(balance1, balance2, balance3) + + // asset 1 is native asset + every { asset2.code } returns "SRT" + every { asset2.issuer } returns "issuerAccount1" + every { asset3.code } returns "USDC" + every { asset3.issuer } returns "issuerAccount2" + + every { balance1.trustLineAsset } returns TrustLineAsset(Asset.createNativeAsset()) + every { balance2.trustLineAsset } returns + TrustLineAsset(Asset.createNonNativeAsset(asset2.code, asset2.issuer)) + every { balance3.trustLineAsset } returns + TrustLineAsset(Asset.createNonNativeAsset(asset3.code, asset3.issuer)) + + every { accountResponse.balances } returns listOf(balance1, balance2, balance3) every { appConfig.horizonUrl } returns TEST_HORIZON_URI every { appConfig.stellarNetworkPassphrase } returns TEST_HORIZON_PASSPHRASE @@ -137,7 +140,6 @@ internal class HorizonTest { val horizon = mockk() every { horizon.server } returns server every { horizon.isTrustlineConfigured(account, asset) } answers { callOriginal() } - assertFalse(horizon.isTrustlineConfigured(account, asset)) } } 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 b71afed85a..ebef7b94de 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep10/Sep10ServiceTest.kt @@ -5,11 +5,8 @@ package org.stellar.anchor.sep10 import com.google.common.io.BaseEncoding import com.google.gson.annotations.SerializedName import io.jsonwebtoken.Jwts -import io.mockk.MockKAnnotations -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.spyk -import io.mockk.verify import java.io.IOException import java.security.SecureRandom import java.time.Instant @@ -64,7 +61,10 @@ import org.stellar.anchor.util.GsonUtils import org.stellar.anchor.util.NetUtil import org.stellar.sdk.* import org.stellar.sdk.Network.* -import org.stellar.sdk.requests.ErrorResponse +import org.stellar.sdk.exception.BadRequestException +import org.stellar.sdk.exception.InvalidSep10ChallengeException +import org.stellar.sdk.operations.ManageDataOperation +import org.stellar.sdk.operations.SetOptionsOperation import org.stellar.sdk.responses.AccountResponse import org.stellar.walletsdk.auth.DefaultAuthHeaderSigner import org.stellar.walletsdk.auth.createAuthSignToken @@ -202,20 +202,26 @@ internal class Sep10ServiceTest { val sourceAccount = Account(serverKP.accountId, -1L) val op1DomainNameMandatory = - ManageDataOperation.Builder("$serverHomeDomain auth", encodedNonce) - .setSourceAccount(clientKP.accountId) + ManageDataOperation.builder() + .name("$serverHomeDomain auth") + .value(encodedNonce) + .sourceAccount(clientKP.accountId) .build() val op2WebAuthDomainMandatory = - ManageDataOperation.Builder("web_auth_domain", serverWebAuthDomain.toByteArray()) - .setSourceAccount(serverKP.accountId) + ManageDataOperation.builder() + .name("web_auth_domain") + .value(serverWebAuthDomain.toByteArray()) + .sourceAccount(serverKP.accountId) .build() val op3clientDomainOptional = - ManageDataOperation.Builder("client_domain", "lobstr.co".toByteArray()) - .setSourceAccount(clientDomainKP.accountId) + ManageDataOperation.builder() + .name("client_domain") + .value("lobstr.co".toByteArray()) + .sourceAccount(clientDomainKP.accountId) .build() val transaction = - TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, TESTNET) + TransactionBuilder(sourceAccount, TESTNET) .addPreconditions( TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() ) @@ -279,20 +285,26 @@ internal class Sep10ServiceTest { val sourceAccount = Account(serverKP.accountId, -1L) val op1DomainNameMandatory = - ManageDataOperation.Builder("$serverHomeDomain auth", encodedNonce) - .setSourceAccount(clientKP.accountId) + ManageDataOperation.builder() + .name("$serverHomeDomain auth") + .value(encodedNonce) + .sourceAccount(clientKP.accountId) .build() val op2WebAuthDomainMandatory = - ManageDataOperation.Builder("web_auth_domain", serverWebAuthDomain.toByteArray()) - .setSourceAccount(serverKP.accountId) + ManageDataOperation.builder() + .name("web_auth_domain") + .value(serverWebAuthDomain.toByteArray()) + .sourceAccount(serverKP.accountId) .build() val op3clientDomainOptional = - ManageDataOperation.Builder("client_domain", "lobstr.co".toByteArray()) - .setSourceAccount(clientDomainKP.accountId) + ManageDataOperation.builder() + .name("client_domain") + .value("lobstr.co".toByteArray()) + .sourceAccount(clientDomainKP.accountId) .build() val transaction = - TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, TESTNET) + TransactionBuilder(sourceAccount, TESTNET) .addPreconditions( TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() ) @@ -360,12 +372,16 @@ internal class Sep10ServiceTest { val vr = ValidationRequest() vr.transaction = createTestChallenge("", TEST_HOME_DOMAIN, false) - val accountResponse = spyk(AccountResponse(clientKeyPair.accountId, 1)) - val signers = - arrayOf(TestSigner(clientKeyPair.accountId, "ed25519_public_key", 1, "").toSigner()) + val mockSigners = + listOf(TestSigner(clientKeyPair.accountId, "ed25519_public_key", 1, "").toSigner()) + val accountResponse = + mockk { + every { accountId } returns clientKeyPair.accountId + every { sequenceNumber } returns 1 + every { signers } returns mockSigners + every { thresholds.medThreshold } returns 1 + } - every { accountResponse.signers } returns signers - every { accountResponse.thresholds.medThreshold } returns 1 every { horizon.server.accounts().account(ofType(String::class)) } returns accountResponse val response = sep10Service.validateChallenge(vr) @@ -376,15 +392,20 @@ internal class Sep10ServiceTest { @Test @LockAndMockStatic([Sep10Challenge::class]) fun `test validate challenge with client domain`() { - val accountResponse = spyk(AccountResponse(clientKeyPair.accountId, 1)) - val signers = - arrayOf( + val mockSigners = + listOf( TestSigner(clientKeyPair.accountId, "ed25519_public_key", 1, "").toSigner(), TestSigner(clientDomainKeyPair.accountId, "ed25519_public_key", 1, "").toSigner() ) - every { accountResponse.signers } returns signers - every { accountResponse.thresholds.medThreshold } returns 1 + val accountResponse = + mockk { + every { accountId } returns clientKeyPair.accountId + every { sequenceNumber } returns 1 + every { signers } returns mockSigners + every { thresholds.medThreshold } returns 1 + } + every { horizon.server.accounts().account(ofType(String::class)) } returns accountResponse val vr = ValidationRequest() @@ -404,7 +425,7 @@ internal class Sep10ServiceTest { // exists every { horizon.server.accounts().account(ofType(String::class)) } answers { - throw ErrorResponse(0, "mock error") + throw BadRequestException(400, "mock error", null, null) } vr.transaction = createTestChallenge(TEST_CLIENT_DOMAIN, TEST_HOME_DOMAIN, false) @@ -418,7 +439,7 @@ internal class Sep10ServiceTest { every { horizon.server.accounts().account(ofType(String::class)) } answers { - throw ErrorResponse(0, "mock error") + throw BadRequestException(400, "mock error", null, null) } sep10Service.validateChallenge(vr) @@ -709,20 +730,26 @@ internal class Sep10ServiceTest { val sourceAccount = Account(serverKP.accountId, -1L) val op1DomainNameMandatory = - ManageDataOperation.Builder("$serverHomeDomain auth", encodedNonce) - .setSourceAccount(clientAddress) + ManageDataOperation.builder() + .name("$serverHomeDomain auth") + .value(encodedNonce) + .sourceAccount(clientAddress) .build() val op2WebAuthDomainMandatory = - ManageDataOperation.Builder("web_auth_domain", serverWebAuthDomain.toByteArray()) - .setSourceAccount(serverKP.accountId) + ManageDataOperation.builder() + .name("web_auth_domain") + .value(serverWebAuthDomain.toByteArray()) + .sourceAccount(serverKP.accountId) .build() val op3clientDomainOptional = - ManageDataOperation.Builder("client_domain", "lobstr.co".toByteArray()) - .setSourceAccount(clientDomainKP.accountId) + ManageDataOperation.builder() + .name("client_domain") + .value("lobstr.co".toByteArray()) + .sourceAccount(clientDomainKP.accountId) .build() val transaction = - TransactionBuilder(AccountConverter.enableMuxed(), sourceAccount, network) + TransactionBuilder(sourceAccount, network) .addPreconditions( TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() ) @@ -757,18 +784,19 @@ internal class Sep10ServiceTest { val clientAccount = horizon.server.accounts().account(clientMasterKP.accountId) val multisigTx = - TransactionBuilder(AccountConverter.enableMuxed(), clientAccount, network) + TransactionBuilder(clientAccount, network) .addPreconditions( TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(900)).build() ) .setBaseFee(300) .addOperation( - SetOptionsOperation.Builder() - .setLowThreshold(20) - .setMediumThreshold(20) - .setHighThreshold(20) - .setSigner(Signer.ed25519PublicKey(clientSecondaryKP), 10) - .setMasterKeyWeight(10) + SetOptionsOperation.builder() + .lowThreshold(20) + .mediumThreshold(20) + .highThreshold(20) + .signer(Signer.ed25519PublicKey(clientSecondaryKP)) + .signerWeight(10) + .masterKeyWeight(10) .build() ) .build() diff --git a/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/AbstractIntegrationTests.kt b/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/AbstractIntegrationTests.kt index ca70c81646..1e8e4bc2ea 100644 --- a/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/AbstractIntegrationTests.kt +++ b/essential-tests/src/testFixtures/kotlin/org/stellar/anchor/platform/AbstractIntegrationTests.kt @@ -69,7 +69,7 @@ abstract class AbstractIntegrationTests(val config: TestConfig) { .records for (payment in payments) { if (payment is PaymentOperationResponse) { - if (payment.transaction.get().memo.toString() == TEST_PAYMENT_MEMO) { + if (payment.transaction.memo.toString() == TEST_PAYMENT_MEMO) { println("Found test payment") // initialize the test payment value pairs for injection testPaymentValues = diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ac2a0accd2..eb6ea6644e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,8 @@ assertj = "3.26.0" aws-iam-auth = "2.1.1" aws-rds = "1.12.759" aws-sqs = "1.12.757" -bcastle = "1.77" +bcastle = "1.79" +bcutil = "1.79" commons-beanutils = "1.9.4" commons-cli = "1.8.0" commons-codec = "1.17.0" @@ -27,7 +28,7 @@ jakarta-annotation-api = "3.0.0" jakarta-transaction-api = "2.0.1" jakarta-validation-api = "3.1.0" jakarta-xml-bind-api = "4.0.2" -java-stellar-sdk = "0.44.1" +java-stellar-sdk = "1.0.0" jjwt = "0.12.5" jsonassert = "1.5.0" junit = "5.10.3" @@ -52,7 +53,7 @@ spotbugs-annotations = "4.8.6" spring-context = "6.1.11" spring-kafka = "3.2.1" spring-retry = "2.0.6" -stellar-wallet-sdk = "1.7.1" +stellar-wallet-sdk = "2.0.0" # Plugin versions spotless = "6.25.0" @@ -67,6 +68,7 @@ aws-iam-auth = { module = "software.amazon.msk:aws-msk-iam-auth", version.ref = 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" } bcastle = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bcastle" } +bcutil = { module = "org.bouncycastle:bcutil-jdk18on", version.ref = "bcutil" } commons-beanutils = { module = "commons-beanutils:commons-beanutils", version.ref = "commons-beanutils" } commons-cli = { module = "commons-cli:commons-cli", version.ref = "commons-cli" } commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" } @@ -90,7 +92,7 @@ jakarta-annotation-api = { module = "jakarta.annotation:jakarta.annotation-api", jakarta-transaction-api = { module = "jakarta.transaction:jakarta.transaction-api", version.ref = "jakarta-transaction-api" } jakarta-validation-api = { module = "jakarta.validation:jakarta.validation-api", version.ref = "jakarta-validation-api" } jakarta-xml-bind-api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version.ref = "jakarta-xml-bind-api" } -java-stellar-sdk = { module = "com.github.stellar:java-stellar-sdk", version.ref = "java-stellar-sdk" } +java-stellar-sdk = { module = "network.lightsail:stellar-sdk", version.ref = "java-stellar-sdk" } jjwt = { module = "io.jsonwebtoken:jjwt", version.ref = "jjwt" } jsonassert = { module = "org.skyscreamer:jsonassert", version.ref = "jsonassert" } junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/event/processor/Sep6EventProcessor.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/event/processor/Sep6EventProcessor.kt index adbe0fd7b9..864c36a7aa 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/event/processor/Sep6EventProcessor.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/event/processor/Sep6EventProcessor.kt @@ -1,5 +1,6 @@ package org.stellar.reference.event.processor +import java.math.BigDecimal import java.time.Instant import java.util.* import kotlinx.coroutines.runBlocking @@ -17,6 +18,9 @@ import org.stellar.reference.log import org.stellar.reference.service.SepHelper import org.stellar.reference.transactionWithRetry import org.stellar.sdk.* +import org.stellar.sdk.exception.BadRequestException +import org.stellar.sdk.operations.PaymentOperation +import org.stellar.sdk.responses.TransactionResponse class Sep6EventProcessor( private val config: Config, @@ -438,14 +442,22 @@ class Sep6EventProcessor( .addPreconditions( TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(60)).build() ) - .addOperation(PaymentOperation.Builder(destination, asset, amount).build()) + .addOperation( + PaymentOperation.builder() + .destination(destination) + .asset(asset) + .amount(BigDecimal(amount)) + .build() + ) .build() transaction.sign(KeyPair.fromSecretSeed(config.appSettings.secret)) - val txnResponse = server.submitTransaction(transaction) - if (!txnResponse.isSuccess) { - throw RuntimeException("Error submitting transaction: ${txnResponse.extras.resultCodes}") + val txnResponse: TransactionResponse + try { + txnResponse = server.submitTransaction(transaction) + } catch (e: BadRequestException) { + throw RuntimeException("Error submitting transaction: ${e.problem?.extras?.resultCodes}") } - + assert(txnResponse.successful) return txnResponse.hash } diff --git a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/service/SepHelper.kt b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/service/SepHelper.kt index 6dae87760e..0f26f6622e 100644 --- a/kotlin-reference-server/src/main/kotlin/org/stellar/reference/service/SepHelper.kt +++ b/kotlin-reference-server/src/main/kotlin/org/stellar/reference/service/SepHelper.kt @@ -20,6 +20,9 @@ import org.stellar.anchor.util.GsonUtils import org.stellar.reference.data.* import org.stellar.reference.data.Transaction import org.stellar.sdk.* +import org.stellar.sdk.exception.BadRequestException +import org.stellar.sdk.operations.PaymentOperation +import org.stellar.sdk.responses.TransactionResponse class SepHelper(private val cfg: Config) { private val log = KotlinLogging.logger {} @@ -97,7 +100,11 @@ class SepHelper(private val cfg: Config) { TransactionPreconditions.builder().timeBounds(TimeBounds.expiresAfter(60)).build() ) .addOperation( - PaymentOperation.Builder(destinationAddress, asset, amount.toPlainString()).build() + PaymentOperation.builder() + .destination(destinationAddress) + .asset(asset) + .amount(amount) + .build() ) if (memo != null && memoType != null) { @@ -115,14 +122,15 @@ class SepHelper(private val cfg: Config) { transaction.sign(cfg.sep24.keyPair) - val resp = server.submitTransaction(transaction) - - if (!resp.isSuccess) { + val resp: TransactionResponse + try { + resp = server.submitTransaction(transaction) + } catch (e: BadRequestException) { throw Exception( - "Failed to submit transaction with code: ${resp?.extras?.resultCodes?.transactionResultCode}" + "Failed to submit transaction with code: ${e.problem?.extras?.resultCodes?.transactionResultCode}" ) } - + assert(resp.successful) return resp.hash } diff --git a/platform/build.gradle.kts b/platform/build.gradle.kts index c0e9adea2f..6980ad127a 100644 --- a/platform/build.gradle.kts +++ b/platform/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(libs.aws.rds) implementation(libs.aws.sqs) implementation(libs.bcastle) + implementation(libs.bcutil) implementation(libs.commons.beanutils) implementation(libs.commons.cli) implementation(libs.commons.io) 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 9a08dba94b..a950ec8d02 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 @@ -21,6 +21,7 @@ import org.stellar.anchor.util.KeyUtil; import org.stellar.anchor.util.NetUtil; import org.stellar.sdk.*; +import org.stellar.sdk.operations.ManageDataOperation; @Data public class PropertySep10Config implements Sep10Config, Validator { @@ -112,7 +113,7 @@ void validateConfig(Errors errors) { if (isNotEmpty(webAuthDomain)) { try { - new ManageDataOperation.Builder(webAuthDomain, new byte[64]).build(); + ManageDataOperation.builder().name(webAuthDomain).value(new byte[64]).build(); } catch (IllegalArgumentException iaex) { errors.rejectValue( "webAuthDomain", @@ -190,7 +191,10 @@ void validateCustodialAccounts(Errors errors) { private void validateDomain(Errors errors, String domain) { try { - new ManageDataOperation.Builder(String.format("%s %s", domain, "auth"), new byte[64]).build(); + ManageDataOperation.builder() + .name(String.format("%s %s", domain, "auth")) + .value(new byte[64]) + .build(); } catch (IllegalArgumentException iaex) { errors.rejectValue( "homeDomain", diff --git a/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep10Controller.java b/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep10Controller.java index 041060dc10..a8639b647f 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep10Controller.java +++ b/platform/src/main/java/org/stellar/anchor/platform/controller/sep/Sep10Controller.java @@ -2,7 +2,6 @@ import static org.stellar.anchor.util.Log.*; -import java.io.IOException; import java.net.URISyntaxException; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -17,7 +16,7 @@ import org.stellar.anchor.api.sep.sep10.ValidationResponse; import org.stellar.anchor.platform.condition.ConditionalOnAllSepsEnabled; import org.stellar.anchor.sep10.Sep10Service; -import org.stellar.sdk.InvalidSep10ChallengeException; +import org.stellar.sdk.exception.InvalidSep10ChallengeException; @RestController @CrossOrigin(origins = "*") @@ -65,8 +64,7 @@ public ChallengeResponse createChallenge( produces = {MediaType.APPLICATION_JSON_VALUE}, method = {RequestMethod.POST}) public ValidationResponse validateChallenge( - @RequestParam(name = "transaction") String transaction) - throws InvalidSep10ChallengeException, IOException, SepValidationException { + @RequestParam(name = "transaction") String transaction) throws SepValidationException { debugF("POST /auth transaction={}", transaction); return validateChallenge(ValidationRequest.of(transaction)); } @@ -79,7 +77,7 @@ public ValidationResponse validateChallenge( method = {RequestMethod.POST}) public ValidationResponse validateChallenge( @RequestBody(required = false) ValidationRequest validationRequest) - throws InvalidSep10ChallengeException, IOException, SepValidationException { + throws SepValidationException { debug("POST /auth details:", validationRequest); return sep10Service.validateChallenge(validationRequest); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/custody/CustodyPayment.java b/platform/src/main/java/org/stellar/anchor/platform/custody/CustodyPayment.java index 0254f29c67..78bf20ddfc 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/custody/CustodyPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/custody/CustodyPayment.java @@ -67,7 +67,7 @@ public static CustodyPayment fromPayment( id = paymentOperation.get().getId().toString(); to = paymentOperation.get().getTo(); amount = paymentOperation.get().getAmount(); - assetType = paymentOperation.get().getAsset().getType(); + assetType = paymentOperation.get().getAssetType(); assetName = paymentOperation.get().getAsset().toString(); if (paymentOperation.get().getAsset() instanceof AssetTypeCreditAlphaNum) { @@ -76,23 +76,22 @@ public static CustodyPayment fromPayment( assetCode = issuedAsset.getCode(); assetIssuer = issuedAsset.getIssuer(); } else if (paymentOperation.get().getAsset() instanceof AssetTypeNative) { - AssetTypeNative issuedAsset = (AssetTypeNative) paymentOperation.get().getAsset(); - assetCode = issuedAsset.getType(); + assetCode = paymentOperation.get().getAssetType(); // "native" } String sourceAccount = paymentOperation.get().getSourceAccount() != null ? paymentOperation.get().getSourceAccount() - : paymentOperation.get().getTransaction().get().getSourceAccount(); + : paymentOperation.get().getTransaction().getSourceAccount(); from = paymentOperation.get().getFrom() != null ? paymentOperation.get().getFrom() : sourceAccount; - Memo memo = paymentOperation.get().getTransaction().get().getMemo(); + Memo memo = paymentOperation.get().getTransaction().getMemo(); transactionMemo = MemoHelper.memoAsString(memo); transactionMemoType = MemoHelper.memoTypeAsString(memo); - transactionEnvelope = paymentOperation.get().getTransaction().get().getEnvelopeXdr(); + transactionEnvelope = paymentOperation.get().getTransaction().getEnvelopeXdr(); } return CustodyPayment.builder() @@ -140,7 +139,7 @@ public static CustodyPayment fromPathPayment( id = pathPaymentOperation.get().getId().toString(); to = pathPaymentOperation.get().getTo(); amount = pathPaymentOperation.get().getAmount(); - assetType = pathPaymentOperation.get().getAsset().getType(); + assetType = pathPaymentOperation.get().getAssetType(); assetName = pathPaymentOperation.get().getAsset().toString(); if (pathPaymentOperation.get().getAsset() instanceof AssetTypeCreditAlphaNum) { @@ -149,23 +148,22 @@ public static CustodyPayment fromPathPayment( assetCode = issuedAsset.getCode(); assetIssuer = issuedAsset.getIssuer(); } else if (pathPaymentOperation.get().getAsset() instanceof AssetTypeNative) { - AssetTypeNative issuedAsset = (AssetTypeNative) pathPaymentOperation.get().getAsset(); - assetCode = issuedAsset.getType(); + assetCode = pathPaymentOperation.get().getAssetType(); // "native" } String sourceAccount = pathPaymentOperation.get().getSourceAccount() != null ? pathPaymentOperation.get().getSourceAccount() - : pathPaymentOperation.get().getTransaction().get().getSourceAccount(); + : pathPaymentOperation.get().getTransaction().getSourceAccount(); from = pathPaymentOperation.get().getFrom() != null ? pathPaymentOperation.get().getFrom() : sourceAccount; - Memo memo = pathPaymentOperation.get().getTransaction().get().getMemo(); + Memo memo = pathPaymentOperation.get().getTransaction().getMemo(); transactionMemo = MemoHelper.memoAsString(memo); transactionMemoType = MemoHelper.memoTypeAsString(memo); - transactionEnvelope = pathPaymentOperation.get().getTransaction().get().getEnvelopeXdr(); + transactionEnvelope = pathPaymentOperation.get().getTransaction().getEnvelopeXdr(); } return CustodyPayment.builder() diff --git a/platform/src/main/java/org/stellar/anchor/platform/job/TrustlineCheckJob.java b/platform/src/main/java/org/stellar/anchor/platform/job/TrustlineCheckJob.java index 40f8454358..7239f7a7a8 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/job/TrustlineCheckJob.java +++ b/platform/src/main/java/org/stellar/anchor/platform/job/TrustlineCheckJob.java @@ -2,7 +2,6 @@ import static org.stellar.anchor.util.Log.info; -import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; import org.springframework.scheduling.annotation.Scheduled; @@ -13,6 +12,7 @@ import org.stellar.anchor.platform.data.JdbcTransactionPendingTrust; import org.stellar.anchor.platform.data.JdbcTransactionPendingTrustRepo; import org.stellar.anchor.platform.rpc.NotifyTrustSetHandler; +import org.stellar.sdk.exception.NetworkException; public class TrustlineCheckJob { @@ -50,7 +50,7 @@ public void checkTrust() throws AnchorException { boolean trustlineConfigured; try { trustlineConfigured = horizon.isTrustlineConfigured(t.getAccount(), t.getAsset()); - } catch (IOException ex) { + } catch (NetworkException ex) { trustlineConfigured = false; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java b/platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java index fa42401f7a..c1e436d3ac 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java +++ b/platform/src/main/java/org/stellar/anchor/platform/observer/ObservedPayment.java @@ -6,7 +6,9 @@ import org.stellar.anchor.api.asset.AssetInfo; import org.stellar.anchor.api.exception.SepException; import org.stellar.anchor.util.MemoHelper; -import org.stellar.sdk.*; +import org.stellar.sdk.AssetTypeCreditAlphaNum; +import org.stellar.sdk.AssetTypeNative; +import org.stellar.sdk.Memo; import org.stellar.sdk.responses.operations.PathPaymentBaseOperationResponse; import org.stellar.sdk.responses.operations.PaymentOperationResponse; @@ -44,8 +46,7 @@ public static ObservedPayment fromPaymentOperationResponse(PaymentOperationRespo throws SepException { String assetCode = null, assetIssuer = null; - if (paymentOp.getAsset() instanceof AssetTypeCreditAlphaNum) { - AssetTypeCreditAlphaNum issuedAsset = (AssetTypeCreditAlphaNum) paymentOp.getAsset(); + if (paymentOp.getAsset() instanceof AssetTypeCreditAlphaNum issuedAsset) { assetCode = issuedAsset.getCode(); assetIssuer = issuedAsset.getIssuer(); } else if (paymentOp.getAsset() instanceof AssetTypeNative) { @@ -55,16 +56,16 @@ public static ObservedPayment fromPaymentOperationResponse(PaymentOperationRespo String sourceAccount = paymentOp.getSourceAccount() != null ? paymentOp.getSourceAccount() - : paymentOp.getTransaction().get().getSourceAccount(); + : paymentOp.getTransaction().getSourceAccount(); String from = paymentOp.getFrom() != null ? paymentOp.getFrom() : sourceAccount; - Memo memo = paymentOp.getTransaction().get().getMemo(); + Memo memo = paymentOp.getTransaction().getMemo(); return ObservedPayment.builder() .id(paymentOp.getId().toString()) .type(Type.PAYMENT) .from(from) .to(paymentOp.getTo()) .amount(paymentOp.getAmount()) - .assetType(paymentOp.getAsset().getType()) + .assetType(paymentOp.getAssetType()) .assetCode(assetCode) .assetIssuer(assetIssuer) .assetName(paymentOp.getAsset().toString()) @@ -73,7 +74,7 @@ public static ObservedPayment fromPaymentOperationResponse(PaymentOperationRespo .transactionHash(paymentOp.getTransactionHash()) .transactionMemo(MemoHelper.memoAsString(memo)) .transactionMemoType(MemoHelper.memoTypeAsString(memo)) - .transactionEnvelope(paymentOp.getTransaction().get().getEnvelopeXdr()) + .transactionEnvelope(paymentOp.getTransaction().getEnvelopeXdr()) .build(); } @@ -101,21 +102,21 @@ public static ObservedPayment fromPathPaymentOperationResponse( String sourceAccount = pathPaymentOp.getSourceAccount() != null ? pathPaymentOp.getSourceAccount() - : pathPaymentOp.getTransaction().get().getSourceAccount(); + : pathPaymentOp.getTransaction().getSourceAccount(); String from = pathPaymentOp.getFrom() != null ? pathPaymentOp.getFrom() : sourceAccount; - Memo memo = pathPaymentOp.getTransaction().get().getMemo(); + Memo memo = pathPaymentOp.getTransaction().getMemo(); return ObservedPayment.builder() .id(pathPaymentOp.getId().toString()) .type(Type.PATH_PAYMENT) .from(from) .to(pathPaymentOp.getTo()) .amount(pathPaymentOp.getAmount()) - .assetType(pathPaymentOp.getAsset().getType()) + .assetType(pathPaymentOp.getAssetType()) .assetCode(assetCode) .assetIssuer(assetIssuer) .assetName(pathPaymentOp.getAsset().toString()) .sourceAmount(pathPaymentOp.getSourceAmount()) - .sourceAssetType(pathPaymentOp.getSourceAsset().getType()) + .sourceAssetType(pathPaymentOp.getSourceAssetType()) .sourceAssetCode(sourceAssetCode) .sourceAssetIssuer(sourceAssetIssuer) .sourceAssetName(pathPaymentOp.getSourceAsset().toString()) @@ -124,7 +125,7 @@ public static ObservedPayment fromPathPaymentOperationResponse( .transactionHash(pathPaymentOp.getTransactionHash()) .transactionMemo(MemoHelper.memoAsString(memo)) .transactionMemoType(MemoHelper.memoTypeAsString(memo)) - .transactionEnvelope(pathPaymentOp.getTransaction().get().getEnvelopeXdr()) + .transactionEnvelope(pathPaymentOp.getTransaction().getEnvelopeXdr()) .build(); } 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 8e17e69011..71e965ff94 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 @@ -11,7 +11,6 @@ import static org.stellar.anchor.util.StringHelper.isEmpty; import com.google.gson.annotations.SerializedName; -import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.*; @@ -37,6 +36,7 @@ import org.stellar.anchor.util.ExponentialBackoffTimer; import org.stellar.anchor.util.Log; import org.stellar.sdk.Server; +import org.stellar.sdk.exception.NetworkException; import org.stellar.sdk.requests.EventListener; import org.stellar.sdk.requests.PaymentsRequestBuilder; import org.stellar.sdk.requests.RequestBuilder; @@ -148,8 +148,8 @@ SSEStream startSSEStream() { new EventListener<>() { @Override public void onEvent(OperationResponse operationResponse) { - if (operationResponse.getTransaction().isPresent()) { - metricLatestBlockRead.set(operationResponse.getTransaction().get().getLedger()); + if (operationResponse.getTransaction() != null) { + metricLatestBlockRead.set(operationResponse.getTransaction().getLedger()); if (isHealthy()) { debugF("Received event {}", operationResponse.getId()); @@ -160,8 +160,7 @@ public void onEvent(OperationResponse operationResponse) { try { debugF("Dispatching event {}", operationResponse.getId()); handleEvent(operationResponse); - metricLatestBlockProcessed.set( - operationResponse.getTransaction().get().getLedger()); + metricLatestBlockProcessed.set(operationResponse.getTransaction().getLedger()); } catch (TransactionException ex) { errorEx("Error handling events", ex); @@ -336,14 +335,14 @@ String fetchLatestCursorFromNetwork() { infoF("Fetching the latest payments records. (limit={})", MIN_RESULTS); pageOpResponse = server.payments().order(RequestBuilder.Order.DESC).limit(MIN_RESULTS).execute(); - } catch (IOException e) { + } catch (NetworkException e) { Log.errorEx("Error fetching the latest /payments result.", e); return null; } if (pageOpResponse == null || pageOpResponse.getRecords() == null - || pageOpResponse.getRecords().size() == 0) { + || pageOpResponse.getRecords().isEmpty()) { info("No payments found."); return null; } @@ -353,7 +352,7 @@ String fetchLatestCursorFromNetwork() { } void handleEvent(OperationResponse operationResponse) { - if (!operationResponse.isTransactionSuccessful()) { + if (!operationResponse.getTransactionSuccessful()) { savePagingToken(operationResponse.getPagingToken()); return; } @@ -369,12 +368,11 @@ void handleEvent(OperationResponse operationResponse) { observedPayment = ObservedPayment.fromPathPaymentOperationResponse(pathPayment); } } catch (SepException ex) { - if (operationResponse.getTransaction().isPresent()) { + if (operationResponse.getTransaction() != null) { warn( String.format( "Payment of id %s contains unsupported memo %s.", - operationResponse.getId(), - operationResponse.getTransaction().get().getMemo().toString())); + operationResponse.getId(), operationResponse.getTransaction().getMemo())); } warnEx(ex); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/DoStellarPaymentHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/DoStellarPaymentHandler.java index 031e8f857c..8617e1fc4e 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/DoStellarPaymentHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/DoStellarPaymentHandler.java @@ -9,7 +9,6 @@ import static org.stellar.anchor.api.sep.SepTransactionStatus.PENDING_TRUST; import com.google.common.collect.ImmutableSet; -import java.io.IOException; import java.time.Instant; import java.util.Set; import org.stellar.anchor.api.exception.AnchorException; @@ -32,6 +31,7 @@ import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep6.Sep6TransactionStore; +import org.stellar.sdk.exception.NetworkException; public class DoStellarPaymentHandler extends RpcTransactionStatusHandler { @@ -103,7 +103,7 @@ protected SepTransactionStatus getNextStatus( default: break; } - } catch (IOException ex) { + } catch (NetworkException ex) { // assume trustline is not configured } @@ -150,7 +150,7 @@ protected void updateTransactionWithRpcRequest( try { trustlineConfigured = horizon.isTrustlineConfigured(txn6.getToAccount(), txn6.getAmountOutAsset()); - } catch (IOException ex) { + } catch (NetworkException ex) { trustlineConfigured = false; } @@ -172,7 +172,7 @@ protected void updateTransactionWithRpcRequest( try { trustlineConfigured = horizon.isTrustlineConfigured(txn24.getToAccount(), txn24.getAmountOutAsset()); - } catch (IOException ex) { + } catch (NetworkException ex) { trustlineConfigured = false; } diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java index a652bd7948..463169e6c1 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandler.java @@ -8,7 +8,6 @@ import static org.stellar.anchor.util.Log.errorEx; import com.google.common.collect.ImmutableSet; -import java.io.IOException; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,6 +35,7 @@ import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep6.Sep6TransactionStore; +import org.stellar.sdk.exception.NetworkException; import org.stellar.sdk.responses.operations.OperationResponse; public class NotifyOnchainFundsReceivedHandler @@ -164,7 +164,7 @@ protected void updateTransactionWithRpcRequest( JdbcSep31Transaction txn31 = (JdbcSep31Transaction) txn; txn31.setFromAccount(txnOperations.get(0).getSourceAccount()); } - } catch (IOException ex) { + } catch (NetworkException ex) { errorEx(String.format("Failed to retrieve stellar transaction by ID[%s]", stellarTxnId), ex); throw new InternalErrorException( String.format("Failed to retrieve Stellar transaction by ID[%s]", stellarTxnId), ex); diff --git a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandler.java b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandler.java index c3bb395857..1bbf62f71a 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandler.java +++ b/platform/src/main/java/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandler.java @@ -10,7 +10,6 @@ import static org.stellar.anchor.util.Log.errorEx; import com.google.common.collect.ImmutableSet; -import java.io.IOException; import java.time.Instant; import java.util.HashSet; import java.util.List; @@ -33,6 +32,7 @@ import org.stellar.anchor.sep24.Sep24TransactionStore; import org.stellar.anchor.sep31.Sep31TransactionStore; import org.stellar.anchor.sep6.Sep6TransactionStore; +import org.stellar.sdk.exception.NetworkException; import org.stellar.sdk.responses.operations.OperationResponse; public class NotifyOnchainFundsSentHandler @@ -110,7 +110,7 @@ protected void updateTransactionWithRpcRequest( try { List txnOperations = horizon.getStellarTxnOperations(stellarTxnId); addStellarTransaction(txn, stellarTxnId, txnOperations); - } catch (IOException ex) { + } catch (NetworkException ex) { errorEx(String.format("Failed to retrieve stellar transaction by ID[%s]", stellarTxnId), ex); throw new InternalErrorException( String.format("Failed to retrieve Stellar transaction by ID[%s]", stellarTxnId), ex); diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/RpcService.java b/platform/src/main/java/org/stellar/anchor/platform/service/RpcService.java index b39d88975b..957ff74782 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/RpcService.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/RpcService.java @@ -19,7 +19,7 @@ import org.stellar.anchor.platform.config.RpcConfig; import org.stellar.anchor.platform.rpc.RpcMethodHandler; import org.stellar.anchor.platform.utils.RpcUtil; -import org.stellar.sdk.requests.ErrorResponse; +import org.stellar.sdk.exception.NetworkException; public class RpcService { @@ -53,7 +53,7 @@ public List handle(List rpcRequests) { return RpcUtil.getRpcErrorResponse(rc, ex); } catch (BadRequestException ex) { return RpcUtil.getRpcErrorResponse(rc, ex); - } catch (ErrorResponse ex) { + } catch (NetworkException ex) { var message = ex.getMessage() + " Code: " + ex.getCode() + " , body: " + ex.getBody(); errorEx( 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 d9af9e001b..a89432dcf5 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 @@ -5,7 +5,6 @@ package org.stellar.anchor.platform.observer.stellar import com.google.gson.reflect.TypeToken import io.mockk.* import io.mockk.impl.annotations.MockK -import java.io.IOException import java.util.* import javax.net.ssl.SSLProtocolException import org.junit.jupiter.api.Assertions.assertEquals @@ -15,10 +14,11 @@ 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.exception.NetworkException 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.gson.GsonSingleton import org.stellar.sdk.responses.operations.OperationResponse class StellarPaymentObserverTest { @@ -76,7 +76,7 @@ class StellarPaymentObserverTest { .order(RequestBuilder.Order.DESC) .limit(1) .execute() - } throws IOException("Some IO Problem happened!") + } throws NetworkException(null, "Some IO Problem happened!") gotCursor = stellarObserver.fetchStreamingCursor() verify(exactly = 2) { paymentStreamerCursorStore.load() } diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt index f1efd54946..1839456cb5 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsReceivedHandlerTest.kt @@ -4,7 +4,6 @@ import com.google.gson.reflect.TypeToken import io.micrometer.core.instrument.Counter import io.mockk.* import io.mockk.impl.annotations.MockK -import java.io.IOException import java.time.Instant import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -46,6 +45,7 @@ import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore import org.stellar.anchor.sep6.Sep6TransactionStore import org.stellar.anchor.util.GsonUtils +import org.stellar.sdk.exception.NetworkException import org.stellar.sdk.responses.operations.OperationResponse import org.stellar.sdk.responses.operations.PaymentOperationResponse @@ -577,7 +577,7 @@ class NotifyOnchainFundsReceivedHandlerTest { every { txn31Store.findByTransactionId(any()) } returns null every { txn24Store.save(capture(sep24TxnCapture)) } returns null every { horizon.getStellarTxnOperations(any()) } throws - IOException("Invalid stellar transaction") + NetworkException(400, "Invalid stellar transaction") val ex = assertThrows { handler.handle(request) } assertEquals("Failed to retrieve Stellar transaction by ID[stellarTxId]", ex.message) @@ -1037,7 +1037,7 @@ class NotifyOnchainFundsReceivedHandlerTest { every { txn31Store.findByTransactionId(any()) } returns null every { txn6Store.save(capture(sep6TxnCapture)) } returns null every { horizon.getStellarTxnOperations(any()) } throws - IOException("Invalid stellar transaction") + NetworkException(400, "Invalid stellar transaction") val ex = assertThrows { handler.handle(request) } assertEquals("Failed to retrieve Stellar transaction by ID[stellarTxId]", ex.message) diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandlerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandlerTest.kt index 8512779991..5dbbf13962 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandlerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/rpc/NotifyOnchainFundsSentHandlerTest.kt @@ -4,7 +4,6 @@ import com.google.gson.reflect.TypeToken import io.micrometer.core.instrument.Counter import io.mockk.* import io.mockk.impl.annotations.MockK -import java.io.IOException import java.time.Instant import kotlin.test.assertEquals import kotlin.test.assertTrue @@ -45,6 +44,7 @@ import org.stellar.anchor.sep24.Sep24TransactionStore import org.stellar.anchor.sep31.Sep31TransactionStore import org.stellar.anchor.sep6.Sep6TransactionStore import org.stellar.anchor.util.GsonUtils +import org.stellar.sdk.exception.NetworkException import org.stellar.sdk.responses.operations.OperationResponse import org.stellar.sdk.responses.operations.PaymentOperationResponse @@ -163,7 +163,7 @@ class NotifyOnchainFundsSentHandlerTest { every { txn31Store.findByTransactionId(any()) } returns null every { txn24Store.save(capture(sep24TxnCapture)) } returns null every { horizon.getStellarTxnOperations(any()) } throws - IOException("Invalid stellar transaction") + NetworkException(400, "Invalid stellar transaction") val ex = assertThrows { handler.handle(request) } assertEquals("Failed to retrieve Stellar transaction by ID[stellarTxId]", ex.message) diff --git a/wallet-reference-server/build.gradle.kts b/wallet-reference-server/build.gradle.kts index 25992c1319..5b81c58198 100644 --- a/wallet-reference-server/build.gradle.kts +++ b/wallet-reference-server/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { implementation(libs.bundles.ktor) implementation(libs.bundles.ktor.client) implementation(libs.bcastle) + implementation(libs.bcutil) implementation(libs.google.gson) implementation(libs.hoplite.core) implementation(libs.hoplite.yaml)