This repository has been archived by the owner on Aug 18, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create various Manager classes for Connector and Ledger * Introduce Delivered vs Forwarded per interledger/rfcs#77 * Fix test harness.
- Loading branch information
Showing
30 changed files
with
1,507 additions
and
983 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
328 changes: 173 additions & 155 deletions
328
src/main/java/money/fluid/ilp/connector/DefaultConnector.java
Large diffs are not rendered by default.
Oops, something went wrong.
122 changes: 122 additions & 0 deletions
122
src/main/java/money/fluid/ilp/connector/managers/ledgers/DefaultLedgerManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package money.fluid.ilp.connector.managers.ledgers; | ||
|
||
import lombok.Getter; | ||
import money.fluid.ilp.connector.model.ids.ConnectorId; | ||
import money.fluid.ilp.connector.model.ids.IlpTransactionId; | ||
import money.fluid.ilp.ledger.model.LedgerId; | ||
import money.fluid.ilp.ledgerclient.LedgerClient; | ||
import org.interledgerx.ilp.core.DeliveredLedgerTransfer; | ||
import org.interledgerx.ilp.core.ForwardedLedgerTransfer; | ||
import org.interledgerx.ilp.core.IlpAddress; | ||
import org.interledgerx.ilp.core.LedgerTransferRejectedReason; | ||
|
||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
/** | ||
* A default implementation of {@link LedgerManager} that uses an in-memory cache to track pending transfers | ||
* and act upon them in a timely manner, as well as to allow them to be expired automatically. | ||
*/ | ||
@Getter | ||
public class DefaultLedgerManager implements LedgerManager { | ||
|
||
private final ConnectorId connectorId; | ||
private final Set<LedgerClient> ledgerClients; | ||
|
||
// The Connector needs to track the pending transfers, not the event handlers. This is because event handler1 will | ||
// receive a transfer for 1 ledger and make another transfer on another ledger. When the transfer executes, the 2nd | ||
// handler won't have the originating ledger's info, so it gets it from here. | ||
// TODO: Consider holding the entire transfer here, or loading-cache that is backed by a Database. | ||
// TODO: This should be backed by a datastore since these transfers should not be lost until they are expired or fulfilled. | ||
private final PendingTransferManager pendingTransferManager; | ||
|
||
/** | ||
* Required-args Constructor. | ||
* | ||
* @param connectorId | ||
* @param ledgerClients | ||
* @param pendingTransferManager | ||
*/ | ||
public DefaultLedgerManager( | ||
final ConnectorId connectorId, | ||
final Set<LedgerClient> ledgerClients, | ||
final PendingTransferManager pendingTransferManager | ||
) { | ||
this.connectorId = Objects.requireNonNull(connectorId); | ||
this.ledgerClients = Objects.requireNonNull(ledgerClients); | ||
this.pendingTransferManager = Objects.requireNonNull(pendingTransferManager); | ||
} | ||
|
||
@Override | ||
public void deliverPayment(final LedgerId sourceLedgerId, final DeliveredLedgerTransfer ledgerTransfer) { | ||
Objects.requireNonNull(sourceLedgerId); | ||
Objects.requireNonNull(ledgerTransfer); | ||
|
||
// Track the transfer for later... | ||
// final NoteToSelf noteToSelf = NoteToSelf.builder().originatingLedgerId( | ||
// ledgerId.getLedgerInfo().getLedgerId()).build(); | ||
|
||
// Because this is a delivery, the ledgerTransfer should have the local destination adress. | ||
this.findLedgerClientSafely(ledgerTransfer.getLedgerId()).send(ledgerTransfer); | ||
|
||
// Track the pending payment before sending to the ledger... | ||
this.pendingTransferManager.addPendingTransfer( | ||
PendingTransfer.of( | ||
ledgerTransfer, | ||
sourceLedgerId | ||
)); | ||
} | ||
|
||
@Override | ||
public void forwardPayment(final LedgerId sourceLedgerId, final ForwardedLedgerTransfer ledgerTransfer) { | ||
Objects.requireNonNull(sourceLedgerId); | ||
Objects.requireNonNull(ledgerTransfer); | ||
|
||
// Track the transfer for later... | ||
// final NoteToSelf noteToSelf = NoteToSelf.builder().originatingLedgerId( | ||
// ledgerId.getLedgerInfo().getLedgerId()).build(); | ||
|
||
// TODO: This method is specifying the ledgerId as calculated by the Connector, but perhaps it should be determining the LedgerId? | ||
this.findLedgerClientSafely(ledgerTransfer.getLedgerId()).send(ledgerTransfer); | ||
|
||
// Track the pending payment before sending to the ledger... | ||
this.pendingTransferManager.addPendingTransfer(PendingTransfer.of( | ||
ledgerTransfer, | ||
sourceLedgerId | ||
)); | ||
} | ||
|
||
@Override | ||
public void rejectPayment( | ||
final IlpTransactionId ilpTransactionId, final LedgerTransferRejectedReason ledgerTransferRejectedReason | ||
) { | ||
Objects.requireNonNull(ilpTransactionId); | ||
|
||
this.getOriginatingLedgerId(ilpTransactionId).ifPresent((ledgerId -> { | ||
this.findLedgerClientSafely(ledgerId).rejectTransfer(ilpTransactionId, ledgerTransferRejectedReason); | ||
})); | ||
// Remove the pending payment _after_ sending to the ledger... | ||
this.pendingTransferManager.removePendingTransfer(ilpTransactionId); | ||
} | ||
|
||
/** | ||
* Given an {@link IlpTransactionId}, return the {@link LedgerId} that initiated this transfer. This method is used | ||
* to fulfill and reject pending transfers. | ||
* | ||
* @param ilpTransactionId | ||
* @return | ||
*/ | ||
@Override | ||
public Optional<LedgerId> getOriginatingLedgerId(final IlpTransactionId ilpTransactionId) { | ||
Objects.requireNonNull(ilpTransactionId); | ||
return this.getPendingTransferManager() | ||
.getPendingTransfer(ilpTransactionId) | ||
.map(pendingTransfer -> pendingTransfer.getLedgerId()); | ||
} | ||
|
||
@Override | ||
public IlpAddress getConnectorAccountOnLedger(final LedgerId ledgerId) { | ||
return this.findLedgerClientSafely(ledgerId).getConnectionInfo().getLedgerAccountId(); | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
src/main/java/money/fluid/ilp/connector/managers/ledgers/InMemoryPendingTransferManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package money.fluid.ilp.connector.managers.ledgers; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import money.fluid.ilp.connector.model.ids.IlpTransactionId; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
/** | ||
* An implementation of {@link PendingTransferManager} that stores all pending transfers in-memory, and expires them | ||
* after {@code defaultExpirationSeconds} seconds. | ||
* <p> | ||
* This implementation uses a simple Guava cache to hold any pending transfers, and expires entries at the | ||
* appropriate time, but all at once (as opposed to on a per-transfer basis). | ||
* <p> | ||
* WARNING: This implementation should not be used in a production environment since it does NOT utilize a | ||
* persistent datastore to store pending transfers. This has a variety of implications, but for example, if a | ||
* Connector using this implementation were restarted, it would lose its ability to expire pending transfers, which | ||
* could cause a Connector to lose money. | ||
*/ | ||
@RequiredArgsConstructor | ||
public class InMemoryPendingTransferManager implements PendingTransferManager { | ||
|
||
//private final Logger logger = LoggerFactory.getLogger(this.getClass()); | ||
|
||
private final Map<IlpTransactionId, PendingTransfer> pendingTransfers; | ||
|
||
/** | ||
* No-args Constructor. | ||
*/ | ||
public InMemoryPendingTransferManager() { | ||
this.pendingTransfers = new HashMap<>(); | ||
} | ||
|
||
@Override | ||
public void addPendingTransfer(final PendingTransfer pendingTransfer) { | ||
this.pendingTransfers.put( | ||
pendingTransfer.getLedgerTransfer().getInterledgerPacketHeader().getIlpTransactionId(), | ||
pendingTransfer | ||
); | ||
} | ||
|
||
@Override | ||
public void removePendingTransfer(final IlpTransactionId ilpTransactionId) { | ||
this.pendingTransfers.remove(ilpTransactionId); | ||
} | ||
|
||
@Override | ||
public Optional<PendingTransfer> getPendingTransfer(IlpTransactionId ilpTransactionId) { | ||
return Optional.ofNullable(this.pendingTransfers.get(ilpTransactionId)); | ||
} | ||
} |
133 changes: 133 additions & 0 deletions
133
src/main/java/money/fluid/ilp/connector/managers/ledgers/LedgerManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package money.fluid.ilp.connector.managers.ledgers; | ||
|
||
import money.fluid.ilp.connector.model.ids.ConnectorId; | ||
import money.fluid.ilp.connector.model.ids.IlpTransactionId; | ||
import money.fluid.ilp.ledger.model.LedgerId; | ||
import money.fluid.ilp.ledgerclient.LedgerClient; | ||
import org.interledgerx.ilp.core.DeliveredLedgerTransfer; | ||
import org.interledgerx.ilp.core.ForwardedLedgerTransfer; | ||
import org.interledgerx.ilp.core.IlpAddress; | ||
import org.interledgerx.ilp.core.Ledger; | ||
import org.interledgerx.ilp.core.LedgerTransferRejectedReason; | ||
|
||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
/** | ||
* An interface that defines how a Connector can communicate with and manage any connected {@link Ledger} to facilitate | ||
* ILP transactions, including ledger transfers whose ILP destination address exists on a directly connected {@link | ||
* Ledger} (i.e., locally delivered payment), as well as transfers that must be serviced by a different connector (i.e., | ||
* a forwarded payment). | ||
* <p> | ||
* In ILP, a connector will complete its part of an interledger payment by reacting to funds received on one ledger and | ||
* sending funds to another account on a different ledger to fullfil its portion of ILP. Because this type of activity | ||
* involves holding Connector funds on multiple ledgers (i.e., tying up Connector liquidity), this service also handles | ||
* ledger transaction timeouts by tracking pending payments, reversing timed-out escrows, and executing escrows in | ||
* response to a valid fulfillment. | ||
*/ | ||
public interface LedgerManager { | ||
|
||
/** | ||
* Begin the process of initiating a transfer. This includes the following steps: | ||
* <p> | ||
* <pre> | ||
* <ol> | ||
* <li>Create a local ledger transfer, including the cryptographic condition, and authorize this transfer on | ||
* the local ledger.</li> | ||
* <li>Wait for the local ledger to put the sender's funds on hold and notify this connector that this has been | ||
* completed.</li> | ||
* <li>Receive the notification from the Ledger, and extract the ILP packet to determine if the payment should | ||
* be forwarded.</li> | ||
* | ||
* </ol> | ||
* </pre> | ||
*/ | ||
//void initiateTransfer(); | ||
|
||
/** | ||
* Get the {@link ConnectorId} for the Connector this {@link LedgerManager} is operating for. | ||
* | ||
* @return | ||
*/ | ||
ConnectorId getConnectorId(); | ||
|
||
Set<LedgerClient> getLedgerClients(); | ||
|
||
/** | ||
* Delivery occurs when the best matching routing table entry is a local ledger. This method facilitates such a | ||
* payment to the appropriate locally connected ledger. | ||
* | ||
* @param sourceLedgerId The {@link LedgerId} of the ledger that should be notified when the {@code ledgerTransfer} | ||
* is either fulfilled, rejected, or timed-out. | ||
* @param ledgerTransfer | ||
* @see "/~https://github.com/interledger/rfcs/issues/77" | ||
*/ | ||
void deliverPayment(final LedgerId sourceLedgerId, final DeliveredLedgerTransfer ledgerTransfer); | ||
|
||
/** | ||
* Forwarding occurs when the best matching (longest prefix) routing table entry names another connector. In other | ||
* words, when a connector has no direct access to the destination ledger. | ||
* | ||
* @param sourceLedgerId The {@link LedgerId} of the ledger that should be notified when the {@code ledgerTransfer} | ||
* is either fulfilled, rejected, or timed-out. | ||
* @param ledgerTransfer | ||
*/ | ||
void forwardPayment(final LedgerId sourceLedgerId, final ForwardedLedgerTransfer ledgerTransfer); | ||
|
||
/** | ||
* Rejection of a payment occurs when the routing table entry identifies an account that this ledger cannot route | ||
* to. In this case, any ILP transactions are unwound and all asset transfers are reversed. | ||
*/ | ||
void rejectPayment( | ||
final IlpTransactionId ilpTransactionId, final LedgerTransferRejectedReason ledgerTransferRejectedReason | ||
); | ||
|
||
/** | ||
* Given an {@link IlpTransactionId}, return the {@link LedgerId} that initiated the transfer. This method is used | ||
* to fulfill and reject pending transfers. | ||
* | ||
* @param ilpTransactionId | ||
* @return | ||
*/ | ||
Optional<LedgerId> getOriginatingLedgerId(final IlpTransactionId ilpTransactionId); | ||
|
||
/** | ||
* Given the specified {@link LedgerId}, find any instances of {@link LedgerClient} for which this connector is | ||
* listening to events for. In general, a Conenctor will have only a single client listening to a given ledger, but | ||
* it's possible there are more than one. | ||
* | ||
* @param ledgerId The {@link LedgerId} that uniquely identifies the {@link LedgerClient} to return. | ||
* @return | ||
*/ | ||
default Optional<LedgerClient> findLedgerClient(final LedgerId ledgerId) { | ||
Objects.requireNonNull(ledgerId); | ||
return this.getLedgerClients().stream() | ||
.filter(ledgerClient -> ledgerClient.getLedgerInfo().getLedgerId().equals(ledgerId)) | ||
.filter(ledgerClient -> ledgerClient.isConnected()) | ||
.findAny(); | ||
} | ||
|
||
/** | ||
* Helper method to get the proper {@link LedgerClient} for the indicated {@link LedgerId}, or throw an | ||
* exception if the Ledger cannot be found, is disconnected, or is otherwise not available. | ||
* | ||
* @param ledgerId | ||
* @return | ||
* @throws RuntimeException if no connected {@link LedgerClient} can be found. | ||
*/ | ||
default LedgerClient findLedgerClientSafely(final LedgerId ledgerId) { | ||
return this.findLedgerClient(ledgerId).orElseThrow( | ||
() -> new RuntimeException(String.format("No LedgerClient found for LedgerId: ", ledgerId))); | ||
} | ||
|
||
/** | ||
* For a given {@link LedgerId}, return the {@link IlpAddress} of the account that the connector should transact | ||
* with for that ledger. | ||
* | ||
* @param ledgerId | ||
*/ | ||
default IlpAddress getConnectorAccountOnLedger(final LedgerId ledgerId) { | ||
return this.findLedgerClientSafely(ledgerId).getConnectionInfo().getLedgerAccountId(); | ||
} | ||
} |
Oops, something went wrong.