Use tasks framework for place offer

This commit is contained in:
Manfred Karrer 2015-03-12 22:55:44 +01:00
parent e1a9fd479e
commit de6c293102
13 changed files with 323 additions and 187 deletions

View File

@ -35,6 +35,7 @@ import io.bitsquare.trade.protocol.offer.CheckOfferAvailabilityModel;
import io.bitsquare.trade.protocol.offer.CheckOfferAvailabilityProtocol;
import io.bitsquare.trade.protocol.offer.messages.ReportOfferAvailabilityMessage;
import io.bitsquare.trade.protocol.offer.messages.RequestIsOfferAvailableMessage;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererModel;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererProtocol;
@ -170,11 +171,10 @@ public class TradeManager {
accountSettings.getAcceptedCountries(),
accountSettings.getAcceptedLanguageLocales());
PlaceOfferModel model = new PlaceOfferModel(offer, walletService, offerBookService);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
offer,
walletService,
offerBookService,
model,
(transaction) -> {
OpenOffer openOffer = createOpenOffer(offer);
createOffererAsBuyerProtocol(openOffer);

View File

@ -0,0 +1,67 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.placeoffer;
import io.bitsquare.btc.WalletService;
import io.bitsquare.offer.Offer;
import io.bitsquare.offer.OfferBookService;
import io.bitsquare.util.tasks.SharedModel;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PlaceOfferModel extends SharedModel {
private static final Logger log = LoggerFactory.getLogger(PlaceOfferModel.class);
private final Offer offer;
private final WalletService walletService;
private final OfferBookService offerBookService;
private Transaction transaction;
public PlaceOfferModel(Offer offer,
WalletService walletService,
OfferBookService offerBookService) {
this.offer = offer;
this.walletService = walletService;
this.offerBookService = offerBookService;
}
// getter/setter
public Offer getOffer() {
return offer;
}
public WalletService getWalletService() {
return walletService;
}
public OfferBookService getOfferBookService() {
return offerBookService;
}
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
}
public Transaction getTransaction() {
return transaction;
}
}

View File

@ -17,130 +17,58 @@
package io.bitsquare.trade.protocol.placeoffer;
import io.bitsquare.btc.WalletService;
import io.bitsquare.offer.Offer;
import io.bitsquare.offer.OfferBookService;
import io.bitsquare.trade.handlers.TransactionResultHandler;
import io.bitsquare.trade.protocol.placeoffer.tasks.AddOffer;
import io.bitsquare.trade.protocol.placeoffer.tasks.BroadcastCreateOfferFeeTx;
import io.bitsquare.trade.protocol.placeoffer.tasks.CreateOfferFeeTx;
import io.bitsquare.trade.protocol.placeoffer.tasks.ValidateOffer;
import io.bitsquare.util.handlers.FaultHandler;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import io.bitsquare.util.tasks.TaskRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Responsible for coordinating tasks involved in the create offer process.
* Executed on UI thread (single threaded)
*/
public class PlaceOfferProtocol {
private static final Logger log = LoggerFactory.getLogger(PlaceOfferProtocol.class);
private final Offer offer;
private final WalletService walletService;
private final TransactionResultHandler resultHandler;
private final FaultHandler faultHandler;
private final OfferBookService offerBookService;
private int repeatAddOfferCallCounter = 0;
private final PlaceOfferModel model;
private TransactionResultHandler resultHandler;
private FaultHandler faultHandle;
public PlaceOfferProtocol(Offer offer, WalletService walletService, OfferBookService offerBookService, TransactionResultHandler resultHandler,
FaultHandler faultHandler) {
this.offer = offer;
this.walletService = walletService;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public PlaceOfferProtocol(PlaceOfferModel model,
TransactionResultHandler resultHandler,
FaultHandler faultHandle) {
this.model = model;
this.resultHandler = resultHandler;
this.faultHandler = faultHandler;
this.offerBookService = offerBookService;
this.faultHandle = faultHandle;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Called from UI
///////////////////////////////////////////////////////////////////////////////////////////
public void placeOffer() {
try {
validateOffer();
Transaction transaction = createOfferFeeTx();
TransactionResultHandler resultHandler1 = transaction1 -> addOffer(transaction1);
FaultHandler faultHandler1 = (message, throwable) -> faultHandler.handleFault(message, throwable);
broadcastCreateOfferFeeTx(transaction, resultHandler1, faultHandler1);
} catch (Throwable t) {
// handled in specific methods
}
}
// 1. Validate offer data
// Sync
// In case of an error: No rollback activity needed
void validateOffer() throws Exception {
try {
offer.validate();
} catch (Exception ex) {
faultHandler.handleFault("Offer validation failed", ex);
throw ex;
}
}
// 2. createOfferFeeTx
// Sync
// In case of an error: No rollback activity needed
Transaction createOfferFeeTx() throws Exception {
try {
return walletService.createOfferFeeTx(offer.getId());
} catch (InsufficientMoneyException ex) {
faultHandler.handleFault(
"Offer fee payment failed because there is insufficient money in the trade wallet", ex);
throw ex;
} catch (Throwable t) {
faultHandler.handleFault("Offer fee payment failed because of an exception occurred", t);
throw t;
}
}
// 3. broadcastCreateOfferFeeTx
// Async
// In case of an error: Not sure if there can be an inconsistent state in failure case. Assuming not but need to check further.
void broadcastCreateOfferFeeTx(Transaction transaction, TransactionResultHandler resultHandler1, FaultHandler faultHandler1) throws Exception {
try {
walletService.broadcastCreateOfferFeeTx(transaction, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString());
if (transaction == null) {
Exception ex = new Exception("Broadcast of offer fee payment failed because transaction = null.");
faultHandler.handleFault("Broadcast of offer fee payment failed.", ex);
}
resultHandler1.handleResult(transaction);
}
@Override
public void onFailure(Throwable t) {
faultHandler1.handleFault("Broadcast of offer fee payment failed with an exception.", t);
}
});
} catch (Throwable t) {
faultHandler1.handleFault("Broadcast of offer fee payment failed with an exception.", t);
throw t;
}
}
// 4. addOffer
// Async
// In case of an error: Try again, afterwards give up.
void addOffer(Transaction transaction) {
// need to write data before storage, otherwise hash is different when removing offer!
offer.setOfferFeePaymentTxID(transaction.getHashAsString());
offerBookService.addOffer(offer,
TaskRunner<PlaceOfferModel> sequence = new TaskRunner<>(model,
() -> {
resultHandler.handleResult(transaction);
log.debug("sequence at handleRequestTakeOfferMessage completed");
resultHandler.handleResult(model.getTransaction());
},
(message, throwable) -> {
repeatAddOfferCallCounter++;
if (repeatAddOfferCallCounter > 1) {
faultHandler.handleFault(message, throwable);
}
else {
addOffer(transaction);
}
});
log.error(message);
faultHandle.handleFault(message, throwable);
}
);
sequence.addTasks(
ValidateOffer.class,
CreateOfferFeeTx.class,
BroadcastCreateOfferFeeTx.class,
AddOffer.class
);
sequence.run();
}
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.placeoffer.tasks;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.util.tasks.Task;
import io.bitsquare.util.tasks.TaskRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddOffer extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(AddOffer.class);
public AddOffer(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
// need to write data before storage, otherwise hash is different when removing offer!
model.getOffer().setOfferFeePaymentTxID(model.getTransaction().getHashAsString());
model.getOfferBookService().addOffer(model.getOffer(),
() -> {
complete();
},
(message, throwable) -> {
failed(message, throwable);
});
}
}

View File

@ -0,0 +1,60 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.placeoffer.tasks;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.util.tasks.Task;
import io.bitsquare.util.tasks.TaskRunner;
import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(BroadcastCreateOfferFeeTx.class);
public BroadcastCreateOfferFeeTx(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
try {
model.getWalletService().broadcastCreateOfferFeeTx(model.getTransaction(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString());
if (transaction == null)
failed("Broadcast of offer fee payment failed because transaction = null.");
else
complete();
}
@Override
public void onFailure(Throwable t) {
failed(t);
}
});
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -0,0 +1,47 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.placeoffer.tasks;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.util.tasks.Task;
import io.bitsquare.util.tasks.TaskRunner;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CreateOfferFeeTx extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateOfferFeeTx.class);
public CreateOfferFeeTx(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
try {
Transaction transaction = model.getWalletService().createOfferFeeTx(model.getOffer().getId());
model.setTransaction(transaction);
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -0,0 +1,44 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.placeoffer.tasks;
import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel;
import io.bitsquare.util.tasks.Task;
import io.bitsquare.util.tasks.TaskRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ValidateOffer extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(ValidateOffer.class);
public ValidateOffer(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model);
}
@Override
protected void run() {
try {
model.getOffer().validate();
complete();
} catch (Exception e) {
failed(e);
}
}
}

View File

@ -23,7 +23,7 @@ import org.bitcoinj.core.Coin;
import java.io.Serializable;
public class BankTransferInitedMessage implements Serializable, TradeMessage {
public class BankTransferStartedMessage implements Serializable, TradeMessage {
private static final long serialVersionUID = -3479634129543632523L;
private final String tradeId;
@ -34,13 +34,13 @@ public class BankTransferInitedMessage implements Serializable, TradeMessage {
private final Coin takerPaybackAmount;
private final String offererPayoutAddress;
public BankTransferInitedMessage(String tradeId,
String depositTxAsHex,
String offererSignatureR,
String offererSignatureS,
Coin offererPaybackAmount,
Coin takerPaybackAmount,
String offererPayoutAddress) {
public BankTransferStartedMessage(String tradeId,
String depositTxAsHex,
String offererSignatureR,
String offererSignatureS,
Coin offererPaybackAmount,
Coin takerPaybackAmount,
String offererPayoutAddress) {
this.tradeId = tradeId;
this.depositTxAsHex = depositTxAsHex;
this.offererSignatureR = offererSignatureR;

View File

@ -19,7 +19,7 @@ package io.bitsquare.trade.protocol.trade.offerer.tasks;
import io.bitsquare.trade.listeners.SendMessageListener;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererModel;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferInitedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferStartedMessage;
import io.bitsquare.util.tasks.Task;
import io.bitsquare.util.tasks.TaskRunner;
@ -35,7 +35,7 @@ public class SendBankTransferInitedMessage extends Task<BuyerAsOffererModel> {
@Override
protected void run() {
BankTransferInitedMessage tradeMessage = new BankTransferInitedMessage(
BankTransferStartedMessage tradeMessage = new BankTransferStartedMessage(
model.getTrade().getId(),
model.getDepositTxAsHex(),
model.getOffererSignatureR(),

View File

@ -1,50 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.trade.shared.tasks;
import io.bitsquare.bank.BankAccount;
import io.bitsquare.btc.BlockChainService;
import io.bitsquare.util.handlers.ExceptionHandler;
import io.bitsquare.util.handlers.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VerifyPeerAccount {
private static final Logger log = LoggerFactory.getLogger(VerifyPeerAccount.class);
public static void run(ResultHandler resultHandler, ExceptionHandler exceptionHandler,
BlockChainService blockChainService, String peersAccountId, BankAccount peersBankAccount) {
//TODO mocked yet
if (blockChainService.verifyAccountRegistration()) {
if (blockChainService.isAccountBlackListed(peersAccountId, peersBankAccount)) {
log.error("Taker is blacklisted");
exceptionHandler.handleException(new Exception("Taker is blacklisted"));
}
else {
resultHandler.handleResult();
}
}
else {
log.error("Account registration validation for peer faultHandler.onFault.");
exceptionHandler.handleException(new Exception("Account registration validation for peer faultHandler" +
".onFault."));
}
}
}

View File

@ -21,7 +21,7 @@ import io.bitsquare.network.Message;
import io.bitsquare.network.Peer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.TradeMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferInitedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.RespondToTakeOfferRequestMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.TakerDepositPaymentRequestMessage;
@ -108,8 +108,8 @@ public class SellerAsTakerProtocol {
else if (tradeMessage instanceof DepositTxPublishedMessage) {
handleDepositTxPublishedMessage((DepositTxPublishedMessage) tradeMessage);
}
else if (tradeMessage instanceof BankTransferInitedMessage) {
handleBankTransferInitedMessage((BankTransferInitedMessage) tradeMessage);
else if (tradeMessage instanceof BankTransferStartedMessage) {
handleBankTransferInitedMessage((BankTransferStartedMessage) tradeMessage);
}
else {
log.error("Incoming message not supported. " + tradeMessage);
@ -175,7 +175,7 @@ public class SellerAsTakerProtocol {
sequence.run();
}
private void handleBankTransferInitedMessage(BankTransferInitedMessage tradeMessage) {
private void handleBankTransferInitedMessage(BankTransferStartedMessage tradeMessage) {
model.setTradeMessage(tradeMessage);
SellerAsTakerTaskRunner<SellerAsTakerModel> sequence = new SellerAsTakerTaskRunner<>(model,

View File

@ -17,7 +17,7 @@
package io.bitsquare.trade.protocol.trade.taker.tasks;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferInitedMessage;
import io.bitsquare.trade.protocol.trade.offerer.messages.BankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerModel;
import io.bitsquare.util.tasks.Task;
import io.bitsquare.util.tasks.TaskRunner;
@ -38,7 +38,7 @@ public class ProcessBankTransferInitedMessage extends Task<SellerAsTakerModel> {
protected void run() {
try {
checkTradeId(model.getTrade().getId(), model.getTradeMessage());
BankTransferInitedMessage message = (BankTransferInitedMessage) model.getTradeMessage();
BankTransferStartedMessage message = (BankTransferStartedMessage) model.getTradeMessage();
model.setDepositTxAsHex(nonEmptyStringOf(message.getDepositTxAsHex()));
model.setOffererSignatureR(nonEmptyStringOf(message.getOffererSignatureR()));

View File

@ -35,15 +35,12 @@ import io.bitsquare.offer.OfferBookService;
import io.bitsquare.offer.tomp2p.TomP2POfferBookService;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.TradeMessageService;
import io.bitsquare.trade.handlers.TransactionResultHandler;
import io.bitsquare.trade.tomp2p.TomP2PTradeMessageService;
import io.bitsquare.user.User;
import io.bitsquare.util.DSAKeyUtil;
import io.bitsquare.util.handlers.FaultHandler;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.utils.Threading;
import java.io.File;
@ -51,21 +48,17 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Currency;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import static org.junit.Assert.*;
/**
* That test is ignored for automated testing as it needs custom setup.
* <p/>
@ -167,7 +160,7 @@ public class PlaceOfferProtocolTest {
bootstrappedPeerBuilder.shutDown();
}
@Test
/* @Test
public void validateOfferTest() throws InterruptedException {
try {
Offer offer = getOffer();
@ -261,7 +254,7 @@ public class PlaceOfferProtocolTest {
countDownLatch.countDown();
}
}
@Test
public void placeOfferTest() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
@ -299,7 +292,7 @@ public class PlaceOfferProtocolTest {
offerBookService,
resultHandler,
faultHandler);
}
}*/
private Offer getOffer() {
return new Offer(OFFER_ID,