From 2e05548b5e72a5e1655b52c6178128c53a9dc216 Mon Sep 17 00:00:00 2001 From: Rizwan Tanoli Date: Mon, 23 Oct 2017 22:54:23 -0400 Subject: [PATCH 1/2] Fixed Javadoc comments as part #1 --- pom.xml | 618 +++++----- .../org/interledger/connector/Connector.java | 103 +- .../interledger/connector/ConnectorUtils.java | 128 +- .../connector/config/ConnectorConfig.java | 39 +- .../config/ConnectorConfigurationService.java | 103 +- .../lpi/AbstractLedgerPluginEventHandler.java | 1040 +++++++++-------- .../interledger/connector/quoting/Quote.java | 128 +- .../connector/quoting/QuotingService.java | 98 +- .../connector/routing/InterledgerHop.java | 112 +- .../connector/routing/InterledgerRoute.java | 194 +-- .../connector/routing/PaymentRouter.java | 80 +- 11 files changed, 1346 insertions(+), 1297 deletions(-) diff --git a/pom.xml b/pom.xml index 0f39c6c..bb700cf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,309 +1,309 @@ - - - 4.0.0 - - org.interledger - ilp-connector - 0.1.0-SNAPSHOT - - ILP Connector (Java) - Java implementation of an Interledger Connector. - http://github.com/interledger/java-ilp-connecotr - 2017 - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - David Fuelling - https://github.com/sappenin - - - - - scm:git:git://github.com/interledger/java-ilp-connector.git - scm:git:git@github.com:interledger/java-ilp-connector.git - - https://github.com/interledger/java-ilp-connector - - - - 2.8.9 - 21.0 - 2.5.6 - 0.9.0-SNAPSHOT - 0.1.2-SNAPSHOT - - 2.17 - true - false - false - google_checks.xml - false - - 1.8 - 1.8 - UTF-8 - 0.90 - - - - - - ch.qos.logback - logback-classic - 1.2.2 - test - - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson-annotations.version} - - - - com.google.guava - guava - ${guava.version} - - - - org.testng - testng - 6.9.10 - - - - org.hamcrest - hamcrest-all - 1.3 - test - - - - org.immutables - value - ${immutables.version} - provided - - - - org.interledger - ilp-core - ${ilp-core.version} - - - - org.interledger - ilp-plugin - ${ilp-plugin.version} - - - - org.javamoney - moneta - 1.1 - - - - org.mockito - mockito-core - 2.7.22 - test - - - - org.slf4j - slf4j-api - 1.7.25 - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.2 - - ${maven.compiler.source} - ${maven.compiler.target} - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - process-sources - - check - - - - - google_checks.xml - - - - - org.jacoco - jacoco-maven-plugin - 0.7.9 - - - - prepare-agent - - - - report - test - - report - - - - - - - maven-surefire-plugin - 2.20 - - ${argLine} - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - ossrh-snapshots-interledger - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - false - - - - attach-javadocs - - jar - - - - - - - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - - checkstyle - - - - - google_checks.xml - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.10.4 - - - - - - - - snapshots-repo - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - - - - - ossrh-snapshots-interledger - https://oss.sonatype.org/content/repositories/snapshots - - - - - - sign - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - + + + 4.0.0 + + org.interledger + ilp-connector + 0.1.0-SNAPSHOT + + ILP Connector (Java) + Java implementation of an Interledger Connector. + http://github.com/interledger/java-ilp-connecotr + 2017 + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + David Fuelling + https://github.com/sappenin + + + + + scm:git:git://github.com/interledger/java-ilp-connector.git + scm:git:git@github.com:interledger/java-ilp-connector.git + + https://github.com/interledger/java-ilp-connector + + + + 2.8.9 + 21.0 + 2.5.6 + 0.9.0-SNAPSHOT + 0.1.2-SNAPSHOT + + 2.17 + true + false + false + google_checks.xml + false + + 1.8 + 1.8 + UTF-8 + 0.90 + + + + + + ch.qos.logback + logback-classic + 1.2.2 + test + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson-annotations.version} + + + + com.google.guava + guava + ${guava.version} + + + + org.testng + testng + 6.9.10 + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + org.immutables + value + ${immutables.version} + provided + + + + org.interledger + ilp-core + ${ilp-core.version} + + + + org.interledger + ilp-plugin + ${ilp-plugin.version} + + + + org.javamoney + moneta + 1.1 + + + + org.mockito + mockito-core + 2.7.22 + test + + + + org.slf4j + slf4j-api + 1.7.25 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.2 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + process-sources + + check + + + + + google_checks.xml + + + + + org.jacoco + jacoco-maven-plugin + 0.7.9 + + + + prepare-agent + + + + report + test + + report + + + + + + + maven-surefire-plugin + 2.20 + + ${argLine} + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + ossrh-snapshots-interledger + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + true + + + + attach-javadocs + + jar + + + + + + + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + + checkstyle + + + + + google_checks.xml + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.4 + + + + + + + + snapshots-repo + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + + ossrh-snapshots-interledger + https://oss.sonatype.org/content/repositories/snapshots + + + + + + sign + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + diff --git a/src/main/java/org/interledger/connector/Connector.java b/src/main/java/org/interledger/connector/Connector.java index c881d0e..aace34a 100644 --- a/src/main/java/org/interledger/connector/Connector.java +++ b/src/main/java/org/interledger/connector/Connector.java @@ -1,52 +1,53 @@ -package org.interledger.connector; - - -import org.interledger.connector.config.ConnectorConfigurationService; -import org.interledger.connector.services.LedgerPluginManager; - -/** - * This interface defines an Interledger Connector, which orchestrates any two ledgers to facilitate an Interledger - * payment. - * - *

The Connector serves three primary purposes:

- * - *
- * 1. To hold accounts on various ledgers so that it can provide liquidity across ledgers.
- * 3. To provide route information and routing updates to other connectors.
- * 3. To provide quotes for a given ledger-to-ledger transfer.
- * 
- * - *

Interledger Payments moves asset representations (e.g., currency, stock, IOUs, gold, etc) from one party to - * another by utilizing one or more ledger transfers, potentially across multiple ledgers.

- * - *

When a sender prepares a transfer on a Ledger to start a payment, the sender attaches an ILP Payment to the - * ledger transfer, in the memo field if possible. If a ledger does not support attaching the entire ILP Payment to a - * transfer as a memo, users of that ledger can transmit the ILP Payment using another authenticated messaging channel, - * but MUST be able to correlate transfers and ILP Payments.

- * - *

When a connector sees an incoming prepared transfer with an ILP Payment, it reads the ILP Payment information to - * confirm the details of the packet. For example, the connector reads the InterledgerAddress of the payment's receiver, - * and if the connector has a route to the receiver's account, the connector prepares a transfer to continue the payment - * chain by attaching the same ILP Payment to the new transfer.

- * - *

At the end of the payment chain, the final receiver (or, more likely, that ledger acting on behalf of the final - * receiver) confirms that the amount in the ILP Payment Packet matches the amount actually delivered by the transfer. - * Finally, the last-hop ledger decodes the data portion of the Payment and matches the condition to the payment. The - * final Interledger node MUST confirm the integrity of the ILP Payment, for example with a hash-based message - * authentication code (HMAC). If the receiver finds the transfer acceptable, the receiver releases the fulfillment for - * the transfer, which can be used to execute all prepared transfers that were established prior to the receiver - * accepting the payment.

- */ -public interface Connector { - - /** - * Accessor for the {@link LedgerPluginManager} that is used to centralize all interactions with ledger plugins for a - * given Connector. - */ - LedgerPluginManager getLedgerPluginManager(); - - T getConnectorConfigurationService(); - - // TODO: Router, Quoter - +package org.interledger.connector; + + +import org.interledger.connector.config.ConnectorConfigurationService; +import org.interledger.connector.services.LedgerPluginManager; + +/** + * This interface defines an Interledger Connector, which orchestrates any two ledgers to facilitate an Interledger + * payment. + * + *

The Connector serves three primary purposes:

+ * + *
+ * 1. To hold accounts on various ledgers so that it can provide liquidity across ledgers.
+ * 3. To provide route information and routing updates to other connectors.
+ * 3. To provide quotes for a given ledger-to-ledger transfer.
+ * 
+ * + *

Interledger Payments moves asset representations (e.g., currency, stock, IOUs, gold, etc) from one party to + * another by utilizing one or more ledger transfers, potentially across multiple ledgers.

+ * + *

When a sender prepares a transfer on a Ledger to start a payment, the sender attaches an ILP Payment to the + * ledger transfer, in the memo field if possible. If a ledger does not support attaching the entire ILP Payment to a + * transfer as a memo, users of that ledger can transmit the ILP Payment using another authenticated messaging channel, + * but MUST be able to correlate transfers and ILP Payments.

+ * + *

When a connector sees an incoming prepared transfer with an ILP Payment, it reads the ILP Payment information to + * confirm the details of the packet. For example, the connector reads the InterledgerAddress of the payment's receiver, + * and if the connector has a route to the receiver's account, the connector prepares a transfer to continue the payment + * chain by attaching the same ILP Payment to the new transfer.

+ * + *

At the end of the payment chain, the final receiver (or, more likely, that ledger acting on behalf of the final + * receiver) confirms that the amount in the ILP Payment Packet matches the amount actually delivered by the transfer. + * Finally, the last-hop ledger decodes the data portion of the Payment and matches the condition to the payment. The + * final Interledger node MUST confirm the integrity of the ILP Payment, for example with a hash-based message + * authentication code (HMAC). If the receiver finds the transfer acceptable, the receiver releases the fulfillment for + * the transfer, which can be used to execute all prepared transfers that were established prior to the receiver + * accepting the payment.

+ */ +public interface Connector { + + /** + * Accessor for the {@link LedgerPluginManager} that is used to centralize all interactions with ledger plugins for a + * given Connector. + * @return {@link LedgerPluginManager} + */ + LedgerPluginManager getLedgerPluginManager(); + + T getConnectorConfigurationService(); + + // TODO: Router, Quoter + } \ No newline at end of file diff --git a/src/main/java/org/interledger/connector/ConnectorUtils.java b/src/main/java/org/interledger/connector/ConnectorUtils.java index 3284693..790b984 100644 --- a/src/main/java/org/interledger/connector/ConnectorUtils.java +++ b/src/main/java/org/interledger/connector/ConnectorUtils.java @@ -1,64 +1,64 @@ -package org.interledger.connector; - -import org.interledger.InterledgerAddress; -import org.interledger.plugin.lpi.TransferId; - -import com.google.common.io.BaseEncoding; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Objects; -import java.util.UUID; - -/** - * Various utilities that might be useful to consumers of this library. - */ -public class ConnectorUtils { - - /** - * Using HMAC-SHA-256, deterministically generate a UUID from a secret and a public input, which in this case is a - * ledger-prefix and a transferId. - * - * This method can be used to generate a deterministic identifier for the "next" transfer that a Connector might make, - * so that the connector doesn't send duplicate outgoing transfers if it receives duplicate notifications. In the case - * of a Connector's next-hop transfer identifier, the deterministic generation should ideally be impossible for a - * third party to predict. Otherwise an attacker might be able to squat on a predicted ID in order to interfere with a - * payment or make a connector look unreliable. In order to assure this, the connector may use a secret that seeds the - * deterministic ID generation. - * - * @param secret A {@link String} containing secret information known only to the creator of this transfer id. - * @param ledgerPrefix A {@link InterledgerAddress} containing a ledger prefix. - * @param transferId A {@link TransferId} that uniquely identifies the transfer. - * - * @returns A deterministically generated {@link UUID}. - **/ - public static TransferId generateTransferId( - final String secret, final InterledgerAddress ledgerPrefix, final TransferId transferId - ) { - Objects.requireNonNull(secret); - Objects.requireNonNull(ledgerPrefix); - InterledgerAddress.requireLedgerPrefix(ledgerPrefix); - Objects.requireNonNull(transferId); - - final String publicInput = String.format("%s/%s", ledgerPrefix, transferId); - - try { - final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); - messageDigest.update(secret.getBytes()); - byte[] digest = messageDigest.digest(publicInput.getBytes()); - - final String hash = BaseEncoding.base16().encode(digest).substring(0, 36); - final char[] hashCharArray = hash.toCharArray(); - hashCharArray[8] = '-'; - hashCharArray[13] = '-'; - hashCharArray[14] = '4'; - hashCharArray[18] = '-'; - hashCharArray[19] = '8'; - hashCharArray[23] = '-'; - return TransferId.of(UUID.fromString(new String(hashCharArray))); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - - } -} +package org.interledger.connector; + +import org.interledger.InterledgerAddress; +import org.interledger.plugin.lpi.TransferId; + +import com.google.common.io.BaseEncoding; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; +import java.util.UUID; + +/** + * Various utilities that might be useful to consumers of this library. + */ +public class ConnectorUtils { + + /** + * Using HMAC-SHA-256, deterministically generate a UUID from a secret and a public input, which in this case is a + * ledger-prefix and a transferId. + * + * This method can be used to generate a deterministic identifier for the "next" transfer that a Connector might make, + * so that the connector doesn't send duplicate outgoing transfers if it receives duplicate notifications. In the case + * of a Connector's next-hop transfer identifier, the deterministic generation should ideally be impossible for a + * third party to predict. Otherwise an attacker might be able to squat on a predicted ID in order to interfere with a + * payment or make a connector look unreliable. In order to assure this, the connector may use a secret that seeds the + * deterministic ID generation. + * + * @param secret A {@link String} containing secret information known only to the creator of this transfer id. + * @param ledgerPrefix A {@link InterledgerAddress} containing a ledger prefix. + * @param transferId A {@link TransferId} that uniquely identifies the transfer. + * + * @return A deterministically generated {@link UUID}. + **/ + public static TransferId generateTransferId( + final String secret, final InterledgerAddress ledgerPrefix, final TransferId transferId + ) { + Objects.requireNonNull(secret); + Objects.requireNonNull(ledgerPrefix); + InterledgerAddress.requireLedgerPrefix(ledgerPrefix); + Objects.requireNonNull(transferId); + + final String publicInput = String.format("%s/%s", ledgerPrefix, transferId); + + try { + final MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + messageDigest.update(secret.getBytes()); + byte[] digest = messageDigest.digest(publicInput.getBytes()); + + final String hash = BaseEncoding.base16().encode(digest).substring(0, 36); + final char[] hashCharArray = hash.toCharArray(); + hashCharArray[8] = '-'; + hashCharArray[13] = '-'; + hashCharArray[14] = '4'; + hashCharArray[18] = '-'; + hashCharArray[19] = '8'; + hashCharArray[23] = '-'; + return TransferId.of(UUID.fromString(new String(hashCharArray))); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/main/java/org/interledger/connector/config/ConnectorConfig.java b/src/main/java/org/interledger/connector/config/ConnectorConfig.java index 5a650dc..4345f8b 100644 --- a/src/main/java/org/interledger/connector/config/ConnectorConfig.java +++ b/src/main/java/org/interledger/connector/config/ConnectorConfig.java @@ -1,19 +1,20 @@ -package org.interledger.connector.config; - -/** - * Defines Connector-wide configuration properties that affect the connector as a whole. - */ -public interface ConnectorConfig { - - /** - * The minimum time, in seconds, that the connector wants to budget for getting a message to the ledgers its trading - * on. - * - * Defaults to 1 second. - */ - default Integer getMinimumMessageWindow() { - return 1; - } - - -} +package org.interledger.connector.config; + +/** + * Defines Connector-wide configuration properties that affect the connector as a whole. + */ +public interface ConnectorConfig { + + /** + * The minimum time, in seconds, that the connector wants to budget for getting a message to the ledgers its trading + * on. + * + * Defaults to 1 second. + * @return Integer + */ + default Integer getMinimumMessageWindow() { + return 1; + } + + +} diff --git a/src/main/java/org/interledger/connector/config/ConnectorConfigurationService.java b/src/main/java/org/interledger/connector/config/ConnectorConfigurationService.java index 68bdf4f..6939143 100644 --- a/src/main/java/org/interledger/connector/config/ConnectorConfigurationService.java +++ b/src/main/java/org/interledger/connector/config/ConnectorConfigurationService.java @@ -1,51 +1,52 @@ -package org.interledger.connector.config; - -import org.interledger.InterledgerAddress; -import org.interledger.connector.services.LedgerPluginManager; -import org.interledger.plugin.lpi.LedgerPluginConfig; - -import java.util.Collection; - -/** - * A configuration service that provides typed (and runtime-reloadable) access to important configuration properties for - * _this_ connector. - */ -public interface ConnectorConfigurationService { - - /** - * Accessor for Connector-wide configuration values. - */ - T getConnectorConfig(); - - /** - * Accessor for all currently configured ledger plugins. - * - * Note that this list does not necessarily reflect the current status of a connection to any underlying ledger, but - * instead merely represents information about the current configuration (whereas the current state of the connector, - * such as the availability of a given ledger plugin due to downtime) is managed by a separate service. - * - * @return A {@link Collection} of type {@link LedgerPluginConfig} for all configured ledger plugins. - */ - Collection getLedgerPluginConfigurations(); - - /** - * Return configuration info for the ledger plugin indicated by the supplied {@code ledgerPrefix}. This interface - * returns only some typed configuration properties, with any other properties contained in {@link - * LedgerPluginConfig#getOptions()}. Implementations can write adaptor classes to access these properties in a typed - * fashion. - * - * Note that this list does not necessarily reflect the current status of a connection to any underlying ledger, but - * instead merely represents information about the current configuration (whereas the current state of the connector, - * such as the availability of a given ledger plugin due to downtime) is managed by the {@link LedgerPluginManager} - * and the plugin itself. - * - * @param ledgerPrefix An {@link InterledgerAddress} that is a ledger-prefix for a ledger that this connector has an - * account on. - * - * @return A {@link LedgerPluginConfig} for the specified ledger prefix. - * - * @throws RuntimeException if ledger-plugin configuration cannot be found or otherwise assembled. - */ - LedgerPluginConfig getLedgerPluginConfiguration(InterledgerAddress ledgerPrefix); - -} +package org.interledger.connector.config; + +import org.interledger.InterledgerAddress; +import org.interledger.connector.services.LedgerPluginManager; +import org.interledger.plugin.lpi.LedgerPluginConfig; + +import java.util.Collection; + +/** + * A configuration service that provides typed (and runtime-reloadable) access to important configuration properties for + * _this_ connector. + */ +public interface ConnectorConfigurationService { + + /** + * Accessor for Connector-wide configuration values. + * @return Generic Type T of {@link ConnectorConfig} + */ + T getConnectorConfig(); + + /** + * Accessor for all currently configured ledger plugins. + * + * Note that this list does not necessarily reflect the current status of a connection to any underlying ledger, but + * instead merely represents information about the current configuration (whereas the current state of the connector, + * such as the availability of a given ledger plugin due to downtime) is managed by a separate service. + * + * @return A {@link Collection} of type {@link LedgerPluginConfig} for all configured ledger plugins. + */ + Collection getLedgerPluginConfigurations(); + + /** + * Return configuration info for the ledger plugin indicated by the supplied {@code ledgerPrefix}. This interface + * returns only some typed configuration properties, with any other properties contained in {@link + * LedgerPluginConfig#getOptions()}. Implementations can write adaptor classes to access these properties in a typed + * fashion. + * + * Note that this list does not necessarily reflect the current status of a connection to any underlying ledger, but + * instead merely represents information about the current configuration (whereas the current state of the connector, + * such as the availability of a given ledger plugin due to downtime) is managed by the {@link LedgerPluginManager} + * and the plugin itself. + * + * @param ledgerPrefix An {@link InterledgerAddress} that is a ledger-prefix for a ledger that this connector has an + * account on. + * + * @return A {@link LedgerPluginConfig} for the specified ledger prefix. + * + * @throws RuntimeException if ledger-plugin configuration cannot be found or otherwise assembled. + */ + LedgerPluginConfig getLedgerPluginConfiguration(InterledgerAddress ledgerPrefix); + +} diff --git a/src/main/java/org/interledger/connector/lpi/AbstractLedgerPluginEventHandler.java b/src/main/java/org/interledger/connector/lpi/AbstractLedgerPluginEventHandler.java index 0138612..97326c4 100644 --- a/src/main/java/org/interledger/connector/lpi/AbstractLedgerPluginEventHandler.java +++ b/src/main/java/org/interledger/connector/lpi/AbstractLedgerPluginEventHandler.java @@ -1,514 +1,526 @@ -package org.interledger.connector.lpi; - -import org.interledger.InterledgerAddress; -import org.interledger.connector.ConnectorUtils; -import org.interledger.connector.repository.ImmutableTransferCorrelation; -import org.interledger.connector.repository.TransferCorrelation; -import org.interledger.connector.routing.InterledgerHop; -import org.interledger.connector.routing.PaymentRouter; -import org.interledger.connector.services.LedgerPluginManager; -import org.interledger.ilp.InterledgerPayment; -import org.interledger.ilp.InterledgerProtocolError; -import org.interledger.ilp.InterledgerProtocolError.Builder; -import org.interledger.ilp.InterledgerProtocolError.ErrorCode; -import org.interledger.plugin.lpi.ImmutableTransfer; -import org.interledger.plugin.lpi.LedgerPlugin; -import org.interledger.plugin.lpi.Transfer; -import org.interledger.plugin.lpi.TransferId; -import org.interledger.plugin.lpi.events.IncomingTransferCancelledEvent; -import org.interledger.plugin.lpi.events.IncomingTransferFulfilledEvent; -import org.interledger.plugin.lpi.events.IncomingTransferPreparedEvent; -import org.interledger.plugin.lpi.events.IncomingTransferRejectedEvent; -import org.interledger.plugin.lpi.events.LedgerPluginErrorEvent; -import org.interledger.plugin.lpi.events.OutgoingTransferCancelledEvent; -import org.interledger.plugin.lpi.events.OutgoingTransferPreparedEvent; -import org.interledger.plugin.lpi.events.OutgoingTransferRejectedEvent; -import org.interledger.plugin.lpi.exceptions.AccountNotFoundException; -import org.interledger.plugin.lpi.exceptions.DuplicateTransferIdentifier; -import org.interledger.plugin.lpi.exceptions.InsufficientBalanceException; -import org.interledger.plugin.lpi.exceptions.InvalidTransferException; -import org.interledger.plugin.lpi.exceptions.LedgerPluginException; -import org.interledger.plugin.lpi.handlers.LedgerPluginEventHandler; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigInteger; -import java.time.Instant; -import java.util.Objects; -import java.util.Optional; - -/** - * An abstract implementation of {@link LedgerPluginEventHandler} that handles events from Ledger plugins running in a - * Connector. - */ -public abstract class AbstractLedgerPluginEventHandler implements LedgerPluginEventHandler { - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - // Provided by a Connector or higher level system to seed a deterministic identifier generating - // function. - private final String deterministicIdSecret; - - private final LedgerPluginManager ledgerPluginManager; - private final PaymentRouter paymentRouter; - - public AbstractLedgerPluginEventHandler( - final String deterministicIdSecret, final LedgerPluginManager ledgerPluginManager, - final PaymentRouter paymentRouter) { - this.ledgerPluginManager = Objects.requireNonNull(ledgerPluginManager); - this.deterministicIdSecret = Objects.requireNonNull(deterministicIdSecret); - this.paymentRouter = Objects.requireNonNull(paymentRouter); - } - - @Override - public void onError(LedgerPluginErrorEvent event) { - logger.error( - "LedgerPlugin will disconnect and be removed from the LedgrePluginManager after encountering an unrecoverable Error: {}", - event.getLedgerPrefix(), event.getError()); - - // Remove ourselves from the active plugins, because something went horribly wrong... - this.getLedgerPluginManager().removeLedgerPlugin(event.getLedgerPrefix()); - } - - @Override - public void onTransferPrepared(IncomingTransferPreparedEvent event) { - logger.info("onTransferPrepared: {}", event); - - // This method works by attempting to process the incoming transfer while if at any point a non-retryable error - // is encountered, it is thrown as an InvalidTransferException, which is caught and then used to reject the - // payment on the source ledger. Otherwise, non-InvalidTransferExceptions are simply thrown and logged by the - // system, with implementations potentially choosing to implement queued notification handling that might - // allow for retries after something like a bug or other temporary condition is fixed. - - final Transfer sourceTransfer = event.getTransfer(); - try { - //this.validateIncomingPreparedTransfer(sourceTransfer); - - // The address of the ultimate receiver of this ILP Payment.... - final InterledgerPayment ilpPaymentPacket = sourceTransfer.getInterlederPaymentPacket(); - - // The address of the connector account on the underlying source ledger... - final InterledgerAddress myAddress = getLedgerPluginManager().getLedgerPluginSafe( - sourceTransfer.getTransferId(), sourceTransfer.getLedgerPrefix()) - .getConnectorAccount(); - - // Don't do anything with incoming ILP payments where this connector is the final receiver, because there is - // no "next-hop" transfer to be made. - if (ilpPaymentPacket.getDestinationAccount().startsWith(myAddress)) { - logger.warn( - "Ignoring Transfer to destination which starts with this plugin's address: " - + "thisPlugin: \"{}\" ilpPayment Destination: \"{}\"", - myAddress, ilpPaymentPacket.getDestinationAccount()); - return; - } - - // Determine the nextHop for this payment.... - final InterledgerHop nextHop = this.getPaymentRouter().determineNexHop( - sourceTransfer.getLedgerPrefix(), - sourceTransfer.getInterlederPaymentPacket(), - sourceTransfer.getAmount() - ) - // If no hop can be determined, we immediately reject the source transfer. - .orElseThrow(() -> new InvalidTransferException( - String.format("No route found from \"%s\" to \"%s\"", - myAddress, - ilpPaymentPacket.getDestinationAccount() - ), - sourceTransfer.getSourceAccount(), - sourceTransfer.getTransferId(), - InterledgerProtocolError.builder() - .errorCode(ErrorCode.F02_UNREACHABLE) - .triggeredAt(Instant.now()) - .triggeredByAddress(myAddress) - .build() - )); - - final Transfer destinationTransfer = this.buildNextHopTransfer(sourceTransfer, nextHop); - - // Specifies which source_transfer to utilize when handling future reject/fulfill events on the - // source and destination ledgers. This operation should be done before preparing the transfer - // on the destination ledger. If that prepare fails, it will likely be retried, in which case - // this call will merely overwrite itself, which is benign. - final TransferCorrelation transferCorrelation = ImmutableTransferCorrelation.builder() - .sourceTransfer(sourceTransfer) - .destinationTransfer(destinationTransfer) - .build(); - this.getLedgerPluginManager().getTransferCorrelationRepository() - .save(transferCorrelation); - - // Prepare the transfer on the destination ledger... - this.prepareDestinationTransfer(sourceTransfer, destinationTransfer); - - } catch (InvalidTransferException e) { - // The transfer was invalid for whatever reason, so we should immediately reject it. - logger.error("Rejecting Incoming Transfer: {}", e.getMessage(), e); - this.getLedgerPluginManager() - .getLedgerPluginSafe(sourceTransfer.getTransferId(), - sourceTransfer.getLedgerPrefix()) - .rejectIncomingTransfer(sourceTransfer.getTransferId(), e.getRejectionReason()); - return; - } - } - - /** - * Given a source transfer and information about the "next hop" in an Interledger payment chain, construct a new - * {@link Transfer} that can be used to complete this Interledger payment. - */ - protected Transfer buildNextHopTransfer( - final Transfer sourceTransfer, final InterledgerHop nextHop - ) { - Objects.requireNonNull(sourceTransfer); - Objects.requireNonNull(nextHop); - - // No need to verify connectivity to the destination ledger here, because this will either succeed or - // fail in the prepareDestinationTransfer call... - - // The address of the connector account on the underlying source ledger... - final InterledgerAddress myAddress = getLedgerPluginManager().getLedgerPluginSafe( - sourceTransfer.getTransferId(), sourceTransfer.getLedgerPrefix()) - .getConnectorAccount(); - - final InterledgerPayment ilpPaymentPacket = sourceTransfer.getInterlederPaymentPacket(); - - // Check if this connector can authorize the final sourceTransfer. - final InterledgerAddress nextHopCreditAccount; - final BigInteger nextHopAmount; - if (nextHop.isFinal()) { - // TODO: Account for slippage? - // Verify expectedFinalAmount ≤ actualFinalAmount - // As long as the fxSpread > slippage, the connector won't lose money. - final BigInteger slippage = BigInteger.ZERO; - final BigInteger expectedFinalAmount = ilpPaymentPacket.getDestinationAmount() - .multiply(BigInteger.ONE.subtract(slippage)); - // If the expectedFinalAmount is greater than the actual final amount, then this sourceTransfer doesn't have - // enough funds in it. - if (expectedFinalAmount.compareTo(nextHop.getFinalAmount()) > 0) { - new InvalidTransferException( - "Payment rate does not match the rate currently offered", - sourceTransfer.getLedgerPrefix(), - sourceTransfer.getTransferId(), - InterledgerProtocolError.builder() - .errorCode(ErrorCode.R01_INSUFFICIENT_SOURCE_AMOUNT) - .triggeredAt(Instant.now()) - .triggeredByAddress(myAddress) - .build() - ); - } - - // Since this is the final hop, we can merely payout to the destination account in the ILP packet. - nextHopCreditAccount = ilpPaymentPacket.getDestinationAccount(); - nextHopAmount = ilpPaymentPacket.getDestinationAmount(); - } else { - // This is not the final "hop" for this payment, so the address/amount should come from the routing table. - nextHopCreditAccount = nextHop.getDestinationLedgerCreditAccount(); - nextHopAmount = nextHop.getDestinationAmount(); - } - - // The ID for the next sourceTransfer should be deterministically generated, so that the connector doesn't send - // duplicate outgoing transfers if it receives duplicate notifications. The deterministic generation should - // ideally be impossible for a third party to predict. Otherwise an attacker might be able to squat on a - // predicted ID in order to interfere with a payment or make a connector look unreliable. In order to assure - // this, the connector may use a secret that seeds the deterministic ID generation. - final TransferId destinationTransferId = ConnectorUtils.generateTransferId( - deterministicIdSecret, sourceTransfer.getLedgerPrefix(), sourceTransfer.getTransferId() - ); - - // The "next-hop" sourceTransfer - final Transfer destinationTransfer = ImmutableTransfer.builder() - .transferId(destinationTransferId) - .ledgerPrefix(nextHop.getDestinationLedgerPrefix()) - // The "source" account for this transfer should be this connector's account on the destination leger. - .sourceAccount( - // If the source plugin is not connected at this point, then something went wrong, and an exception - // should be thrown, but ultimately this operation should simply be retried (assuming this event is - // queued). - this.getLedgerPluginManager() - .getLedgerPluginSafe(destinationTransferId, - nextHop.getDestinationLedgerPrefix()) - .getConnectorAccount() - ) - .amount(nextHopAmount) - .destinationAccount(nextHopCreditAccount) - .interlederPaymentPacket(sourceTransfer.getInterlederPaymentPacket()) - .executionCondition(sourceTransfer.getExecutionCondition()) - .cancellationCondition(sourceTransfer.getCancellationCondition()) - .expiresAt(sourceTransfer.getExpiresAt()) - // TODO: Atomic-mode "cases"? - .build(); - - return destinationTransfer; - } - - /** - * Called when an incoming transfer has been fulfilled on the underlying ledger. For standard behavior, this is a - * no-op because in general, this connector was the one that would have passed the fulfillment to that ledger plugin. - * - * @param event A {@link IncomingTransferFulfilledEvent}. - */ - @Override - public void onTransferFulfilled(IncomingTransferFulfilledEvent event) { - // No-Op. - if (logger.isDebugEnabled()) { - logger - .debug( - "Incoming Transfer intended for this Connector successfully fulfilled by this Connector: {}", - event); - } - } - - /** - * Called when an incoming transfer has expired on the underlying ledger. This event may be emitted by the - * ledger-plugin, but it might also be emitted by an Atomic-mode validator. - * - * For this implementation, this is currently a no-op. However, if this is occurring in a Universal-Mode Connector, it - * may be desirable to track this, because it _may_ have occurred whilst an outgoing transfer was waiting to be - * prepared, and/or might have been fulfilled. In that instance, this connector likely would lose money. - * - * In Atomic-Mode usage, this callback merely indicates that the source-transfer ledger cancelled the transaction. - * However, this is likely still just a no-op because either no destination transfer has yet been prepared, in which - * case nothing need happen. Or, the destination ledger will consult the same Atomic-Mode notary and likewise reject - * the destination transfer, meaning no action is necessary here. - * - * One optimization that could be made is for the Connector to respond to this method by checking to see if any - * incoming_transfer events have yet to be processed, and preemptively _not_ prepare on the destination ledger, but - * this is likely an optimization that would slow-down the happy path due to checking in the prepare handler, so the - * optimization may not be worth it. - * - * @param event A {@link IncomingTransferCancelledEvent}. - */ - @Override - public void onTransferCancelled(IncomingTransferCancelledEvent event) { - // No-Op. - if (logger.isDebugEnabled()) { - logger - .debug("Incoming Transfer intended for this Connector expired: {}", event); - } - } - - /** - * Called when an incoming transfer that was rejected by this connector has completed its rejection. - * - * @param event A {@link IncomingTransferRejectedEvent}. - */ - @Override - public void onTransferRejected(IncomingTransferRejectedEvent event) { - // No-Op. - if (logger.isDebugEnabled()) { - logger - .debug( - "Incoming Transfer intended for this Connector successfully rejected by this Connector: {}", - event); - } - } - - /** - * Called when an outgoing transfer has been prepared on the underlying, destination ledger. For standard behavior, - * this is a no-op because in general, this connector was the one that would have prepared the transfer in the first - * place, so other than logging the notification, there's nothing more to be done here. - * - * @param event A {@link OutgoingTransferPreparedEvent}. - */ - @Override - public void onTransferPrepared(final OutgoingTransferPreparedEvent event) { - Objects.requireNonNull(event); - // No-Op. - if (logger.isDebugEnabled()) { - logger - .debug("Outgoing Transfer from this Connector successfully prepared: {}", event); - } - } - - /** - * This is event is emitted if an outgoing transfer prepared by this connector is cancelled by the destination ledger - * (i.e., it timed out). - * - * @param event A {@link OutgoingTransferCancelledEvent}. - */ - @Override - public void onTransferCancelled(OutgoingTransferCancelledEvent event) { - this.rejectSourceTransferForDestination(event.getTransfer(), event.getCancellationReason()); - } - - /** - * This is event is emitted if an outgoing transfer prepared by this connector is rejected by the destination ledger. - * - * @param event A {@link OutgoingTransferCancelledEvent}. - */ - @Override - public void onTransferRejected(OutgoingTransferRejectedEvent event) { - Objects.requireNonNull(event); - this.rejectSourceTransferForDestination(event.getTransfer(), event.getRejectionReason()); - } - - //////////////////// - // Helper methods... - //////////////////// - -// @VisibleForTesting -// protected void validateIncomingPreparedTransfer(final Transfer transfer) { -// Objects.requireNonNull(transfer); -// -// // The expected ledger prefix for the incoming transfer. -// final InterledgerAddress expectedLedgerPrefix = this.ledgerPluginConfig.getLedgerPrefix(); -// -// // Ensure that the transfer's ledger-prefix matches the ledger prefix of this plugin. -// if (!expectedLedgerPrefix.equals(transfer.getLedgerPrefix())) { -// throw new InvalidTransferException( -// String.format("Unsupported Transfer Ledger Prefix \"%s\" for LedgerPlugin prefix \"%s\"!", -// transfer.getLedgerPrefix(), expectedLedgerPrefix), -// expectedLedgerPrefix, -// transfer.getTransferId(), -// InterledgerProtocolError.builder() -// .errorCode(ErrorCode.F00_BAD_REQUEST) -// // TODO: https://github.com/interledger/java-ilp-core/issues/82 -// .triggeredAt(Instant.now()) -// .triggeredByAddress(expectedLedgerPrefix) -// .build() -// ); -// } -// -// // Ensure that the destination account is this plugin's connector account. -// if (!transfer.getDestinationAccount().startsWith(expectedLedgerPrefix)) { -// throw new InvalidTransferException( -// String.format("Invalid _destination_ account: \"%s\" for LedgerPlugin: \"%s\"!", -// transfer.getSourceAccount(), expectedLedgerPrefix), -// expectedLedgerPrefix, -// transfer.getTransferId(), -// InterledgerProtocolError.builder() -// .errorCode(ErrorCode.F00_BAD_REQUEST) -// // TODO: https://github.com/interledger/java-ilp-core/issues/82 -// .triggeredAt(Instant.now()) -// .triggeredByAddress(expectedLedgerPrefix) -// .build() -// ); -// } -// -// // Ensure that the source account is correct for the ledger prefix. -// if (!transfer.getSourceAccount().startsWith(expectedLedgerPrefix)) { -// throw new InvalidTransferException( -// String.format("Invalid _source_ account: \"%s\" for LedgerPlugin: \"%s\"!", -// transfer.getSourceAccount(), expectedLedgerPrefix), -// expectedLedgerPrefix, -// transfer.getTransferId(), InterledgerProtocolError.builder() -// .errorCode(ErrorCode.F00_BAD_REQUEST) -// // TODO: https://github.com/interledger/java-ilp-core/issues/82 -// .triggeredAt(Instant.now()) -// .triggeredByAddress(expectedLedgerPrefix) -// .build() -// ); -// } -// } - - /** - * Given a prepared source transfer, and a ready to transmit outgoing transfer, prepare the destination transfer on - * the appropriate ledger plugin. - * - * If the destination transfer cannot be prepared, for whatever reason, then reject the incoming source transfer with - * an appropriate ILP error code. - */ - @VisibleForTesting - protected void prepareDestinationTransfer(final Transfer sourceTransfer, - final Transfer destinationTransfer) { - if (logger.isDebugEnabled()) { - logger.debug("About to settle payment. Source: {}; Destination Transfer: {}", - sourceTransfer, - destinationTransfer); - } - - // Before trying to settle, we should ensure that the connector is connected to both the source and destination - // via the correct ledger plugins. If resolving these fails for any reason, then this is a runtime error, and - // should not trigger any responses to the source ledger (in other words, this is like a precondition). - - final LedgerPlugin destinationLedgerPlugin = this.getLedgerPluginManager() - .getLedgerPluginSafe(destinationTransfer.getTransferId(), - sourceTransfer.getLedgerPrefix()); - - try { - destinationLedgerPlugin.sendTransfer(destinationTransfer); - } catch (LedgerPluginException lpe) { - // Map the LedgerPluginException to a proper RejectionMessage that can be sent back to the source ledger plugin. - final InterledgerProtocolError rejectionReason = this - .fromLedgerPluginException(destinationTransfer.getLedgerPrefix(), lpe); - - // If the source ledger plugin cannot be located, this is definitely a runtime exception, which can simply - // be emitted and handled by the caller of this method. However, no exception is expected, so we reject the - // source transfer on the located ledger plugin. - this.getLedgerPluginManager() - .getLedgerPluginSafe(sourceTransfer.getTransferId(), - sourceTransfer.getLedgerPrefix()) - .rejectIncomingTransfer(sourceTransfer.getTransferId(), rejectionReason); - } - } - - /** - * Map an instance of {@link LedgerPluginException} to a corresponding {@link InterledgerProtocolError} for sending - * back to a source ledger. - */ - @VisibleForTesting - protected InterledgerProtocolError fromLedgerPluginException( - final InterledgerAddress triggeringLedgerPrefix, final LedgerPluginException lpe - ) { - Objects.requireNonNull(triggeringLedgerPrefix); - - return Optional.of(lpe) - .map(exception -> { - final Builder builder = InterledgerProtocolError.builder() - .triggeredAt(Instant.now()) - .triggeredByAddress(triggeringLedgerPrefix); - if (exception instanceof DuplicateTransferIdentifier - || exception instanceof InvalidTransferException) { - builder.errorCode(ErrorCode.F00_BAD_REQUEST); - } else if (exception instanceof InsufficientBalanceException) { - builder.errorCode(ErrorCode.T04_INSUFFICIENT_LIQUIDITY); - } else if (exception instanceof AccountNotFoundException) { - builder.errorCode(ErrorCode.F02_UNREACHABLE); - } else { - builder.errorCode(ErrorCode.T01_LEDGER_UNREACHABLE); - } - return builder.build(); - }).get(); - } - - /** - * This method rejects a source transfer where there is a rejected destination transfer. - * - * @param rejectedDestinationTransfer A destination {@link Transfer} - * @param rejectionReason A {@link InterledgerProtocolError} containing information from the destination - * ledger about why that destination transfer was rejected. - */ - @VisibleForTesting - protected void rejectSourceTransferForDestination( - final Transfer rejectedDestinationTransfer, final InterledgerProtocolError rejectionReason - ) { - final TransferCorrelation transferCorrelation = this.getLedgerPluginManager() - .getTransferCorrelationRepository() - .findByDestinationTransferId(rejectedDestinationTransfer.getTransferId()) - .orElseThrow(() -> new RuntimeException(String.format( - "Unable to reject source transfer for supplied destination transfer due to missing " - + "TransferCorrelation info! " - + "DestinationTransfer: %s; rejectionReason: %s", - rejectedDestinationTransfer, rejectionReason)) - ); - - final TransferId sourceTransferId = transferCorrelation.getSourceTransfer().getTransferId(); - final InterledgerAddress sourceLedgerPrefix = transferCorrelation.getSourceTransfer() - .getLedgerPrefix(); - - final LedgerPlugin sourceLedgerPlugin = this.getLedgerPluginManager() - .getLedgerPluginSafe(sourceTransferId, sourceLedgerPrefix); - - final InterledgerProtocolError forwardedRejectionReason = InterledgerProtocolError - .withForwardedAddress(rejectionReason, sourceLedgerPlugin.getConnectorAccount()); - sourceLedgerPlugin.rejectIncomingTransfer(sourceTransferId, forwardedRejectionReason); - } - - public LedgerPluginManager getLedgerPluginManager() { - return this.ledgerPluginManager; - } - - public PaymentRouter getPaymentRouter() { - return paymentRouter; - } -} +package org.interledger.connector.lpi; + +import org.interledger.InterledgerAddress; +import org.interledger.connector.ConnectorUtils; +import org.interledger.connector.repository.ImmutableTransferCorrelation; +import org.interledger.connector.repository.TransferCorrelation; +import org.interledger.connector.routing.InterledgerHop; +import org.interledger.connector.routing.PaymentRouter; +import org.interledger.connector.services.LedgerPluginManager; +import org.interledger.ilp.InterledgerPayment; +import org.interledger.ilp.InterledgerProtocolError; +import org.interledger.ilp.InterledgerProtocolError.Builder; +import org.interledger.ilp.InterledgerProtocolError.ErrorCode; +import org.interledger.plugin.lpi.ImmutableTransfer; +import org.interledger.plugin.lpi.LedgerPlugin; +import org.interledger.plugin.lpi.Transfer; +import org.interledger.plugin.lpi.TransferId; +import org.interledger.plugin.lpi.events.IncomingTransferCancelledEvent; +import org.interledger.plugin.lpi.events.IncomingTransferFulfilledEvent; +import org.interledger.plugin.lpi.events.IncomingTransferPreparedEvent; +import org.interledger.plugin.lpi.events.IncomingTransferRejectedEvent; +import org.interledger.plugin.lpi.events.LedgerPluginErrorEvent; +import org.interledger.plugin.lpi.events.OutgoingTransferCancelledEvent; +import org.interledger.plugin.lpi.events.OutgoingTransferPreparedEvent; +import org.interledger.plugin.lpi.events.OutgoingTransferRejectedEvent; +import org.interledger.plugin.lpi.exceptions.AccountNotFoundException; +import org.interledger.plugin.lpi.exceptions.DuplicateTransferIdentifier; +import org.interledger.plugin.lpi.exceptions.InsufficientBalanceException; +import org.interledger.plugin.lpi.exceptions.InvalidTransferException; +import org.interledger.plugin.lpi.exceptions.LedgerPluginException; +import org.interledger.plugin.lpi.handlers.LedgerPluginEventHandler; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.time.Instant; +import java.util.Objects; +import java.util.Optional; + +/** + * An abstract implementation of {@link LedgerPluginEventHandler} that handles events from Ledger plugins running in a + * Connector. + */ +public abstract class AbstractLedgerPluginEventHandler implements LedgerPluginEventHandler { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + // Provided by a Connector or higher level system to seed a deterministic identifier generating + // function. + private final String deterministicIdSecret; + + private final LedgerPluginManager ledgerPluginManager; + private final PaymentRouter paymentRouter; + + public AbstractLedgerPluginEventHandler( + final String deterministicIdSecret, final LedgerPluginManager ledgerPluginManager, + final PaymentRouter paymentRouter) { + this.ledgerPluginManager = Objects.requireNonNull(ledgerPluginManager); + this.deterministicIdSecret = Objects.requireNonNull(deterministicIdSecret); + this.paymentRouter = Objects.requireNonNull(paymentRouter); + } + + @Override + public void onError(LedgerPluginErrorEvent event) { + logger.error( + "LedgerPlugin will disconnect and be removed from the LedgrePluginManager after encountering an unrecoverable Error: {}", + event.getLedgerPrefix(), event.getError()); + + // Remove ourselves from the active plugins, because something went horribly wrong... + this.getLedgerPluginManager().removeLedgerPlugin(event.getLedgerPrefix()); + } + + @Override + public void onTransferPrepared(IncomingTransferPreparedEvent event) { + logger.info("onTransferPrepared: {}", event); + + // This method works by attempting to process the incoming transfer while if at any point a non-retryable error + // is encountered, it is thrown as an InvalidTransferException, which is caught and then used to reject the + // payment on the source ledger. Otherwise, non-InvalidTransferExceptions are simply thrown and logged by the + // system, with implementations potentially choosing to implement queued notification handling that might + // allow for retries after something like a bug or other temporary condition is fixed. + + final Transfer sourceTransfer = event.getTransfer(); + try { + //this.validateIncomingPreparedTransfer(sourceTransfer); + + // The address of the ultimate receiver of this ILP Payment.... + final InterledgerPayment ilpPaymentPacket = sourceTransfer.getInterlederPaymentPacket(); + + // The address of the connector account on the underlying source ledger... + final InterledgerAddress myAddress = getLedgerPluginManager().getLedgerPluginSafe( + sourceTransfer.getTransferId(), sourceTransfer.getLedgerPrefix()) + .getConnectorAccount(); + + // Don't do anything with incoming ILP payments where this connector is the final receiver, because there is + // no "next-hop" transfer to be made. + if (ilpPaymentPacket.getDestinationAccount().startsWith(myAddress)) { + logger.warn( + "Ignoring Transfer to destination which starts with this plugin's address: " + + "thisPlugin: \"{}\" ilpPayment Destination: \"{}\"", + myAddress, ilpPaymentPacket.getDestinationAccount()); + return; + } + + // Determine the nextHop for this payment.... + final InterledgerHop nextHop = this.getPaymentRouter().determineNexHop( + sourceTransfer.getLedgerPrefix(), + sourceTransfer.getInterlederPaymentPacket(), + sourceTransfer.getAmount() + ) + // If no hop can be determined, we immediately reject the source transfer. + .orElseThrow(() -> new InvalidTransferException( + String.format("No route found from \"%s\" to \"%s\"", + myAddress, + ilpPaymentPacket.getDestinationAccount() + ), + sourceTransfer.getSourceAccount(), + sourceTransfer.getTransferId(), + InterledgerProtocolError.builder() + .errorCode(ErrorCode.F02_UNREACHABLE) + .triggeredAt(Instant.now()) + .triggeredByAddress(myAddress) + .build() + )); + + final Transfer destinationTransfer = this.buildNextHopTransfer(sourceTransfer, nextHop); + + // Specifies which source_transfer to utilize when handling future reject/fulfill events on the + // source and destination ledgers. This operation should be done before preparing the transfer + // on the destination ledger. If that prepare fails, it will likely be retried, in which case + // this call will merely overwrite itself, which is benign. + final TransferCorrelation transferCorrelation = ImmutableTransferCorrelation.builder() + .sourceTransfer(sourceTransfer) + .destinationTransfer(destinationTransfer) + .build(); + this.getLedgerPluginManager().getTransferCorrelationRepository() + .save(transferCorrelation); + + // Prepare the transfer on the destination ledger... + this.prepareDestinationTransfer(sourceTransfer, destinationTransfer); + + } catch (InvalidTransferException e) { + // The transfer was invalid for whatever reason, so we should immediately reject it. + logger.error("Rejecting Incoming Transfer: {}", e.getMessage(), e); + this.getLedgerPluginManager() + .getLedgerPluginSafe(sourceTransfer.getTransferId(), + sourceTransfer.getLedgerPrefix()) + .rejectIncomingTransfer(sourceTransfer.getTransferId(), e.getRejectionReason()); + return; + } + } + + /** + * Given a source transfer and information about the "next hop" in an Interledger payment chain, construct a new + * {@link Transfer} that can be used to complete this Interledger payment. + * @param sourceTransfer - a {@link Transfer} + * @param nextHop - an {@link InterledgerHop} + * @return {@link Transfer} + */ + protected Transfer buildNextHopTransfer( + final Transfer sourceTransfer, final InterledgerHop nextHop + ) { + Objects.requireNonNull(sourceTransfer); + Objects.requireNonNull(nextHop); + + // No need to verify connectivity to the destination ledger here, because this will either succeed or + // fail in the prepareDestinationTransfer call... + + // The address of the connector account on the underlying source ledger... + final InterledgerAddress myAddress = getLedgerPluginManager().getLedgerPluginSafe( + sourceTransfer.getTransferId(), sourceTransfer.getLedgerPrefix()) + .getConnectorAccount(); + + final InterledgerPayment ilpPaymentPacket = sourceTransfer.getInterlederPaymentPacket(); + + // Check if this connector can authorize the final sourceTransfer. + final InterledgerAddress nextHopCreditAccount; + final BigInteger nextHopAmount; + if (nextHop.isFinal()) { + // TODO: Account for slippage? + // Verify expectedFinalAmount ≤ actualFinalAmount + // As long as the fxSpread > slippage, the connector won't lose money. + final BigInteger slippage = BigInteger.ZERO; + final BigInteger expectedFinalAmount = ilpPaymentPacket.getDestinationAmount() + .multiply(BigInteger.ONE.subtract(slippage)); + // If the expectedFinalAmount is greater than the actual final amount, then this sourceTransfer doesn't have + // enough funds in it. + if (expectedFinalAmount.compareTo(nextHop.getFinalAmount()) > 0) { + new InvalidTransferException( + "Payment rate does not match the rate currently offered", + sourceTransfer.getLedgerPrefix(), + sourceTransfer.getTransferId(), + InterledgerProtocolError.builder() + .errorCode(ErrorCode.R01_INSUFFICIENT_SOURCE_AMOUNT) + .triggeredAt(Instant.now()) + .triggeredByAddress(myAddress) + .build() + ); + } + + // Since this is the final hop, we can merely payout to the destination account in the ILP packet. + nextHopCreditAccount = ilpPaymentPacket.getDestinationAccount(); + nextHopAmount = ilpPaymentPacket.getDestinationAmount(); + } else { + // This is not the final "hop" for this payment, so the address/amount should come from the routing table. + nextHopCreditAccount = nextHop.getDestinationLedgerCreditAccount(); + nextHopAmount = nextHop.getDestinationAmount(); + } + + // The ID for the next sourceTransfer should be deterministically generated, so that the connector doesn't send + // duplicate outgoing transfers if it receives duplicate notifications. The deterministic generation should + // ideally be impossible for a third party to predict. Otherwise an attacker might be able to squat on a + // predicted ID in order to interfere with a payment or make a connector look unreliable. In order to assure + // this, the connector may use a secret that seeds the deterministic ID generation. + final TransferId destinationTransferId = ConnectorUtils.generateTransferId( + deterministicIdSecret, sourceTransfer.getLedgerPrefix(), sourceTransfer.getTransferId() + ); + + // The "next-hop" sourceTransfer + final Transfer destinationTransfer = ImmutableTransfer.builder() + .transferId(destinationTransferId) + .ledgerPrefix(nextHop.getDestinationLedgerPrefix()) + // The "source" account for this transfer should be this connector's account on the destination leger. + .sourceAccount( + // If the source plugin is not connected at this point, then something went wrong, and an exception + // should be thrown, but ultimately this operation should simply be retried (assuming this event is + // queued). + this.getLedgerPluginManager() + .getLedgerPluginSafe(destinationTransferId, + nextHop.getDestinationLedgerPrefix()) + .getConnectorAccount() + ) + .amount(nextHopAmount) + .destinationAccount(nextHopCreditAccount) + .interlederPaymentPacket(sourceTransfer.getInterlederPaymentPacket()) + .executionCondition(sourceTransfer.getExecutionCondition()) + .cancellationCondition(sourceTransfer.getCancellationCondition()) + .expiresAt(sourceTransfer.getExpiresAt()) + // TODO: Atomic-mode "cases"? + .build(); + + return destinationTransfer; + } + + /** + * Called when an incoming transfer has been fulfilled on the underlying ledger. For standard behavior, this is a + * no-op because in general, this connector was the one that would have passed the fulfillment to that ledger plugin. + * + * @param event A {@link IncomingTransferFulfilledEvent}. + */ + @Override + public void onTransferFulfilled(IncomingTransferFulfilledEvent event) { + // No-Op. + if (logger.isDebugEnabled()) { + logger + .debug( + "Incoming Transfer intended for this Connector successfully fulfilled by this Connector: {}", + event); + } + } + + /** + * Called when an incoming transfer has expired on the underlying ledger. This event may be emitted by the + * ledger-plugin, but it might also be emitted by an Atomic-mode validator. + * + * For this implementation, this is currently a no-op. However, if this is occurring in a Universal-Mode Connector, it + * may be desirable to track this, because it _may_ have occurred whilst an outgoing transfer was waiting to be + * prepared, and/or might have been fulfilled. In that instance, this connector likely would lose money. + * + * In Atomic-Mode usage, this callback merely indicates that the source-transfer ledger cancelled the transaction. + * However, this is likely still just a no-op because either no destination transfer has yet been prepared, in which + * case nothing need happen. Or, the destination ledger will consult the same Atomic-Mode notary and likewise reject + * the destination transfer, meaning no action is necessary here. + * + * One optimization that could be made is for the Connector to respond to this method by checking to see if any + * incoming_transfer events have yet to be processed, and preemptively _not_ prepare on the destination ledger, but + * this is likely an optimization that would slow-down the happy path due to checking in the prepare handler, so the + * optimization may not be worth it. + * + * @param event A {@link IncomingTransferCancelledEvent}. + */ + @Override + public void onTransferCancelled(IncomingTransferCancelledEvent event) { + // No-Op. + if (logger.isDebugEnabled()) { + logger + .debug("Incoming Transfer intended for this Connector expired: {}", event); + } + } + + /** + * Called when an incoming transfer that was rejected by this connector has completed its rejection. + * + * @param event A {@link IncomingTransferRejectedEvent}. + */ + @Override + public void onTransferRejected(IncomingTransferRejectedEvent event) { + // No-Op. + if (logger.isDebugEnabled()) { + logger + .debug( + "Incoming Transfer intended for this Connector successfully rejected by this Connector: {}", + event); + } + } + + /** + * Called when an outgoing transfer has been prepared on the underlying, destination ledger. For standard behavior, + * this is a no-op because in general, this connector was the one that would have prepared the transfer in the first + * place, so other than logging the notification, there's nothing more to be done here. + * + * @param event A {@link OutgoingTransferPreparedEvent}. + */ + @Override + public void onTransferPrepared(final OutgoingTransferPreparedEvent event) { + Objects.requireNonNull(event); + // No-Op. + if (logger.isDebugEnabled()) { + logger + .debug("Outgoing Transfer from this Connector successfully prepared: {}", event); + } + } + + /** + * This is event is emitted if an outgoing transfer prepared by this connector is cancelled by the destination ledger + * (i.e., it timed out). + * + * @param event A {@link OutgoingTransferCancelledEvent}. + */ + @Override + public void onTransferCancelled(OutgoingTransferCancelledEvent event) { + this.rejectSourceTransferForDestination(event.getTransfer(), event.getCancellationReason()); + } + + /** + * This is event is emitted if an outgoing transfer prepared by this connector is rejected by the destination ledger. + * + * @param event A {@link OutgoingTransferCancelledEvent}. + */ + @Override + public void onTransferRejected(OutgoingTransferRejectedEvent event) { + Objects.requireNonNull(event); + this.rejectSourceTransferForDestination(event.getTransfer(), event.getRejectionReason()); + } + + //////////////////// + // Helper methods... + //////////////////// + +// @VisibleForTesting +// protected void validateIncomingPreparedTransfer(final Transfer transfer) { +// Objects.requireNonNull(transfer); +// +// // The expected ledger prefix for the incoming transfer. +// final InterledgerAddress expectedLedgerPrefix = this.ledgerPluginConfig.getLedgerPrefix(); +// +// // Ensure that the transfer's ledger-prefix matches the ledger prefix of this plugin. +// if (!expectedLedgerPrefix.equals(transfer.getLedgerPrefix())) { +// throw new InvalidTransferException( +// String.format("Unsupported Transfer Ledger Prefix \"%s\" for LedgerPlugin prefix \"%s\"!", +// transfer.getLedgerPrefix(), expectedLedgerPrefix), +// expectedLedgerPrefix, +// transfer.getTransferId(), +// InterledgerProtocolError.builder() +// .errorCode(ErrorCode.F00_BAD_REQUEST) +// // TODO: https://github.com/interledger/java-ilp-core/issues/82 +// .triggeredAt(Instant.now()) +// .triggeredByAddress(expectedLedgerPrefix) +// .build() +// ); +// } +// +// // Ensure that the destination account is this plugin's connector account. +// if (!transfer.getDestinationAccount().startsWith(expectedLedgerPrefix)) { +// throw new InvalidTransferException( +// String.format("Invalid _destination_ account: \"%s\" for LedgerPlugin: \"%s\"!", +// transfer.getSourceAccount(), expectedLedgerPrefix), +// expectedLedgerPrefix, +// transfer.getTransferId(), +// InterledgerProtocolError.builder() +// .errorCode(ErrorCode.F00_BAD_REQUEST) +// // TODO: https://github.com/interledger/java-ilp-core/issues/82 +// .triggeredAt(Instant.now()) +// .triggeredByAddress(expectedLedgerPrefix) +// .build() +// ); +// } +// +// // Ensure that the source account is correct for the ledger prefix. +// if (!transfer.getSourceAccount().startsWith(expectedLedgerPrefix)) { +// throw new InvalidTransferException( +// String.format("Invalid _source_ account: \"%s\" for LedgerPlugin: \"%s\"!", +// transfer.getSourceAccount(), expectedLedgerPrefix), +// expectedLedgerPrefix, +// transfer.getTransferId(), InterledgerProtocolError.builder() +// .errorCode(ErrorCode.F00_BAD_REQUEST) +// // TODO: https://github.com/interledger/java-ilp-core/issues/82 +// .triggeredAt(Instant.now()) +// .triggeredByAddress(expectedLedgerPrefix) +// .build() +// ); +// } +// } + + /** + * Given a prepared source transfer, and a ready to transmit outgoing transfer, prepare the destination transfer on + * the appropriate ledger plugin. + * + * If the destination transfer cannot be prepared, for whatever reason, then reject the incoming source transfer with + * an appropriate ILP error code. + * + * @param sourceTransfer - a source {@link Transfer} + * @param destinationTransfer - a destination {@link Transfer} + * + */ + @VisibleForTesting + protected void prepareDestinationTransfer(final Transfer sourceTransfer, + final Transfer destinationTransfer) { + if (logger.isDebugEnabled()) { + logger.debug("About to settle payment. Source: {}; Destination Transfer: {}", + sourceTransfer, + destinationTransfer); + } + + // Before trying to settle, we should ensure that the connector is connected to both the source and destination + // via the correct ledger plugins. If resolving these fails for any reason, then this is a runtime error, and + // should not trigger any responses to the source ledger (in other words, this is like a precondition). + + final LedgerPlugin destinationLedgerPlugin = this.getLedgerPluginManager() + .getLedgerPluginSafe(destinationTransfer.getTransferId(), + sourceTransfer.getLedgerPrefix()); + + try { + destinationLedgerPlugin.sendTransfer(destinationTransfer); + } catch (LedgerPluginException lpe) { + // Map the LedgerPluginException to a proper RejectionMessage that can be sent back to the source ledger plugin. + final InterledgerProtocolError rejectionReason = this + .fromLedgerPluginException(destinationTransfer.getLedgerPrefix(), lpe); + + // If the source ledger plugin cannot be located, this is definitely a runtime exception, which can simply + // be emitted and handled by the caller of this method. However, no exception is expected, so we reject the + // source transfer on the located ledger plugin. + this.getLedgerPluginManager() + .getLedgerPluginSafe(sourceTransfer.getTransferId(), + sourceTransfer.getLedgerPrefix()) + .rejectIncomingTransfer(sourceTransfer.getTransferId(), rejectionReason); + } + } + + /** + * Map an instance of {@link LedgerPluginException} to a corresponding {@link InterledgerProtocolError} for sending + * back to a source ledger. + * + * @param triggeringLedgerPrefix - a {@link InterledgerAddress} + * @param lpe - a {@link LedgerPluginException} + * + * @return {@link InterledgerProtocolError} + */ + @VisibleForTesting + protected InterledgerProtocolError fromLedgerPluginException( + final InterledgerAddress triggeringLedgerPrefix, final LedgerPluginException lpe + ) { + Objects.requireNonNull(triggeringLedgerPrefix); + + return Optional.of(lpe) + .map(exception -> { + final Builder builder = InterledgerProtocolError.builder() + .triggeredAt(Instant.now()) + .triggeredByAddress(triggeringLedgerPrefix); + if (exception instanceof DuplicateTransferIdentifier + || exception instanceof InvalidTransferException) { + builder.errorCode(ErrorCode.F00_BAD_REQUEST); + } else if (exception instanceof InsufficientBalanceException) { + builder.errorCode(ErrorCode.T04_INSUFFICIENT_LIQUIDITY); + } else if (exception instanceof AccountNotFoundException) { + builder.errorCode(ErrorCode.F02_UNREACHABLE); + } else { + builder.errorCode(ErrorCode.T01_LEDGER_UNREACHABLE); + } + return builder.build(); + }).get(); + } + + /** + * This method rejects a source transfer where there is a rejected destination transfer. + * + * @param rejectedDestinationTransfer A destination {@link Transfer} + * @param rejectionReason A {@link InterledgerProtocolError} containing information from the destination + * ledger about why that destination transfer was rejected. + */ + @VisibleForTesting + protected void rejectSourceTransferForDestination( + final Transfer rejectedDestinationTransfer, final InterledgerProtocolError rejectionReason + ) { + final TransferCorrelation transferCorrelation = this.getLedgerPluginManager() + .getTransferCorrelationRepository() + .findByDestinationTransferId(rejectedDestinationTransfer.getTransferId()) + .orElseThrow(() -> new RuntimeException(String.format( + "Unable to reject source transfer for supplied destination transfer due to missing " + + "TransferCorrelation info! " + + "DestinationTransfer: %s; rejectionReason: %s", + rejectedDestinationTransfer, rejectionReason)) + ); + + final TransferId sourceTransferId = transferCorrelation.getSourceTransfer().getTransferId(); + final InterledgerAddress sourceLedgerPrefix = transferCorrelation.getSourceTransfer() + .getLedgerPrefix(); + + final LedgerPlugin sourceLedgerPlugin = this.getLedgerPluginManager() + .getLedgerPluginSafe(sourceTransferId, sourceLedgerPrefix); + + final InterledgerProtocolError forwardedRejectionReason = InterledgerProtocolError + .withForwardedAddress(rejectionReason, sourceLedgerPlugin.getConnectorAccount()); + sourceLedgerPlugin.rejectIncomingTransfer(sourceTransferId, forwardedRejectionReason); + } + + public LedgerPluginManager getLedgerPluginManager() { + return this.ledgerPluginManager; + } + + public PaymentRouter getPaymentRouter() { + return paymentRouter; + } +} diff --git a/src/main/java/org/interledger/connector/quoting/Quote.java b/src/main/java/org/interledger/connector/quoting/Quote.java index 4d087e2..bc6a21c 100644 --- a/src/main/java/org/interledger/connector/quoting/Quote.java +++ b/src/main/java/org/interledger/connector/quoting/Quote.java @@ -1,59 +1,69 @@ -package org.interledger.connector.quoting; - -import org.interledger.connector.RouteId; -import org.interledger.connector.routing.InterledgerHop; - -import java.math.BigInteger; -import java.time.Instant; - -import javax.money.convert.ExchangeRate; - -/** - * Defines an Interledger payment quote that can be used to assemble a "next-hop" transfer on a destination ledger, in - * response to an incoming transfer on a different ledger. - */ -public interface Quote { - - /** - * The unique identifier of the liquidity path this quote is valid for. - */ - RouteId getRouteId(); - - /** - * The next "hop" that this quote exists for. - */ - InterledgerHop getNextHop(); - - /** - * The exchange-rate applied to this quote. - */ - ExchangeRate getExchangeRate(); - - /** - * The amount to be delivered to the next-hop ledger, in local-ledger units of that destination ledger. - * - * @deprecated // TODO: This interface assumes a fixed quote amount regardless of transfer amount. - */ - BigInteger getAmount(); - - /** - * The date/time when this quote expires. - */ - Instant getExpiresAt(); - -// route: fullRoute, -// hop: fullRoute.isLocal ? null : connector, -// liquidityCurve: fullRoute.curve.shiftX(shiftBy), -// sourceHoldDuration: request.destinationHoldDuration + fullRoute.minMessageWindow * 1000, -// expiresAt: Math.min(quoteExpiresAt, fullRoute.curveExpiresAt || Infinity) -// - -// appliesToPrefix = "usd-ledger." -// expiresAt = 1434412845000 -// hop = null -// liquidityCurve = LiquidityCurve -// route = Route -// sourceHoldDuration = 11001 - - -} +package org.interledger.connector.quoting; + +import org.interledger.connector.RouteId; +import org.interledger.connector.routing.InterledgerHop; + +import java.math.BigInteger; +import java.time.Instant; + +import javax.money.convert.ExchangeRate; + +/** + * Defines an Interledger payment quote that can be used to assemble a "next-hop" transfer on a destination ledger, in + * response to an incoming transfer on a different ledger. + */ +public interface Quote { + + /** + * The unique identifier of the liquidity path this quote is valid for. + * + * @return {@link RouteId} + */ + RouteId getRouteId(); + + /** + * The next "hop" that this quote exists for. + * + * @return {@link InterledgerHop} + */ + InterledgerHop getNextHop(); + + /** + * The exchange-rate applied to this quote. + * + * @return {@link ExchangeRate} + */ + ExchangeRate getExchangeRate(); + + /** + * The amount to be delivered to the next-hop ledger, in local-ledger units of that destination ledger. + * + * @deprecated // TODO: This interface assumes a fixed quote amount regardless of transfer amount. + * + * @return BigInteger + */ + BigInteger getAmount(); + + /** + * The date/time when this quote expires. + * + * @return Instant + */ + Instant getExpiresAt(); + +// route: fullRoute, +// hop: fullRoute.isLocal ? null : connector, +// liquidityCurve: fullRoute.curve.shiftX(shiftBy), +// sourceHoldDuration: request.destinationHoldDuration + fullRoute.minMessageWindow * 1000, +// expiresAt: Math.min(quoteExpiresAt, fullRoute.curveExpiresAt || Infinity) +// + +// appliesToPrefix = "usd-ledger." +// expiresAt = 1434412845000 +// hop = null +// liquidityCurve = LiquidityCurve +// route = Route +// sourceHoldDuration = 11001 + + +} diff --git a/src/main/java/org/interledger/connector/quoting/QuotingService.java b/src/main/java/org/interledger/connector/quoting/QuotingService.java index f316320..e84f769 100644 --- a/src/main/java/org/interledger/connector/quoting/QuotingService.java +++ b/src/main/java/org/interledger/connector/quoting/QuotingService.java @@ -1,47 +1,51 @@ -package org.interledger.connector.quoting; - -import org.interledger.InterledgerAddress; -import org.interledger.connector.routing.InterledgerHop; - -import java.math.BigInteger; - -/** - * A service for determining FX quotes for particular liquidity paths. - * - * A quote contains information relating to the - */ -public interface QuotingService { - - /** - * Gets a quote to deliver the specified {@code sourceAmount} to a destination ledger via Interledger. - * - * A - */ - Quote getQuoteBySourceAmount(InterledgerAddress sourceLedgerPrefix, BigInteger sourceAmount); - - - - /** - * Gets a quote to deliver the specified {@code destinationAmount} to a destination ledger via Interledger - * - * @param {InterledgerAddress} sourceLedgerPrefix - * @param {InterledgerAddress} destinationAmount - * @return {Quote} - */ - Quote getQuoteByDestinationAmount(InterledgerAddress sourceLedgerPrefix, BigInteger destinationAmount); - - //findBestPathForSourceAmount(IlpAddress sourceLedger, IlpAddress destination, BigInteger sourceAmount); - - - - //findBestPathForSourceAmount(sourceLedger, ilpPacket.account, sourceTransfer.amount) - - - - /** - * - * @return - */ - InterledgerHop findNextHop(); - -} +package org.interledger.connector.quoting; + +import org.interledger.InterledgerAddress; +import org.interledger.connector.routing.InterledgerHop; + +import java.math.BigInteger; + +/** + * A service for determining FX quotes for particular liquidity paths. + * + * A quote contains information relating to the + */ +public interface QuotingService { + + /** + * Gets a quote to deliver the specified {@code sourceAmount} to a destination ledger via Interledger. + * + * A + * + * @param sourceLedgerPrefix - an {@link InterledgerAddress} + * @param sourceAmount - a BigInteger + * @return {@link Quote} + */ + Quote getQuoteBySourceAmount(InterledgerAddress sourceLedgerPrefix, BigInteger sourceAmount); + + + + /** + * Gets a quote to deliver the specified {@code destinationAmount} to a destination ledger via Interledger + * + * @param sourceLedgerPrefix - a source {@link InterledgerAddress} + * @param destinationAmount - BigInteger + * @return {@link Quote} + */ + Quote getQuoteByDestinationAmount(InterledgerAddress sourceLedgerPrefix, BigInteger destinationAmount); + + //findBestPathForSourceAmount(IlpAddress sourceLedger, IlpAddress destination, BigInteger sourceAmount); + + + + //findBestPathForSourceAmount(sourceLedger, ilpPacket.account, sourceTransfer.amount) + + + + /** + * + * @return {@link InterledgerHop} + */ + InterledgerHop findNextHop(); + +} diff --git a/src/main/java/org/interledger/connector/routing/InterledgerHop.java b/src/main/java/org/interledger/connector/routing/InterledgerHop.java index b7c04ca..7c9b5e9 100644 --- a/src/main/java/org/interledger/connector/routing/InterledgerHop.java +++ b/src/main/java/org/interledger/connector/routing/InterledgerHop.java @@ -1,51 +1,61 @@ -package org.interledger.connector.routing; - -import org.interledger.InterledgerAddress; - -import org.immutables.value.Value; - -import java.math.BigInteger; - -/** - * Represents a "next hop" for purposes of Interledger Routing. - */ -@Value.Immutable -public interface InterledgerHop { - - /** - * The ledger prefix of the destination ledger to make the "next" local-ledger transfer in. - */ - InterledgerAddress getDestinationLedgerPrefix(); - - /** - * The ILP address of the account to credit funds to as part of the "next" local-ledger transfer (the source-account - * will always be the account of the connector at the destination ledger). - */ - InterledgerAddress getDestinationLedgerCreditAccount(); - - /** - * The amount of the next-hop transfer. - * - * headCurve.amountAt(sourceAmount).toString(), - */ - BigInteger getDestinationAmount(); - - /** - * The actual final amount of this transfer, once slippage is considered. - * - * quote.liquidityCurve.amountAt(sourceAmount).toString() - */ - BigInteger getFinalAmount(); - - /** - * Determines if this hop is servicable by a ledger that is locally-connected to the Connector wanting to know about a - * next-hop. If a hop is "final", it means that this connector is delivering this payment (as opposed to forwarding - * it). * - * - * @return {@code true} if this hop is the final hop; {@code false} if this hop is an intermediate hop. - * - * @see "https://github.com/interledger/rfcs/issues/77" - */ - boolean isFinal(); - -} +package org.interledger.connector.routing; + +import org.interledger.InterledgerAddress; + +import org.immutables.value.Value; + +import java.math.BigInteger; + +/** + * Represents a "next hop" for purposes of Interledger Routing. + */ +@Value.Immutable +public interface InterledgerHop { + + /** + * The ledger prefix of the destination ledger to make the "next" local-ledger transfer in. + * + * @return {InterledgerAddress} + */ + InterledgerAddress getDestinationLedgerPrefix(); + + /** + * The ILP address of the account to credit funds to as part of the "next" local-ledger transfer (the source-account + * will always be the account of the connector at the destination ledger). + * + * @return {@link InterledgerAddress} + */ + InterledgerAddress getDestinationLedgerCreditAccount(); + + /** + * The amount of the next-hop transfer. + * + * headCurve.amountAt(sourceAmount).toString(), + * + * @return BigInteger + */ + BigInteger getDestinationAmount(); + + /** + * The actual final amount of this transfer, once slippage is considered. + * + * quote.liquidityCurve.amountAt(sourceAmount).toString() + * + * @return BigInteger + */ + BigInteger getFinalAmount(); + + /** + * Determines if this hop is servicable by a ledger that is locally-connected to the Connector wanting to know about a + * next-hop. If a hop is "final", it means that this connector is delivering this payment (as opposed to forwarding + * it). * + * + * @return {@code true} if this hop is the final hop; {@code false} if this hop is an intermediate hop. + * + * @see "https://github.com/interledger/rfcs/issues/77" + * + * @return boolean + */ + boolean isFinal(); + +} diff --git a/src/main/java/org/interledger/connector/routing/InterledgerRoute.java b/src/main/java/org/interledger/connector/routing/InterledgerRoute.java index 2c1ea5f..c5ef532 100644 --- a/src/main/java/org/interledger/connector/routing/InterledgerRoute.java +++ b/src/main/java/org/interledger/connector/routing/InterledgerRoute.java @@ -1,93 +1,101 @@ -package org.interledger.connector.routing; - -import org.interledger.InterledgerAddress; -import org.interledger.connector.RouteId; - -import org.immutables.value.Value; -import org.immutables.value.Value.Default; - -/** - * A "route" for purposes of Interledger payments. This design is very simple for current functionality, and currently - * models a table that knows about all and looks like this: - * - *
- * ┌──────────────────────────┐│┌────────────────────────┐│┌────────┐
- * │Destination Ledger Prefix │││ Next Hop Ledger Prefix │││Num Hops│
- * └──────────────────────────┘│└────────────────────────┘│└────────┘
- * ────────────────────────────┼──────────────────────────┼──────────
- * ┌──────────────────────────┐│┌────────────────────────┐│┌────────┐
- * │        g.us.bank.        │││        g.hub1.         │││   4    │
- * ├──────────────────────────┤│├────────────────────────┤│├────────┤
- * │        g.eu.bank.        │││        g.hub2.         │││   2    │
- * └──────────────────────────┘│└────────────────────────┘│└────────┘
- * 
- * - * Eventually, this class needs to be refined so that it can properly model an appropriate route, likely taking into - * account things like LiquidityCurve and Fees (i.e., a cost function). - * - * @see "https://github.com/interledgerjs/five-bells-shared/blob/v22.0.1/schemas/Routes.json" - * @deprecated This interface will likely be replaced with a variant from java-ilp-core. - */ -@Deprecated -@Value.Immutable -public interface InterledgerRoute { - - RouteId getRouteId(); - // TODO - -// addedDuringEpoch = 0 -// additionalInfo = undefined -// curve = LiquidityCurve -// destinationAccount = "usd-ledger.mark" -// destinationLedger = "usd-ledger." -// expiresAt = undefined -// isLocal = true -// minMessageWindow = 1 -// nextLedger = "usd-ledger." -// paths = Array[1] -// sourceAccount = "eur-ledger.mark" -// sourceLedger = "eur-ledger." -// targetPrefix = "usd-ledger." - - - /** - * The ledger-prefix of the destination ledger for this route. - */ - InterledgerAddress getDestinationLedgerPrefix(); - - /** - * The ledger-prefix of the next-hop ledger that a payment should be forwarded to in order to complete an Interledger - * payment. - */ - InterledgerAddress getNextHopLedgerPrefix(); - - /** - * The number of hops that this route will require in order to reach the destination. - **/ - Integer getNumHops(); - - /** - * The target of this route. Defines the address or prefix that a particular message/packet should be routed - * towards. - */ - //InterledgerAddress getTargetInterledgerAddress(); - - /** - * The address-prefix of the ledger that will satisfy this route (in other words, a "ledger prefix"). - */ - //InterledgerAddress getLedgerPrefix(); - - /** - * The Interledger address of this connector's account on the above ledger. - */ - //InterledgerAddress getConnectorAddress(); - - /** - * Determines if this route is "local", meaning the route's target is connected to this connector (i.e., the connector - * holding the routing table). - */ - @Default - default boolean isLocal() { - return false; - } -} +package org.interledger.connector.routing; + +import org.interledger.InterledgerAddress; +import org.interledger.connector.RouteId; + +import org.immutables.value.Value; +import org.immutables.value.Value.Default; + +/** + * A "route" for purposes of Interledger payments. This design is very simple for current functionality, and currently + * models a table that knows about all and looks like this: + * + *
+ * ┌──────────────────────────┐│┌────────────────────────┐│┌────────┐
+ * │Destination Ledger Prefix │││ Next Hop Ledger Prefix │││Num Hops│
+ * └──────────────────────────┘│└────────────────────────┘│└────────┘
+ * ────────────────────────────┼──────────────────────────┼──────────
+ * ┌──────────────────────────┐│┌────────────────────────┐│┌────────┐
+ * │        g.us.bank.        │││        g.hub1.         │││   4    │
+ * ├──────────────────────────┤│├────────────────────────┤│├────────┤
+ * │        g.eu.bank.        │││        g.hub2.         │││   2    │
+ * └──────────────────────────┘│└────────────────────────┘│└────────┘
+ * 
+ * + * Eventually, this class needs to be refined so that it can properly model an appropriate route, likely taking into + * account things like LiquidityCurve and Fees (i.e., a cost function). + * + * @see "https://github.com/interledgerjs/five-bells-shared/blob/v22.0.1/schemas/Routes.json" + * @deprecated This interface will likely be replaced with a variant from java-ilp-core. + */ +@Deprecated +@Value.Immutable +public interface InterledgerRoute { + + RouteId getRouteId(); + // TODO + +// addedDuringEpoch = 0 +// additionalInfo = undefined +// curve = LiquidityCurve +// destinationAccount = "usd-ledger.mark" +// destinationLedger = "usd-ledger." +// expiresAt = undefined +// isLocal = true +// minMessageWindow = 1 +// nextLedger = "usd-ledger." +// paths = Array[1] +// sourceAccount = "eur-ledger.mark" +// sourceLedger = "eur-ledger." +// targetPrefix = "usd-ledger." + + + /** + * The ledger-prefix of the destination ledger for this route. + * + * @return {@link InterledgerAddress} + */ + InterledgerAddress getDestinationLedgerPrefix(); + + /** + * The ledger-prefix of the next-hop ledger that a payment should be forwarded to in order to complete an Interledger + * payment. + * + * @return {@link InterledgerAddress} + */ + InterledgerAddress getNextHopLedgerPrefix(); + + /** + * The number of hops that this route will require in order to reach the destination. + * + * @return Integer + **/ + Integer getNumHops(); + + /** + * The target of this route. Defines the address or prefix that a particular message/packet should be routed + * towards. + */ + //InterledgerAddress getTargetInterledgerAddress(); + + /** + * The address-prefix of the ledger that will satisfy this route (in other words, a "ledger prefix"). + */ + //InterledgerAddress getLedgerPrefix(); + + /** + * The Interledger address of this connector's account on the above ledger. + */ + //InterledgerAddress getConnectorAddress(); + + /** + * Determines if this route is "local", meaning the route's target is connected to this connector (i.e., the connector + * holding the routing table). + * + * @return boolean + */ + @Default + default boolean isLocal() { + return false; + } +} diff --git a/src/main/java/org/interledger/connector/routing/PaymentRouter.java b/src/main/java/org/interledger/connector/routing/PaymentRouter.java index ec1d524..5689da5 100644 --- a/src/main/java/org/interledger/connector/routing/PaymentRouter.java +++ b/src/main/java/org/interledger/connector/routing/PaymentRouter.java @@ -1,39 +1,41 @@ -package org.interledger.connector.routing; - -import org.interledger.InterledgerAddress; -import org.interledger.ilp.InterledgerPayment; -import org.interledger.plugin.lpi.Transfer; - -import java.math.BigInteger; -import java.util.Optional; - -/** - * An interface that determines which payment paths a particular Interledger payment should be routed along using - * hop-by-hop transfers. - */ -public interface PaymentRouter { - - /** - * Given a source {@link Transfer} with an embedded Interledger Payment (i.e., a transfer made to this connector on an - * underlying ledger, also known as an "incoming transfer), determine the "next hop" for an ILP payment according to - * the routing and quoting requirements of a particular connector implementation. - * - * At a general level, this method works as follows: - * - * Given an ILP Payment from A→C, find the next hop B on the payment path from A to C. If the next hop is the final - * one (B == C), return a hop with {@link InterledgerHop#isFinal()} set to {@code true} and information that will - * direct a connector event-handler to deliver construct a transfer on the final ledger (B/C in this case). Otherwise, - * return a transfer at B, with an embedded {@link InterledgerPayment} destined for ledger C. - * - * @param sourceLedgerPrefix The {@link InterledgerAddress} prefix of the source ledger that an incoming transfer - * (for this Connector) was received on. - * @param interlederPaymentPacket An {@link InterledgerPayment} containing information about the overall ILP payment. - * @param sourceTransferAmount The amount of the incoming source transfer. - */ - default Optional determineNexHop( - InterledgerAddress sourceLedgerPrefix, InterledgerPayment interlederPaymentPacket, - BigInteger sourceTransferAmount - ) { - return Optional.empty(); - } -} +package org.interledger.connector.routing; + +import org.interledger.InterledgerAddress; +import org.interledger.ilp.InterledgerPayment; +import org.interledger.plugin.lpi.Transfer; + +import java.math.BigInteger; +import java.util.Optional; + +/** + * An interface that determines which payment paths a particular Interledger payment should be routed along using + * hop-by-hop transfers. + */ +public interface PaymentRouter { + + /** + * Given a source {@link Transfer} with an embedded Interledger Payment (i.e., a transfer made to this connector on an + * underlying ledger, also known as an "incoming transfer), determine the "next hop" for an ILP payment according to + * the routing and quoting requirements of a particular connector implementation. + * + * At a general level, this method works as follows: + * + * Given an ILP Payment from A→C, find the next hop B on the payment path from A to C. If the next hop is the final + * one (B == C), return a hop with {@link InterledgerHop#isFinal()} set to {@code true} and information that will + * direct a connector event-handler to deliver construct a transfer on the final ledger (B/C in this case). Otherwise, + * return a transfer at B, with an embedded {@link InterledgerPayment} destined for ledger C. + * + * @param sourceLedgerPrefix The {@link InterledgerAddress} prefix of the source ledger that an incoming transfer + * (for this Connector) was received on. + * @param interlederPaymentPacket An {@link InterledgerPayment} containing information about the overall ILP payment. + * @param sourceTransferAmount The amount of the incoming source transfer. + * + * @return {@link Optional} of type {@link InterledgerHop} + */ + default Optional determineNexHop( + InterledgerAddress sourceLedgerPrefix, InterledgerPayment interlederPaymentPacket, + BigInteger sourceTransferAmount + ) { + return Optional.empty(); + } +} From 4a69b3a88ebc2df1e27a4e57eca4c208b8c9da78 Mon Sep 17 00:00:00 2001 From: Rizwan Tanoli Date: Tue, 24 Oct 2017 08:48:18 -0400 Subject: [PATCH 2/2] Removed TODO from pom.xml relating to #1 for javadocs --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index bb700cf..5dd1056 100644 --- a/pom.xml +++ b/pom.xml @@ -219,7 +219,6 @@ maven-javadoc-plugin 2.9.1 - true