From 7069fb6c4dab49e4afa856b69c13b81ca5a4895d Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 6 Aug 2014 18:47:20 +0200 Subject: [PATCH] createOfferCoordinator updated --- .../java/io/bitsquare/btc/WalletFacade.java | 17 ++- .../createOffer/CreateOfferController.java | 4 +- .../java/io/bitsquare/trade/TradeManager.java | 49 +++---- .../PublishTransactionResultHandler.java | 6 - .../handlers/TransactionResultHandler.java | 8 ++ .../createoffer/CreateOfferCoordinator.java | 132 +++++++++++------- ...OfferFee.java => BroadCastOfferFeeTx.java} | 14 +- .../createoffer/tasks/CreateOfferFeeTx.java | 28 ++++ .../createoffer/tasks/ValidateOffer.java | 2 +- 9 files changed, 164 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/io/bitsquare/trade/handlers/PublishTransactionResultHandler.java create mode 100644 src/main/java/io/bitsquare/trade/handlers/TransactionResultHandler.java rename src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/{PayOfferFee.java => BroadCastOfferFeeTx.java} (73%) create mode 100644 src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java index cd5af2b06b..a493cf1621 100644 --- a/src/main/java/io/bitsquare/btc/WalletFacade.java +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -641,8 +641,9 @@ public class WalletFacade printInputs("payRegistrationFee", tx); } - public String payCreateOfferFee(String offerId, FutureCallback callback) throws InsufficientMoneyException + public Transaction createOfferFeeTx(String offerId) throws InsufficientMoneyException { + log.trace("createOfferFeeTx"); Transaction tx = new Transaction(params); Coin fee = FeePolicy.CREATE_OFFER_FEE.subtract(FeePolicy.TX_FEE); log.trace("fee: " + fee.toFriendlyString()); @@ -653,15 +654,17 @@ public class WalletFacade // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) sendRequest.coinSelector = new AddressBasedCoinSelector(params, getAddressInfoByTradeID(offerId), true); sendRequest.changeAddress = getAddressInfoByTradeID(offerId).getAddress(); - Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); - Futures.addCallback(sendResult.broadcastComplete, callback); - + wallet.completeTx(sendRequest); printInputs("payCreateOfferFee", tx); - log.debug("tx=" + tx); - - return tx.getHashAsString(); + return tx; } + public void broadcastCreateOfferFeeTx(Transaction tx, FutureCallback callback) throws InsufficientMoneyException + { + log.trace("broadcast tx"); + ListenableFuture future = walletAppKit.peerGroup().broadcastTransaction(tx); + Futures.addCallback(future, callback); + } public String payTakeOfferFee(String offerId, FutureCallback callback) throws InsufficientMoneyException { diff --git a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java index eace1348fc..3bf5a6d20e 100644 --- a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java +++ b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java @@ -232,7 +232,7 @@ public class CreateOfferController implements Initializable, ChildController, Hi if (inputsValid()) { placeOfferButton.setDisable(true); - + double price = BitSquareConverter.stringToDouble(priceTextField.getText()); Coin amount = BitSquareFormatter.parseBtcToCoin(getAmountString()); Coin minAmount = BitSquareFormatter.parseBtcToCoin(getMinAmountString()); @@ -241,7 +241,7 @@ public class CreateOfferController implements Initializable, ChildController, Hi price, amount, minAmount, - (transactionId) -> setupSuccessScreen(transactionId), + (transaction) -> setupSuccessScreen(transaction.getHashAsString()), errorMessage -> { Popups.openErrorPopup("An error occurred", errorMessage); placeOfferButton.setDisable(false); diff --git a/src/main/java/io/bitsquare/trade/TradeManager.java b/src/main/java/io/bitsquare/trade/TradeManager.java index b6aacd9d41..df49ef5992 100644 --- a/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/src/main/java/io/bitsquare/trade/TradeManager.java @@ -13,7 +13,7 @@ import io.bitsquare.msg.listeners.TakeOfferRequestListener; import io.bitsquare.settings.Settings; import io.bitsquare.storage.Persistence; import io.bitsquare.trade.handlers.ErrorMessageHandler; -import io.bitsquare.trade.handlers.PublishTransactionResultHandler; +import io.bitsquare.trade.handlers.TransactionResultHandler; import io.bitsquare.trade.protocol.TradeMessage; import io.bitsquare.trade.protocol.createoffer.CreateOfferCoordinator; import io.bitsquare.trade.protocol.offerer.*; @@ -131,7 +131,7 @@ public class TradeManager double price, Coin amount, Coin minAmount, - PublishTransactionResultHandler resultHandler, + TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { @@ -155,30 +155,31 @@ public class TradeManager } else { + CreateOfferCoordinator createOfferCoordinator = new CreateOfferCoordinator(persistence, + offer, + walletFacade, + messageFacade, + (transactionId) -> { + try + { + addOffer(offer); + offer.setOfferFeePaymentTxID(transactionId.getHashAsString()); + createOfferCoordinatorMap.remove(offer.getId()); - - CreateOfferCoordinator createOfferCoordinator = new CreateOfferCoordinator(offer, walletFacade, messageFacade); + resultHandler.onResult(transactionId); + } catch (Exception e) + { + //TODO retry policy + errorMessageHandler.onFault("Could not save offer. Reason: " + e.getMessage()); + createOfferCoordinatorMap.remove(offer.getId()); + } + }, + (message, throwable) -> { + errorMessageHandler.onFault(message); + createOfferCoordinatorMap.remove(offer.getId()); + }); createOfferCoordinatorMap.put(offer.getId(), createOfferCoordinator); - createOfferCoordinator.start( - (transactionId) -> { - try - { - addOffer(offer); - offer.setOfferFeePaymentTxID(transactionId); - createOfferCoordinatorMap.remove(offer.getId()); - - resultHandler.onResult(transactionId); - } catch (Exception e) - { - //TODO retry policy - errorMessageHandler.onFault("Could not save offer. Reason: " + e.getMessage()); - createOfferCoordinatorMap.remove(offer.getId()); - } - }, - (message, throwable) -> { - errorMessageHandler.onFault(message); - createOfferCoordinatorMap.remove(offer.getId()); - }); + createOfferCoordinator.start(); } } diff --git a/src/main/java/io/bitsquare/trade/handlers/PublishTransactionResultHandler.java b/src/main/java/io/bitsquare/trade/handlers/PublishTransactionResultHandler.java deleted file mode 100644 index dbdc3bb6ec..0000000000 --- a/src/main/java/io/bitsquare/trade/handlers/PublishTransactionResultHandler.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.bitsquare.trade.handlers; - -public interface PublishTransactionResultHandler -{ - void onResult(String transactionId); -} diff --git a/src/main/java/io/bitsquare/trade/handlers/TransactionResultHandler.java b/src/main/java/io/bitsquare/trade/handlers/TransactionResultHandler.java new file mode 100644 index 0000000000..3e1b69f533 --- /dev/null +++ b/src/main/java/io/bitsquare/trade/handlers/TransactionResultHandler.java @@ -0,0 +1,8 @@ +package io.bitsquare.trade.handlers; + +import com.google.bitcoin.core.Transaction; + +public interface TransactionResultHandler +{ + void onResult(Transaction transaction); +} diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java index 8e5b101776..e55f1fc9ec 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/CreateOfferCoordinator.java @@ -1,83 +1,126 @@ package io.bitsquare.trade.protocol.createoffer; +import com.google.bitcoin.core.Transaction; import io.bitsquare.btc.WalletFacade; import io.bitsquare.msg.MessageFacade; +import io.bitsquare.storage.Persistence; import io.bitsquare.trade.Offer; import io.bitsquare.trade.handlers.FaultHandler; -import io.bitsquare.trade.handlers.PublishTransactionResultHandler; -import io.bitsquare.trade.protocol.createoffer.tasks.PayOfferFee; +import io.bitsquare.trade.handlers.TransactionResultHandler; +import io.bitsquare.trade.protocol.createoffer.tasks.BroadCastOfferFeeTx; +import io.bitsquare.trade.protocol.createoffer.tasks.CreateOfferFeeTx; import io.bitsquare.trade.protocol.createoffer.tasks.PublishOfferToDHT; import io.bitsquare.trade.protocol.createoffer.tasks.ValidateOffer; +import java.io.Serializable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Responsible for coordinating tasks involved in the create offer process. - * It holds the state of the current process and support recovery if possible. + * It holds the model.state of the current process and support recovery if possible. */ //TODO recover policy, timer public class CreateOfferCoordinator { public enum State { - INIT, - OFFER_FEE_PAID, + INITED, + STARTED, + VALIDATED, + OFFER_FEE_TX_CREATED, + OFFER_FEE_BROAD_CASTED, OFFER_PUBLISHED_TO_DHT } + static class Model implements Serializable + { + private static final long serialVersionUID = 3027720554200858916L; + + private Persistence persistence; + private State state; + //TODO use tx id and make Transaction transient + Transaction transaction; + + Model(Persistence persistence) + { + this.persistence = persistence; + } + + public State getState() + { + return state; + } + + public void setState(State state) + { + this.state = state; + persistence.write(this); + } + } + private static final Logger log = LoggerFactory.getLogger(CreateOfferCoordinator.class); private final Offer offer; private final WalletFacade walletFacade; private final MessageFacade messageFacade; - private PublishTransactionResultHandler resultHandler; - private FaultHandler faultHandler; + private final TransactionResultHandler resultHandler; + private final FaultHandler faultHandler; + private final Model model; - private State state; + public CreateOfferCoordinator(Persistence persistence, Offer offer, WalletFacade walletFacade, MessageFacade messageFacade, TransactionResultHandler resultHandler, FaultHandler faultHandler) + { + this(offer, walletFacade, messageFacade, resultHandler, faultHandler, new Model(persistence)); + } - // result - private String transactionId; - - public CreateOfferCoordinator(Offer offer, WalletFacade walletFacade, MessageFacade messageFacade) + // for recovery from model + public CreateOfferCoordinator(Offer offer, WalletFacade walletFacade, MessageFacade messageFacade, TransactionResultHandler resultHandler, FaultHandler faultHandler, Model model) { this.offer = offer; this.walletFacade = walletFacade; this.messageFacade = messageFacade; - } - - public void start(PublishTransactionResultHandler resultHandler, FaultHandler faultHandler) - { this.resultHandler = resultHandler; this.faultHandler = faultHandler; + this.model = model; - state = State.INIT; + model.setState(State.INITED); + } + + public void start() + { + model.setState(State.STARTED); ValidateOffer.run(this::onOfferValidated, this::onFailed, offer); } private void onOfferValidated() { - PayOfferFee.run(this::onOfferFeePaid, this::onFailed, walletFacade, offer); + model.setState(State.VALIDATED); + CreateOfferFeeTx.run(this::onOfferFeeTxCreated, this::onFailed, walletFacade, offer); } - private void onOfferFeePaid(String transactionId) + private void onOfferFeeTxCreated(Transaction transaction) { - state = State.OFFER_FEE_PAID; + model.transaction = transaction; + model.setState(State.OFFER_FEE_TX_CREATED); + BroadCastOfferFeeTx.run(this::onOfferFeeTxBroadCasted, this::onFailed, walletFacade, transaction); + } + + private void onOfferFeeTxBroadCasted() + { + model.setState(State.OFFER_FEE_BROAD_CASTED); - this.transactionId = transactionId; PublishOfferToDHT.run(this::onOfferPublishedToDHT, this::onFailed, messageFacade, offer); } private void onOfferPublishedToDHT() { - state = State.OFFER_PUBLISHED_TO_DHT; + model.setState(State.OFFER_PUBLISHED_TO_DHT); - resultHandler.onResult(transactionId); + resultHandler.onResult(model.transaction); } private void onFailed(String message, Throwable throwable) { //TODO recover policy, timer - faultHandler.onFault(message, throwable); } @@ -86,38 +129,31 @@ public class CreateOfferCoordinator // Recovery /////////////////////////////////////////////////////////////////////////////////////////// - public void recover(State lastState, String transactionId, PublishTransactionResultHandler resultHandler, FaultHandler faultHandler) + public void recover() { - this.transactionId = transactionId; - this.resultHandler = resultHandler; - this.faultHandler = faultHandler; - switch (lastState) + switch (model.getState()) { - case INIT: - PayOfferFee.run(this::onOfferFeePaid, this::onFailed, walletFacade, offer); + case INITED: + case STARTED: + // no need for recover, just call start break; - case OFFER_FEE_PAID: - PublishOfferToDHT.run(this::onOfferPublishedToDHT, this::onFailed, messageFacade, offer); + case VALIDATED: + onOfferValidated(); + break; + case OFFER_FEE_TX_CREATED: + onOfferFeeTxCreated(model.transaction); + break; + case OFFER_FEE_BROAD_CASTED: + onOfferFeeTxBroadCasted(); break; case OFFER_PUBLISHED_TO_DHT: // should be impossible - resultHandler.onResult(transactionId); + onOfferPublishedToDHT(); + break; + default: + log.error("Must not happen"); break; } } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Getters for persisting state - /////////////////////////////////////////////////////////////////////////////////////////// - - public String getTransactionId() - { - return transactionId; - } - - public State getState() - { - return state; - } } diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PayOfferFee.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/BroadCastOfferFeeTx.java similarity index 73% rename from src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PayOfferFee.java rename to src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/BroadCastOfferFeeTx.java index bc8a953d85..b6c7e1903a 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/PayOfferFee.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/BroadCastOfferFeeTx.java @@ -4,22 +4,21 @@ import com.google.bitcoin.core.InsufficientMoneyException; import com.google.bitcoin.core.Transaction; import com.google.common.util.concurrent.FutureCallback; import io.bitsquare.btc.WalletFacade; -import io.bitsquare.trade.Offer; import io.bitsquare.trade.handlers.FaultHandler; -import io.bitsquare.trade.handlers.PublishTransactionResultHandler; +import io.bitsquare.trade.handlers.ResultHandler; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class PayOfferFee +public class BroadCastOfferFeeTx { - private static final Logger log = LoggerFactory.getLogger(PayOfferFee.class); + private static final Logger log = LoggerFactory.getLogger(BroadCastOfferFeeTx.class); - public static void run(PublishTransactionResultHandler publishTransactionResultHandler, FaultHandler faultHandler, WalletFacade walletFacade, Offer offer) + public static void run(ResultHandler resultHandler, FaultHandler faultHandler, WalletFacade walletFacade, Transaction tx) { try { - walletFacade.payCreateOfferFee(offer.getId(), new FutureCallback() + walletFacade.broadcastCreateOfferFeeTx(tx, new FutureCallback() { @Override public void onSuccess(@javax.annotation.Nullable Transaction transaction) @@ -27,10 +26,9 @@ public class PayOfferFee log.info("sendResult onSuccess:" + transaction); if (transaction != null) { - offer.setOfferFeePaymentTxID(transaction.getHashAsString()); try { - publishTransactionResultHandler.onResult(transaction.getHashAsString()); + resultHandler.onResult(); } catch (Exception e) { faultHandler.onFault("Offer fee payment failed.", e); diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java new file mode 100644 index 0000000000..a78ba823b9 --- /dev/null +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/CreateOfferFeeTx.java @@ -0,0 +1,28 @@ +package io.bitsquare.trade.protocol.createoffer.tasks; + +import com.google.bitcoin.core.InsufficientMoneyException; +import io.bitsquare.btc.WalletFacade; +import io.bitsquare.trade.Offer; +import io.bitsquare.trade.handlers.TransactionResultHandler; +import io.bitsquare.trade.handlers.FaultHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CreateOfferFeeTx +{ + private static final Logger log = LoggerFactory.getLogger(CreateOfferFeeTx.class); + + public static void run(TransactionResultHandler resultHandler, FaultHandler faultHandler, WalletFacade walletFacade, Offer offer) + { + try + { + resultHandler.onResult(walletFacade.createOfferFeeTx(offer.getId())); + } catch (InsufficientMoneyException e) + { + faultHandler.onFault("Offer fee payment failed because there is insufficient money in the trade pocket. ", e); + } catch (Throwable t) + { + faultHandler.onFault("Offer fee payment failed because of an exception occurred. ", t); + } + } +} diff --git a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java index 60aa923136..a439f4288b 100644 --- a/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java +++ b/src/main/java/io/bitsquare/trade/protocol/createoffer/tasks/ValidateOffer.java @@ -14,7 +14,7 @@ public class ValidateOffer { boolean isValid = offer.getAmount().isGreaterThan(offer.getAmount()); - if (offer.getAmount().compareTo(offer.getMinAmount()) >= 0) + if (offer.getAmount().compareTo(offer.getMinAmount()) < 0) { faultHandler.onFault("Offer validation failed: Min. amount is larger than amount.", new Exception("Offer validation failed: Min. amount is larger than amount."));