Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ANCHOR-916] Put Horizon behind interface #1624

Open
wants to merge 1 commit into
base: epic/anchor-937-stellar-rpc-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package org.stellar.anchor.horizon;
package org.stellar.anchor.ledger;

import static org.stellar.anchor.api.asset.AssetInfo.NATIVE_ASSET_CODE;

import java.util.List;
import java.util.stream.Collectors;
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.Transaction;
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.TransactionResponse;
import org.stellar.sdk.responses.operations.OperationResponse;
import org.stellar.sdk.xdr.AssetType;

/** The horizon-server. */
public class Horizon {
public class Horizon implements LedgerApi {

@Getter private final String horizonUrl;
@Getter private final String stellarNetworkPassphrase;
Expand All @@ -32,7 +35,7 @@ public Server getServer() {
return this.horizonServer;
}

public boolean isTrustlineConfigured(String account, String asset) throws NetworkException {
public boolean hasTrustline(String account, String asset) throws NetworkException {
String assetCode = AssetHelper.getAssetCode(asset);
if (NATIVE_ASSET_CODE.equals(assetCode)) {
return true;
Expand All @@ -56,13 +59,54 @@ public boolean isTrustlineConfigured(String account, String asset) throws Networ
});
}

@Override
public Account getAccount(String account) throws NetworkException {
AccountResponse response = getServer().accounts().account(account);
AccountResponse.Thresholds thresholds = response.getThresholds();

return Account.builder()
.accountId(response.getAccountId())
.sequenceNumber(response.getSequenceNumber())
.thresholds(
LedgerApi.Thresholds.builder()
.lowThreshold(thresholds.getLowThreshold())
.medThreshold(thresholds.getMedThreshold())
.highThreshold(thresholds.getHighThreshold())
.build())
.balances(
response.getBalances().stream()
.map(
b ->
Balance.builder()
.assetType(b.getAssetType())
.assetCode(b.getAssetCode())
.assetIssuer(b.getAssetIssuer())
.liquidityPoolId(b.getLiquidityPoolId())
.limit(b.getLimit())
.build())
.collect(Collectors.toList()))
.signers(
response.getSigners().stream()
.map(
s ->
Signer.builder()
.key(s.getKey())
.type(s.getType())
.weight(s.getWeight())
.sponsor(s.getSponsor())
.build())
.collect(Collectors.toList()))
.build();
}

/**
* Get payment operations for a transaction.
*
* @param stellarTxnId the transaction id
* @return the operations
* @throws NetworkException request failed, see {@link PaymentsRequestBuilder#execute()}
*/
@Override
public List<OperationResponse> getStellarTxnOperations(String stellarTxnId) {
return getServer()
.payments()
Expand All @@ -71,4 +115,9 @@ public List<OperationResponse> getStellarTxnOperations(String stellarTxnId) {
.execute()
.getRecords();
}

@Override
public TransactionResponse submitTransaction(Transaction transaction) throws NetworkException {
return getServer().submitTransaction(transaction, false);
lijamie98 marked this conversation as resolved.
Show resolved Hide resolved
}
}
111 changes: 111 additions & 0 deletions core/src/main/java/org/stellar/anchor/ledger/LedgerApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package org.stellar.anchor.ledger;

import java.io.IOException;
import java.util.List;
import lombok.Builder;
import lombok.Getter;
import lombok.Value;
import org.stellar.sdk.KeyPair;
import org.stellar.sdk.Transaction;
import org.stellar.sdk.TransactionBuilderAccount;
import org.stellar.sdk.exception.NetworkException;
import org.stellar.sdk.responses.TransactionResponse;
import org.stellar.sdk.responses.operations.OperationResponse;

public interface LedgerApi {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEP-45 uses transaction simulation which is only available in RPC. How do we plan to expose RPC-specific methods in the interface?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In SEP-45, it should work directly with the implementation class instead of LedgerApi.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And Horizon option (horizon_url) will cause conflict and fail to start during validation of sep45.enabled is true.

/**
* Check if the account has a trustline for the given asset.
*
* @param account The account to check.
* @param asset The asset to check.
* @return True if the account has a trustline for the asset.
* @throws NetworkException If there was an error communicating with the network.
*/
boolean hasTrustline(String account, String asset) throws NetworkException, IOException;

/**
* Get the account details for the given account.
*
* @param account The account to get.
* @return The account details.
* @throws NetworkException If there was an error communicating with the network.
*/
Account getAccount(String account) throws NetworkException;

/**
* Get the operations for the given Stellar transaction.
*
* @param stellarTxnId The Stellar transaction ID.
* @return The operations for the transaction.
*/
List<OperationResponse> getStellarTxnOperations(String stellarTxnId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OperationResponse is Horizon's getOperation response. If we use RPC, we must parse the operations from the transaction envelope. Should we create a new type to encapsulate the operation response?


/**
* Submit a transaction to the network.
*
* @param transaction
* @return The transaction response.
* @throws NetworkException
*/
TransactionResponse submitTransaction(Transaction transaction) throws NetworkException;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TransactionResponse is Horizon's submitTransaction response. RPC's sendTransaction returns a SendTransactionResponse which only contains the transaction hash and its status. We will need to call getTransaction to fetch the transaction afterward. The schemas are not the same. Should we create a new type to encapsulate the transaction submission responses?


@Builder
@Getter
class Account implements TransactionBuilderAccount {
private String accountId;
private Long sequenceNumber;

private Thresholds thresholds;
private List<Balance> balances;
private List<Signer> signers;

@Override
public KeyPair getKeyPair() {
return KeyPair.fromAccountId(accountId);
}

@Override
public void setSequenceNumber(long seqNum) {
sequenceNumber = seqNum;
}

@Override
public Long getIncrementedSequenceNumber() {
return sequenceNumber + 1;
}

/** Increments sequence number in this object by one. */
public void incrementSequenceNumber() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required by the TransactionBuilderAccount interface.

sequenceNumber++;
}
}

@Builder
@Getter
class Thresholds {
Integer lowThreshold;
Integer medThreshold;
Integer highThreshold;
}

@Builder
@Getter
class Balance {
String assetType;
String assetCode;
String assetIssuer;
String liquidityPoolId;
String limit;
String balance;
}

@Value
@Builder
@Getter
class Signer {
String key;
String type;
Integer weight;
String sponsor;
}
}
84 changes: 84 additions & 0 deletions core/src/main/java/org/stellar/anchor/ledger/StellarRpc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.stellar.anchor.ledger;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import lombok.SneakyThrows;
import org.stellar.sdk.KeyPair;
import org.stellar.sdk.SorobanServer;
import org.stellar.sdk.Transaction;
import org.stellar.sdk.exception.NetworkException;
import org.stellar.sdk.responses.TransactionResponse;
import org.stellar.sdk.responses.operations.OperationResponse;
import org.stellar.sdk.responses.sorobanrpc.GetLedgerEntriesResponse;
import org.stellar.sdk.xdr.AccountEntry;
import org.stellar.sdk.xdr.LedgerEntry;
import org.stellar.sdk.xdr.LedgerEntryType;
import org.stellar.sdk.xdr.LedgerKey;

public class StellarRpc implements LedgerApi {
String rpcServerUrl;
SorobanServer sorobanServer;

public StellarRpc(String rpcServerUrl) {
this.rpcServerUrl = rpcServerUrl;
sorobanServer = new SorobanServer(rpcServerUrl);
}

@SneakyThrows
@Override
public boolean hasTrustline(String account, String asset) throws NetworkException {
AccountEntry accountEntry = fetchAccountEntry(account);
// TODO: Implement this method
return false;
}

@Override
public Account getAccount(String account) throws NetworkException {
// TODO: Implement this method
return null;
}

@Override
public List<OperationResponse> getStellarTxnOperations(String stellarTxnId) {
// TODO: Implement this method
return List.of();
}

@Override
public TransactionResponse submitTransaction(Transaction transaction) throws NetworkException {
// TODO: Implement this method
return null;
}

private AccountEntry fetchAccountEntry(String account) throws IOException {
// TODO: Implement this method
return null;
// GetLedgerEntriesResponse response;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to put the Horizon behind the interface. Will add tests after the StellarRpc implementation in a different PR.

// KeyPair keyPair = KeyPair.fromAccountId(account);
//
// // Create ledger keys
// List<LedgerKey> ledgerKeys =
// Collections.singletonList(
// LedgerKey.builder()
// .account(
// LedgerKey.LedgerKeyAccount.builder()
// .accountID(keyPair.getXdrAccountId())
// .build())
// .discriminant(LedgerEntryType.ACCOUNT)
// .build());
//
// // Get ledger entries
// response = sorobanServer.getLedgerEntries(ledgerKeys);
//
// // Print ledger entries
// for (GetLedgerEntriesResponse.LedgerEntryResult result : response.getEntries()) {
// LedgerEntry.LedgerEntryData ledgerEntryData =
// LedgerEntry.LedgerEntryData.fromXdrBase64(result.getXdr());
// if (ledgerEntryData.getDiscriminant() == LedgerEntryType.ACCOUNT) {
// return ledgerEntryData.getAccount();
// }
// }
// throw new NetworkException(404, "Account not found");
}
}
25 changes: 12 additions & 13 deletions core/src/main/java/org/stellar/anchor/sep10/Sep10Service.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,21 @@
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.ledger.LedgerApi;
import org.stellar.anchor.util.Log;
import org.stellar.sdk.*;
import org.stellar.sdk.Sep10Challenge.ChallengeTransaction;
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. */
public class Sep10Service implements ISep10Service {
final AppConfig appConfig;
final SecretConfig secretConfig;
final Sep10Config sep10Config;
final Horizon horizon;
final LedgerApi ledgerApi;
final JwtService jwtService;
final ClientFinder clientFinder;
final String serverAccountId;
Expand All @@ -57,15 +56,15 @@ public Sep10Service(
AppConfig appConfig,
SecretConfig secretConfig,
Sep10Config sep10Config,
Horizon horizon,
LedgerApi ledgerApi,
JwtService jwtService,
ClientFinder clientFinder) {
debug("appConfig:", appConfig);
debug("sep10Config:", sep10Config);
this.appConfig = appConfig;
this.secretConfig = secretConfig;
this.sep10Config = sep10Config;
this.horizon = horizon;
this.ledgerApi = ledgerApi;
this.jwtService = jwtService;
this.clientFinder = clientFinder;
this.serverAccountId =
Expand Down Expand Up @@ -127,11 +126,11 @@ public ValidationResponse validateChallenge(ValidationRequest request)

// fetch the client domain from the transaction
String clientDomain = fetchClientDomain(challenge);
// fetch the account response from the horizon
AccountResponse account = fetchAccount(request, challenge, clientDomain);
// fetch the account response from the ledgerApi
LedgerApi.Account account = fetchAccount(request, challenge, clientDomain);

if (account == null) {
// The account does not exist from Horizon, using the client's master key to verify.
// The account does not exist from LedgerApi, using the client's master key to verify.
return ValidationResponse.of(generateSep10Jwt(challenge, clientDomain, homeDomain));
}
// Since the account exists, we should check the signers and the client domain
Expand Down Expand Up @@ -388,7 +387,7 @@ void validateAccountFormat(ChallengeRequest request) throws SepException {
}

void validateChallengeRequest(
ValidationRequest request, AccountResponse account, String clientDomain)
ValidationRequest request, LedgerApi.Account account, String clientDomain)
throws SepValidationException {
// fetch the signers from the transaction
Set<Sep10Challenge.Signer> signers = fetchSigners(account);
Expand All @@ -414,22 +413,22 @@ void validateChallengeRequest(
signers);
}

Set<Sep10Challenge.Signer> fetchSigners(AccountResponse account) {
Set<Sep10Challenge.Signer> fetchSigners(LedgerApi.Account account) {
// Find the signers of the client account.
return account.getSigners().stream()
.filter(as -> as.getType().equals("ed25519_public_key"))
.map(as -> new Sep10Challenge.Signer(as.getKey(), as.getWeight()))
.collect(Collectors.toSet());
}

AccountResponse fetchAccount(
LedgerApi.Account fetchAccount(
ValidationRequest request, ChallengeTransaction challenge, String clientDomain)
throws SepValidationException {
// Check the client's account
AccountResponse account;
LedgerApi.Account account;
try {
infoF("Checking if {} exists in the Stellar network", challenge.getClientAccountId());
account = horizon.getServer().accounts().account(challenge.getClientAccountId());
account = ledgerApi.getAccount(challenge.getClientAccountId());
traceF("challenge account: {}", account);
sep10ChallengeValidatedCounter.increment();
return account;
Expand Down
Loading
Loading