createOfferCoordinator updated

This commit is contained in:
Manfred Karrer 2014-08-06 18:47:20 +02:00
parent 0fdab0d309
commit 7069fb6c4d
9 changed files with 164 additions and 96 deletions

View file

@ -641,8 +641,9 @@ public class WalletFacade
printInputs("payRegistrationFee", tx); printInputs("payRegistrationFee", tx);
} }
public String payCreateOfferFee(String offerId, FutureCallback<Transaction> callback) throws InsufficientMoneyException public Transaction createOfferFeeTx(String offerId) throws InsufficientMoneyException
{ {
log.trace("createOfferFeeTx");
Transaction tx = new Transaction(params); Transaction tx = new Transaction(params);
Coin fee = FeePolicy.CREATE_OFFER_FEE.subtract(FeePolicy.TX_FEE); Coin fee = FeePolicy.CREATE_OFFER_FEE.subtract(FeePolicy.TX_FEE);
log.trace("fee: " + fee.toFriendlyString()); 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) // 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.coinSelector = new AddressBasedCoinSelector(params, getAddressInfoByTradeID(offerId), true);
sendRequest.changeAddress = getAddressInfoByTradeID(offerId).getAddress(); sendRequest.changeAddress = getAddressInfoByTradeID(offerId).getAddress();
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); wallet.completeTx(sendRequest);
Futures.addCallback(sendResult.broadcastComplete, callback);
printInputs("payCreateOfferFee", tx); printInputs("payCreateOfferFee", tx);
log.debug("tx=" + tx); return tx;
return tx.getHashAsString();
} }
public void broadcastCreateOfferFeeTx(Transaction tx, FutureCallback<Transaction> callback) throws InsufficientMoneyException
{
log.trace("broadcast tx");
ListenableFuture<Transaction> future = walletAppKit.peerGroup().broadcastTransaction(tx);
Futures.addCallback(future, callback);
}
public String payTakeOfferFee(String offerId, FutureCallback<Transaction> callback) throws InsufficientMoneyException public String payTakeOfferFee(String offerId, FutureCallback<Transaction> callback) throws InsufficientMoneyException
{ {

View file

@ -232,7 +232,7 @@ public class CreateOfferController implements Initializable, ChildController, Hi
if (inputsValid()) if (inputsValid())
{ {
placeOfferButton.setDisable(true); placeOfferButton.setDisable(true);
double price = BitSquareConverter.stringToDouble(priceTextField.getText()); double price = BitSquareConverter.stringToDouble(priceTextField.getText());
Coin amount = BitSquareFormatter.parseBtcToCoin(getAmountString()); Coin amount = BitSquareFormatter.parseBtcToCoin(getAmountString());
Coin minAmount = BitSquareFormatter.parseBtcToCoin(getMinAmountString()); Coin minAmount = BitSquareFormatter.parseBtcToCoin(getMinAmountString());
@ -241,7 +241,7 @@ public class CreateOfferController implements Initializable, ChildController, Hi
price, price,
amount, amount,
minAmount, minAmount,
(transactionId) -> setupSuccessScreen(transactionId), (transaction) -> setupSuccessScreen(transaction.getHashAsString()),
errorMessage -> { errorMessage -> {
Popups.openErrorPopup("An error occurred", errorMessage); Popups.openErrorPopup("An error occurred", errorMessage);
placeOfferButton.setDisable(false); placeOfferButton.setDisable(false);

View file

@ -13,7 +13,7 @@ import io.bitsquare.msg.listeners.TakeOfferRequestListener;
import io.bitsquare.settings.Settings; import io.bitsquare.settings.Settings;
import io.bitsquare.storage.Persistence; import io.bitsquare.storage.Persistence;
import io.bitsquare.trade.handlers.ErrorMessageHandler; 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.TradeMessage;
import io.bitsquare.trade.protocol.createoffer.CreateOfferCoordinator; import io.bitsquare.trade.protocol.createoffer.CreateOfferCoordinator;
import io.bitsquare.trade.protocol.offerer.*; import io.bitsquare.trade.protocol.offerer.*;
@ -131,7 +131,7 @@ public class TradeManager
double price, double price,
Coin amount, Coin amount,
Coin minAmount, Coin minAmount,
PublishTransactionResultHandler resultHandler, TransactionResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) ErrorMessageHandler errorMessageHandler)
{ {
@ -155,30 +155,31 @@ public class TradeManager
} }
else else
{ {
CreateOfferCoordinator createOfferCoordinator = new CreateOfferCoordinator(persistence,
offer,
walletFacade,
messageFacade,
(transactionId) -> {
try
{
addOffer(offer);
offer.setOfferFeePaymentTxID(transactionId.getHashAsString());
createOfferCoordinatorMap.remove(offer.getId());
resultHandler.onResult(transactionId);
CreateOfferCoordinator createOfferCoordinator = new CreateOfferCoordinator(offer, walletFacade, messageFacade); } 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); createOfferCoordinatorMap.put(offer.getId(), createOfferCoordinator);
createOfferCoordinator.start( 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());
});
} }
} }

View file

@ -1,6 +0,0 @@
package io.bitsquare.trade.handlers;
public interface PublishTransactionResultHandler
{
void onResult(String transactionId);
}

View file

@ -0,0 +1,8 @@
package io.bitsquare.trade.handlers;
import com.google.bitcoin.core.Transaction;
public interface TransactionResultHandler
{
void onResult(Transaction transaction);
}

View file

@ -1,83 +1,126 @@
package io.bitsquare.trade.protocol.createoffer; package io.bitsquare.trade.protocol.createoffer;
import com.google.bitcoin.core.Transaction;
import io.bitsquare.btc.WalletFacade; import io.bitsquare.btc.WalletFacade;
import io.bitsquare.msg.MessageFacade; import io.bitsquare.msg.MessageFacade;
import io.bitsquare.storage.Persistence;
import io.bitsquare.trade.Offer; import io.bitsquare.trade.Offer;
import io.bitsquare.trade.handlers.FaultHandler; import io.bitsquare.trade.handlers.FaultHandler;
import io.bitsquare.trade.handlers.PublishTransactionResultHandler; import io.bitsquare.trade.handlers.TransactionResultHandler;
import io.bitsquare.trade.protocol.createoffer.tasks.PayOfferFee; 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.PublishOfferToDHT;
import io.bitsquare.trade.protocol.createoffer.tasks.ValidateOffer; import io.bitsquare.trade.protocol.createoffer.tasks.ValidateOffer;
import java.io.Serializable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Responsible for coordinating tasks involved in the create offer process. * 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 //TODO recover policy, timer
public class CreateOfferCoordinator public class CreateOfferCoordinator
{ {
public enum State public enum State
{ {
INIT, INITED,
OFFER_FEE_PAID, STARTED,
VALIDATED,
OFFER_FEE_TX_CREATED,
OFFER_FEE_BROAD_CASTED,
OFFER_PUBLISHED_TO_DHT 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 static final Logger log = LoggerFactory.getLogger(CreateOfferCoordinator.class);
private final Offer offer; private final Offer offer;
private final WalletFacade walletFacade; private final WalletFacade walletFacade;
private final MessageFacade messageFacade; private final MessageFacade messageFacade;
private PublishTransactionResultHandler resultHandler; private final TransactionResultHandler resultHandler;
private FaultHandler faultHandler; 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 // for recovery from model
private String transactionId; public CreateOfferCoordinator(Offer offer, WalletFacade walletFacade, MessageFacade messageFacade, TransactionResultHandler resultHandler, FaultHandler faultHandler, Model model)
public CreateOfferCoordinator(Offer offer, WalletFacade walletFacade, MessageFacade messageFacade)
{ {
this.offer = offer; this.offer = offer;
this.walletFacade = walletFacade; this.walletFacade = walletFacade;
this.messageFacade = messageFacade; this.messageFacade = messageFacade;
}
public void start(PublishTransactionResultHandler resultHandler, FaultHandler faultHandler)
{
this.resultHandler = resultHandler; this.resultHandler = resultHandler;
this.faultHandler = faultHandler; 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); ValidateOffer.run(this::onOfferValidated, this::onFailed, offer);
} }
private void onOfferValidated() 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); PublishOfferToDHT.run(this::onOfferPublishedToDHT, this::onFailed, messageFacade, offer);
} }
private void onOfferPublishedToDHT() 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) private void onFailed(String message, Throwable throwable)
{ {
//TODO recover policy, timer //TODO recover policy, timer
faultHandler.onFault(message, throwable); faultHandler.onFault(message, throwable);
} }
@ -86,38 +129,31 @@ public class CreateOfferCoordinator
// Recovery // Recovery
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void recover(State lastState, String transactionId, PublishTransactionResultHandler resultHandler, FaultHandler faultHandler) public void recover()
{ {
this.transactionId = transactionId; switch (model.getState())
this.resultHandler = resultHandler;
this.faultHandler = faultHandler;
switch (lastState)
{ {
case INIT: case INITED:
PayOfferFee.run(this::onOfferFeePaid, this::onFailed, walletFacade, offer); case STARTED:
// no need for recover, just call start
break; break;
case OFFER_FEE_PAID: case VALIDATED:
PublishOfferToDHT.run(this::onOfferPublishedToDHT, this::onFailed, messageFacade, offer); onOfferValidated();
break;
case OFFER_FEE_TX_CREATED:
onOfferFeeTxCreated(model.transaction);
break;
case OFFER_FEE_BROAD_CASTED:
onOfferFeeTxBroadCasted();
break; break;
case OFFER_PUBLISHED_TO_DHT: case OFFER_PUBLISHED_TO_DHT:
// should be impossible // should be impossible
resultHandler.onResult(transactionId); onOfferPublishedToDHT();
break;
default:
log.error("Must not happen");
break; break;
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Getters for persisting state
///////////////////////////////////////////////////////////////////////////////////////////
public String getTransactionId()
{
return transactionId;
}
public State getState()
{
return state;
}
} }

View file

@ -4,22 +4,21 @@ import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.bitcoin.core.Transaction; import com.google.bitcoin.core.Transaction;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.btc.WalletFacade; import io.bitsquare.btc.WalletFacade;
import io.bitsquare.trade.Offer;
import io.bitsquare.trade.handlers.FaultHandler; 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.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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 try
{ {
walletFacade.payCreateOfferFee(offer.getId(), new FutureCallback<Transaction>() walletFacade.broadcastCreateOfferFeeTx(tx, new FutureCallback<Transaction>()
{ {
@Override @Override
public void onSuccess(@javax.annotation.Nullable Transaction transaction) public void onSuccess(@javax.annotation.Nullable Transaction transaction)
@ -27,10 +26,9 @@ public class PayOfferFee
log.info("sendResult onSuccess:" + transaction); log.info("sendResult onSuccess:" + transaction);
if (transaction != null) if (transaction != null)
{ {
offer.setOfferFeePaymentTxID(transaction.getHashAsString());
try try
{ {
publishTransactionResultHandler.onResult(transaction.getHashAsString()); resultHandler.onResult();
} catch (Exception e) } catch (Exception e)
{ {
faultHandler.onFault("Offer fee payment failed.", e); faultHandler.onFault("Offer fee payment failed.", e);

View file

@ -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);
}
}
}

View file

@ -14,7 +14,7 @@ public class ValidateOffer
{ {
boolean isValid = offer.getAmount().isGreaterThan(offer.getAmount()); 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.")); faultHandler.onFault("Offer validation failed: Min. amount is larger than amount.", new Exception("Offer validation failed: Min. amount is larger than amount."));