Improve state handling of trade

This commit is contained in:
Manfred Karrer 2015-03-21 21:51:13 +01:00
parent 6099f36b53
commit 5768b93cdf
32 changed files with 179 additions and 249 deletions

View file

@ -375,12 +375,13 @@ public class TradeWalletService {
Futures.addCallback(broadcastComplete, callback);
}
public void takerCommitsDepositTx(Transaction depositTx) throws WalletException {
// Returns local transaction which has a different state as the serialized depositTx we get from the offerer
public Transaction takerCommitsDepositTx(Transaction depositTx) throws WalletException {
log.trace("takerCommitsDepositTx called");
log.trace("depositTx " + depositTx.toString());
// We need to recreate the tx we get a null pointer otherwise
depositTx = new Transaction(params, depositTx.bitcoinSerialize());
Transaction localDepositTx = new Transaction(params, depositTx.bitcoinSerialize());
try {
// TODO check if that is correct
@ -390,6 +391,7 @@ public class TradeWalletService {
t.printStackTrace();
throw new WalletException(t);
}
return localDepositTx;
}
public byte[] offererCreatesAndSignsPayoutTx(Transaction depositTx,

View file

@ -18,6 +18,7 @@
package io.bitsquare.gui.main.portfolio.offer;
import io.bitsquare.offer.Offer;
import io.bitsquare.trade.Trade;
/**
* We could remove that wrapper if it is not needed for additional UI only fields.
@ -26,8 +27,8 @@ class OfferListItem {
private final Offer offer;
public OfferListItem(Offer offer) {
this.offer = offer;
public OfferListItem(Trade trade) {
this.offer = trade.getOffer();
}
public Offer getOffer() {

View file

@ -23,6 +23,7 @@ import io.bitsquare.common.viewfx.model.Activatable;
import io.bitsquare.common.viewfx.model.DataModel;
import io.bitsquare.offer.Direction;
import io.bitsquare.offer.Offer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.User;
@ -44,7 +45,7 @@ class OffersDataModel implements Activatable, DataModel {
private final User user;
private final ObservableList<OfferListItem> list = FXCollections.observableArrayList();
private final MapChangeListener<String, Offer> offerMapChangeListener;
private final MapChangeListener<String, Trade> offerMapChangeListener;
@Inject
@ -63,17 +64,17 @@ class OffersDataModel implements Activatable, DataModel {
@Override
public void activate() {
list.clear();
list.addAll(tradeManager.getOpenOffers().values().stream().map(OfferListItem::new).collect(Collectors.toList()));
list.addAll(tradeManager.getOpenOfferTrades().values().stream().map(OfferListItem::new).collect(Collectors.toList()));
// we sort by date, earliest first
list.sort((o1, o2) -> o2.getOffer().getCreationDate().compareTo(o1.getOffer().getCreationDate()));
tradeManager.getOpenOffers().addListener(offerMapChangeListener);
tradeManager.getOpenOfferTrades().addListener(offerMapChangeListener);
}
@Override
public void deactivate() {
tradeManager.getOpenOffers().removeListener(offerMapChangeListener);
tradeManager.getOpenOfferTrades().removeListener(offerMapChangeListener);
}
void removeOpenOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {

View file

@ -66,11 +66,11 @@ class PendingTradesDataModel implements Activatable, DataModel {
private boolean isOfferer;
private Trade closedTrade;
private final ChangeListener<Trade.State> tradeStateChangeListener;
private final ChangeListener<Trade.ProcessState> tradeStateChangeListener;
private final MapChangeListener<String, Trade> mapChangeListener;
final StringProperty txId = new SimpleStringProperty();
final ObjectProperty<Trade.State> tradeState = new SimpleObjectProperty<>();
final ObjectProperty<Trade.ProcessState> tradeState = new SimpleObjectProperty<>();
final IntegerProperty selectedIndex = new SimpleIntegerProperty(-1);
@Inject
@ -146,9 +146,9 @@ class PendingTradesDataModel implements Activatable, DataModel {
isOfferer = getTrade().getOffer().getP2PSigPubKey().equals(user.getP2PSigPubKey());
Trade trade = getTrade();
trade.stateProperty().addListener(tradeStateChangeListener);
tradeState.set(trade.stateProperty().get());
log.trace("selectTrade trade.stateProperty().get() " + trade.stateProperty().get());
trade.processStateProperty().addListener(tradeStateChangeListener);
tradeState.set(trade.processStateProperty().get());
log.trace("selectTrade trade.stateProperty().get() " + trade.processStateProperty().get());
if (trade.getDepositTx() != null)
txId.set(trade.getDepositTx().getHashAsString());
@ -277,7 +277,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
private void cleanUpSelectedTrade() {
if (selectedItem != null) {
selectedItem.getTrade().stateProperty().removeListener(tradeStateChangeListener);
selectedItem.getTrade().processStateProperty().removeListener(tradeStateChangeListener);
}
}

View file

@ -181,6 +181,7 @@ public class PendingTradesView extends ActivatableViewAndModel<AnchorPane, Pendi
@FXML
void onPaymentStarted() {
model.fiatPaymentStarted();
confirmPaymentReceiptButton.setDisable(true);
}
@FXML

View file

@ -218,10 +218,10 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
private void updateState() {
Trade.State tradeState = dataModel.tradeState.get();
log.trace("tradeState " + tradeState);
if (tradeState != null) {
switch (tradeState) {
Trade.ProcessState tradeProcessState = dataModel.tradeState.get();
log.trace("tradeState " + tradeProcessState);
if (tradeProcessState != null) {
switch (tradeProcessState) {
case DEPOSIT_PUBLISHED:
state.set(dataModel.isOfferer() ? State.OFFERER_BUYER_WAIT_TX_CONF : State.TAKER_SELLER_WAIT_TX_CONF);
break;
@ -240,7 +240,7 @@ class PendingTradesViewModel extends ActivatableWithDataModel<PendingTradesDataM
// TODO error states not implemented yet
break;
default:
log.warn("unhandled state " + tradeState);
log.warn("unhandled state " + tradeProcessState);
break;
}
}

View file

@ -127,12 +127,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
// for irc demo
showAdvancedSettingsButton.setVisible(false);
showAdvancedSettingsButton.setManaged(false);
//TODO temp for testing
amountTextField.setText("1");
priceTextField.setText("1");
volumeTextField.setText("1");
}
@Override
@ -150,6 +144,11 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
priceColumn.setSortType((model.getDirection() == Direction.BUY) ?
TableColumn.SortType.ASCENDING : TableColumn.SortType.DESCENDING);
table.sort();
//TODO temp for testing
amountTextField.setText("1");
priceTextField.setText("300");
volumeTextField.setText("300");
}
@Override

View file

@ -208,14 +208,14 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
isTakeOfferSpinnerVisible.set(true);
dataModel.takeOffer((trade) -> {
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
trade.processStateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue);
String msg = "";
if (newValue.getErrorMessage() != null)
msg = "\nError message: " + newValue.getErrorMessage();
switch (newValue) {
case OPEN:
case INIT:
break;
case TAKE_OFFER_FEE_TX_CREATED:
break;

View file

@ -18,6 +18,7 @@
package io.bitsquare.trade;
import io.bitsquare.offer.Offer;
import io.bitsquare.p2p.Peer;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
@ -38,8 +39,16 @@ public class Trade implements Serializable {
// Enum
///////////////////////////////////////////////////////////////////////////////////////////
public static enum State {
OPEN,
public static enum LifeCycleState {
OPEN_OFFER,
CANCELED,
PENDING,
COMPLETED,
FAILED
}
public static enum ProcessState {
INIT,
TAKE_OFFER_FEE_PUBLISH_FAILED,
TAKE_OFFER_FEE_TX_CREATED,
DEPOSIT_PUBLISHED,
@ -64,7 +73,7 @@ public class Trade implements Serializable {
private final Offer offer;
private final Date date;
private State state;
private ProcessState processState;
private Coin tradeAmount;
private Contract contract;
private String contractAsJson;
@ -72,13 +81,14 @@ public class Trade implements Serializable {
private String offererContractSignature;
private Transaction depositTx;
private Transaction payoutTx;
private Peer tradingPeer;
// For changing values we use properties to get binding support in the UI (table)
// When serialized those transient properties are not instantiated, so we instantiate them in the getters at first
// access. Only use the accessor not the private field.
transient private ObjectProperty<Coin> _tradeAmount;
transient private ObjectProperty<Fiat> _tradeVolume;
transient private ObjectProperty<State> _state;
transient private ObjectProperty<ProcessState> _state;
///////////////////////////////////////////////////////////////////////////////////////////
@ -89,7 +99,7 @@ public class Trade implements Serializable {
this.offer = offer;
date = new Date();
setState(State.OPEN);
setProcessState(ProcessState.INIT);
}
@ -97,6 +107,10 @@ public class Trade implements Serializable {
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setTradingPeer(Peer tradingPeer) {
this.tradingPeer = tradingPeer;
}
public void setTakerContractSignature(String takerSignature) {
this.takerContractSignature = takerSignature;
}
@ -135,9 +149,9 @@ public class Trade implements Serializable {
this.payoutTx = tx;
}
public void setState(State state) {
this.state = state;
stateProperty().set(state);
public void setProcessState(ProcessState processState) {
this.processState = processState;
processStateProperty().set(processState);
}
@ -165,8 +179,8 @@ public class Trade implements Serializable {
return payoutTx;
}
public State getState() {
return state;
public ProcessState getProcessState() {
return processState;
}
public Coin getSecurityDeposit() {
@ -189,6 +203,11 @@ public class Trade implements Serializable {
return date;
}
public Peer getTradingPeer() {
return tradingPeer;
}
// When serialized those transient properties are not instantiated, so we need to instantiate them at first access
public ObjectProperty<Coin> tradeAmountProperty() {
if (_tradeAmount == null)
@ -204,9 +223,9 @@ public class Trade implements Serializable {
return _tradeVolume;
}
public ObjectProperty<State> stateProperty() {
public ObjectProperty<ProcessState> processStateProperty() {
if (_state == null)
_state = new SimpleObjectProperty<>(state);
_state = new SimpleObjectProperty<>(processState);
return _state;
}
@ -216,7 +235,7 @@ public class Trade implements Serializable {
return "Trade{" +
"offer=" + offer +
", date=" + date +
", state=" + state +
", state=" + processState +
", tradeAmount=" + tradeAmount +
", contract=" + contract +
", contractAsJson='" + contractAsJson + '\'' +

View file

@ -90,7 +90,7 @@ public class TradeManager {
private final Map<String, OffererAsBuyerProtocol> offererAsBuyerProtocolMap = new HashMap<>();
private final Map<String, CheckOfferAvailabilityProtocol> checkOfferAvailabilityProtocolMap = new HashMap<>();
private final ObservableMap<String, Offer> openOffers = FXCollections.observableHashMap();
private final ObservableMap<String, Trade> openOfferTrades = FXCollections.observableHashMap();
private final ObservableMap<String, Trade> pendingTrades = FXCollections.observableHashMap();
private final ObservableMap<String, Trade> closedTrades = FXCollections.observableHashMap();
private final Map<String, MailboxMessage> mailboxMessages = new HashMap<>();
@ -119,9 +119,9 @@ public class TradeManager {
this.encryptionService = encryptionService;
this.offerBookService = offerBookService;
Serializable openOffersObject = persistence.read(this, "openOffers");
if (openOffersObject instanceof Map<?, ?>) {
openOffers.putAll((Map<String, Offer>) openOffersObject);
Serializable openOfferTradesObject = persistence.read(this, "openOfferTrades");
if (openOfferTradesObject instanceof Map<?, ?>) {
openOfferTrades.putAll((Map<String, Trade>) openOfferTradesObject);
}
Serializable pendingTradesObject = persistence.read(this, "pendingTrades");
@ -143,33 +143,19 @@ public class TradeManager {
// When all services are initialized we create the protocols for our open offers and persisted not completed pendingTrades
// BuyerAcceptsOfferProtocol listens for take offer requests, so we need to instantiate it early.
public void onAllServicesInitialized() {
for (Map.Entry<String, Offer> entry : openOffers.entrySet()) {
for (Map.Entry<String, Trade> entry : openOfferTrades.entrySet()) {
createOffererAsBuyerProtocol(entry.getValue());
}
for (Map.Entry<String, Trade> entry : pendingTrades.entrySet()) {
// We continue an interrupted trade.
// TODO if the peer has changed its IP address, we need to make another findPeer request. At the moment we use the peer stored in trade to
// continue the trade, but that might fail.
Trade trade = entry.getValue();
if (trade.getState() == Trade.State.FAULT) {
closeTrade(trade);
if (isMyOffer(trade.getOffer())) {
createOffererAsBuyerProtocol(trade);
}
else {
Offer offer = trade.getOffer();
if (isMyOffer(offer)) {
createOffererAsBuyerProtocol(offer);
}
else {
CheckOfferAvailabilityModel model = new CheckOfferAvailabilityModel(offer, messageService, addressService);
CheckOfferAvailabilityProtocol protocol = new CheckOfferAvailabilityProtocol(model,
() -> {
disposeCheckOfferAvailabilityRequest(offer);
// TODO need to check that trade hijacking is not possible (taking trades form other peers)
if (offer.getState() == Offer.State.AVAILABLE || offer.getState() == Offer.State.RESERVED) {
createTakerAsSellerProtocol(trade, model.getPeer());
}
},
(errorMessage) -> disposeCheckOfferAvailabilityRequest(offer));
checkOfferAvailabilityProtocolMap.put(offer.getId(), protocol);
protocol.checkOfferAvailability();
}
createTakerAsSellerProtocol(trade);
}
}
@ -220,9 +206,10 @@ public class TradeManager {
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model,
(transaction) -> {
openOffers.put(offer.getId(), offer);
Trade trade = new Trade(offer);
openOfferTrades.put(trade.getId(), trade);
persistOpenOffers();
createOffererAsBuyerProtocol(offer);
createOffererAsBuyerProtocol(trade);
resultHandler.handleResult(transaction);
},
(message) -> errorMessageHandler.handleErrorMessage(message)
@ -340,8 +327,8 @@ public class TradeManager {
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public ObservableMap<String, Offer> getOpenOffers() {
return openOffers;
public ObservableMap<String, Trade> getOpenOfferTrades() {
return openOfferTrades;
}
public ObservableMap<String, Trade> getPendingTrades() {
@ -372,8 +359,8 @@ public class TradeManager {
String offerId = offer.getId();
offerBookService.removeOffer(offer,
() -> {
if (openOffers.containsKey(offerId)) {
openOffers.remove(offerId);
if (openOfferTrades.containsKey(offerId)) {
openOfferTrades.remove(offerId);
disposeCheckOfferAvailabilityRequest(offer);
persistOpenOffers();
if (removeFromBuyerAcceptsOfferProtocolMap && offererAsBuyerProtocolMap.containsKey(offerId)) {
@ -413,8 +400,9 @@ public class TradeManager {
private Trade takeAvailableOffer(Coin amount, Offer offer, Peer peer) {
Trade trade = createTrade(offer);
trade.setTradeAmount(amount);
trade.setTradingPeer(peer);
TakerAsSellerProtocol sellerTakesOfferProtocol = createTakerAsSellerProtocol(trade, peer);
TakerAsSellerProtocol sellerTakesOfferProtocol = createTakerAsSellerProtocol(trade);
sellerTakesOfferProtocol.takeAvailableOffer();
return trade;
}
@ -432,11 +420,11 @@ public class TradeManager {
return trade;
}
private TakerAsSellerProtocol createTakerAsSellerProtocol(Trade trade, Peer peer) {
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
private TakerAsSellerProtocol createTakerAsSellerProtocol(Trade trade) {
trade.processStateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue);
switch (newValue) {
case OPEN:
case INIT:
break;
case TAKE_OFFER_FEE_TX_CREATED:
case DEPOSIT_PUBLISHED:
@ -458,7 +446,6 @@ public class TradeManager {
TakerAsSellerModel model = new TakerAsSellerModel(
trade,
peer,
messageService,
mailboxService,
walletService,
@ -478,17 +465,7 @@ public class TradeManager {
}
private void createOffererAsBuyerProtocol(Offer offer) {
Trade trade;
if (pendingTrades.containsKey(offer.getId())) {
trade = pendingTrades.get(offer.getId());
currentPendingTrade = trade;
}
else {
trade = new Trade(offer);
// don't save it in pendingTrades. It is only a potential trade
}
private void createOffererAsBuyerProtocol(Trade trade) {
OffererAsBuyerModel model = new OffererAsBuyerModel(
trade,
messageService,
@ -501,16 +478,16 @@ public class TradeManager {
// TODO check, remove listener
trade.stateProperty().addListener((ov, oldValue, newValue) -> {
trade.processStateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue);
switch (newValue) {
case OPEN:
case INIT:
break;
case TAKE_OFFER_FEE_TX_CREATED:
persistPendingTrades();
break;
case DEPOSIT_PUBLISHED:
removeOpenOffer(offer,
removeOpenOffer(trade.getOffer(),
() -> log.debug("remove offer was successful"),
(message) -> log.error(message),
false);
@ -531,7 +508,6 @@ public class TradeManager {
case MESSAGE_SENDING_FAILED:
case FAULT:
closeTrade(trade);
offererAsBuyerProtocolMap.get(trade.getId()).cleanup();
break;
default:
log.warn("Unhandled trade state: " + newValue);
@ -540,7 +516,7 @@ public class TradeManager {
});
OffererAsBuyerProtocol protocol = new OffererAsBuyerProtocol(model);
offererAsBuyerProtocolMap.put(offer.getId(), protocol);
offererAsBuyerProtocolMap.put(trade.getId(), protocol);
if (mailboxMessages.containsKey(trade.getId())) {
log.debug("OffererAsBuyerProtocol setMailboxMessage " + trade.getId());
protocol.setMailboxMessage(mailboxMessages.get(trade.getId()));
@ -612,13 +588,13 @@ public class TradeManager {
}
boolean isOfferOpen(String offerId) {
return openOffers.containsKey(offerId)
&& (openOffers.get(offerId).getState() == Offer.State.UNKNOWN
|| openOffers.get(offerId).getState() == Offer.State.AVAILABLE);
return openOfferTrades.containsKey(offerId)
&& (openOfferTrades.get(offerId).getOffer().getState() == Offer.State.UNKNOWN
|| openOfferTrades.get(offerId).getOffer().getState() == Offer.State.AVAILABLE);
}
private void persistOpenOffers() {
persistence.write(this, "openOffers", (Map<String, Offer>) new HashMap<>(openOffers));
persistence.write(this, "openOfferTrades", (Map<String, Trade>) new HashMap<>(openOfferTrades));
}
private void persistPendingTrades() {

View file

@ -22,7 +22,6 @@ import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.MessageHandler;
import io.bitsquare.p2p.Peer;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestOffererPublishDepositTxMessage;
@ -42,8 +41,6 @@ import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyAndSignContract;
import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyTakeOfferFeePayment;
import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyTakerAccount;
import org.bitcoinj.core.TransactionConfidence;
import javafx.application.Platform;
import org.slf4j.Logger;
@ -57,8 +54,6 @@ public class OffererAsBuyerProtocol {
private final OffererAsBuyerModel model;
private final MessageHandler messageHandler;
private TransactionConfidence.Listener transactionConfidenceListener;
private TransactionConfidence transactionConfidence;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@ -94,11 +89,6 @@ public class OffererAsBuyerProtocol {
// tradeMessageService and transactionConfidence use CopyOnWriteArrayList as listeners, but be safe and delay remove a bit.
Platform.runLater(() -> {
model.messageService.removeMessageHandler(messageHandler);
if (transactionConfidence != null) {
if (!transactionConfidence.removeEventListener(transactionConfidenceListener))
throw new RuntimeException("Remove transactionConfidenceListener failed at BuyerAsOffererProtocol.");
}
});
}
@ -109,16 +99,11 @@ public class OffererAsBuyerProtocol {
private void handleRequestDepositTxInputsMessage(RequestDepositTxInputsMessage tradeMessage, Peer taker) {
checkTradeId(model.id, tradeMessage);
model.setTradeMessage(tradeMessage);
model.taker.peer = taker;
model.trade.setTradingPeer(taker);
TaskRunner<OffererAsBuyerModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("sequence at handleTakeOfferFeePayedMessage completed");
},
(errorMessage) -> {
log.error(errorMessage);
}
);
() -> log.debug("sequence at handleTakeOfferFeePayedMessage completed"),
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
ProcessRequestDepositTxInputsMessage.class,
CreateOffererDepositTxInputs.class,
@ -131,23 +116,8 @@ public class OffererAsBuyerProtocol {
model.setTradeMessage(tradeMessage);
TaskRunner<OffererAsBuyerModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("taskRunner at handleRequestOffererPublishDepositTxMessage completed");
transactionConfidenceListener = (tx, reason) -> {
log.trace("onConfidenceChanged " + tx.getConfidence());
if (reason == TransactionConfidence.Listener.ChangeReason.TYPE && tx.getConfidence().getConfidenceType() == TransactionConfidence
.ConfidenceType.BUILDING) {
model.trade.setState(Trade.State.DEPOSIT_CONFIRMED);
}
};
transactionConfidence = model.trade.getDepositTx().getConfidence();
transactionConfidence.addEventListener(transactionConfidenceListener);
},
(errorMessage) -> {
log.error(errorMessage);
}
);
() -> log.debug("taskRunner at handleRequestOffererPublishDepositTxMessage completed"),
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
ProcessRequestOffererPublishDepositTxMessage.class,
VerifyTakerAccount.class,
@ -167,13 +137,8 @@ public class OffererAsBuyerProtocol {
// User clicked the "bank transfer started" button
public void onFiatPaymentStarted() {
TaskRunner<OffererAsBuyerModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("sequence at handleBankTransferStartedUIEvent completed");
},
(errorMessage) -> {
log.error(errorMessage);
}
);
() -> log.debug("sequence at handleBankTransferStartedUIEvent completed"),
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
CreateAndSignPayoutTx.class,
VerifyTakeOfferFeePayment.class,
@ -193,14 +158,11 @@ public class OffererAsBuyerProtocol {
TaskRunner<OffererAsBuyerModel> taskRunner = new TaskRunner<>(model,
() -> {
log.debug("sequence at handlePayoutTxPublishedMessage completed");
// we are done!
model.onComplete();
},
(errorMessage) -> {
log.error(errorMessage);
}
);
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(ProcessPayoutTxPublishedMessage.class);
taskRunner.run();
}
@ -233,4 +195,8 @@ public class OffererAsBuyerProtocol {
}
}
}
private void handleTaskRunnerFault(String errorMessage) {
cleanup();
}
}

View file

@ -18,7 +18,6 @@
package io.bitsquare.trade.protocol.trade.offerer.models;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.p2p.Peer;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
@ -34,7 +33,6 @@ public class Taker implements Serializable {
private static final long serialVersionUID = 2660909397210346486L;
// written by tasks
public Peer peer;
public String accountId;
public FiatAccount fiatAccount;
public PublicKey p2pSigPublicKey;

View file

@ -43,7 +43,7 @@ public class ProcessPayoutTxPublishedMessage extends Task<OffererAsBuyerModel> {
model.trade.setPayoutTx(checkNotNull(((PayoutTxPublishedMessage) model.getTradeMessage()).payoutTx));
model.trade.setState(Trade.State.PAYOUT_PUBLISHED);
model.trade.setProcessState(Trade.ProcessState.PAYOUT_PUBLISHED);
complete();
} catch (Throwable t) {

View file

@ -45,7 +45,7 @@ public class RequestTakerDepositPayment extends Task<OffererAsBuyerModel> {
model.offerer.fiatAccount,
model.offerer.accountId);
model.messageService.sendMessage(model.taker.peer, tradeMessage, new SendMessageListener() {
model.messageService.sendMessage(model.trade.getTradingPeer(), tradeMessage, new SendMessageListener() {
@Override
public void handleResult() {
log.trace("RequestTakerDepositPaymentMessage successfully arrived at peer");

View file

@ -43,26 +43,26 @@ public class SendBankTransferStartedMessage extends Task<OffererAsBuyerModel> {
model.taker.payoutAmount,
model.offerer.addressEntry.getAddressString());
model.messageService.sendMessage(model.taker.peer, tradeMessage,
model.messageService.sendMessage(model.trade.getTradingPeer(), tradeMessage,
model.taker.p2pSigPublicKey,
model.taker.p2pEncryptPubKey,
new SendMessageListener() {
@Override
public void handleResult() {
log.trace("Sending BankTransferInitedMessage succeeded.");
model.trade.setState(Trade.State.FIAT_PAYMENT_STARTED);
model.trade.setProcessState(Trade.ProcessState.FIAT_PAYMENT_STARTED);
complete();
}
@Override
public void handleFault() {
failed("Sending BankTransferInitedMessage failed.");
model.trade.setState(Trade.State.FAULT);
model.trade.setProcessState(Trade.ProcessState.FAULT);
}
});
} catch (Throwable t) {
failed("Sending BankTransferInitedMessage failed.");
model.trade.setState(Trade.State.FAULT);
model.trade.setProcessState(Trade.ProcessState.FAULT);
}
}
}

View file

@ -37,7 +37,7 @@ public class SendDepositTxToTaker extends Task<OffererAsBuyerModel> {
protected void doRun() {
DepositTxPublishedMessage tradeMessage = new DepositTxPublishedMessage(model.id, model.trade.getDepositTx());
model.messageService.sendMessage(model.taker.peer, tradeMessage, new SendMessageListener() {
model.messageService.sendMessage(model.trade.getTradingPeer(), tradeMessage, new SendMessageListener() {
@Override
public void handleResult() {
log.trace("DepositTxPublishedMessage successfully arrived at peer");

View file

@ -48,7 +48,7 @@ public class SetupListenerForBlockChainConfirmation extends Task<OffererAsBuyerM
public void onConfidenceChanged(Transaction tx, ChangeReason reason) {
log.trace("onConfidenceChanged " + tx.getConfidence());
if (reason == ChangeReason.TYPE && tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
model.trade.setState(Trade.State.DEPOSIT_CONFIRMED);
model.trade.setProcessState(Trade.ProcessState.DEPOSIT_CONFIRMED);
// transactionConfidence use CopyOnWriteArrayList as listeners, but be safe and delay remove a bit.
Platform.runLater(() -> removeEventListener());
@ -62,6 +62,6 @@ public class SetupListenerForBlockChainConfirmation extends Task<OffererAsBuyerM
private void removeEventListener() {
if (!transactionConfidence.removeEventListener(transactionConfidenceListener))
throw new RuntimeException("Remove transactionConfidenceListener failed at SetupListenerForBlockChainConfirmation.");
log.error("Remove transactionConfidenceListener failed at SetupListenerForBlockChainConfirmation.");
}
}

View file

@ -59,7 +59,7 @@ public class SignAndPublishDepositTx extends Task<OffererAsBuyerModel> {
log.trace("offererSignAndPublishTx succeeded " + transaction);
model.trade.setDepositTx(transaction);
model.trade.setState(Trade.State.DEPOSIT_PUBLISHED);
model.trade.setProcessState(Trade.ProcessState.DEPOSIT_PUBLISHED);
complete();
}

View file

@ -27,7 +27,7 @@ import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.FiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestTakerDepositPaymentMessage;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.protocol.trade.offerer.tasks.SetupListenerForBlockChainConfirmation;
import io.bitsquare.trade.protocol.trade.taker.tasks.SetupListenerForBlockChainConfirmation;
import io.bitsquare.trade.protocol.trade.taker.models.TakerAsSellerModel;
import io.bitsquare.trade.protocol.trade.taker.tasks.BroadcastTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract;
@ -43,11 +43,6 @@ import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCommitDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCreatesAndSignsDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.VerifyOfferFeePayment;
import io.bitsquare.trade.protocol.trade.taker.tasks.VerifyOffererAccount;
import io.bitsquare.util.Utilities;
import java.util.function.Function;
import javafx.animation.AnimationTimer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -56,11 +51,9 @@ import static io.bitsquare.util.Validator.nonEmptyStringOf;
public class TakerAsSellerProtocol {
private static final Logger log = LoggerFactory.getLogger(TakerAsSellerProtocol.class);
private static final int TIMEOUT_DELAY = 10000;
private final TakerAsSellerModel model;
private final MessageHandler messageHandler;
private AnimationTimer timeoutTimer;
///////////////////////////////////////////////////////////////////////////////////////////
@ -72,8 +65,6 @@ public class TakerAsSellerProtocol {
this.model = model;
messageHandler = this::handleMessage;
model.messageService.addMessageHandler(messageHandler);
}
@ -105,10 +96,8 @@ public class TakerAsSellerProtocol {
() -> {
log.debug("taskRunner at takeAvailableOffer completed");
},
(errorMessage) -> {
log.error(errorMessage);
}
);
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
CreateTakeOfferFeeTx.class,
BroadcastTakeOfferFeeTx.class,
@ -129,10 +118,8 @@ public class TakerAsSellerProtocol {
() -> {
log.debug("taskRunner at handleTakerDepositPaymentRequestMessage completed");
},
(errorMessage) -> {
log.error(errorMessage);
}
);
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
ProcessRequestTakerDepositPaymentMessage.class,
VerifyOffererAccount.class,
@ -150,10 +137,8 @@ public class TakerAsSellerProtocol {
() -> {
log.debug("taskRunner at handleDepositTxPublishedMessage completed");
},
(errorMessage) -> {
log.error(errorMessage);
}
);
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
ProcessDepositTxPublishedMessage.class,
TakerCommitDepositTx.class,
@ -169,10 +154,8 @@ public class TakerAsSellerProtocol {
() -> {
log.debug("taskRunner at handleFiatTransferStartedMessage completed");
},
(errorMessage) -> {
log.error(errorMessage);
}
);
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(ProcessFiatTransferStartedMessage.class);
taskRunner.run();
}
@ -184,7 +167,7 @@ public class TakerAsSellerProtocol {
// User clicked the "bank transfer received" button, so we release the funds for pay out
public void onFiatPaymentReceived() {
model.trade.setState(Trade.State.FIAT_PAYMENT_RECEIVED);
model.trade.setProcessState(Trade.ProcessState.FIAT_PAYMENT_RECEIVED);
TaskRunner<TakerAsSellerModel> taskRunner = new TaskRunner<>(model,
() -> {
@ -193,10 +176,8 @@ public class TakerAsSellerProtocol {
// we are done!
model.onComplete();
},
(errorMessage) -> {
log.error(errorMessage);
}
);
(errorMessage) -> handleTaskRunnerFault(errorMessage));
taskRunner.addTasks(
SignAndPublishPayoutTx.class,
VerifyOfferFeePayment.class,
@ -232,22 +213,8 @@ public class TakerAsSellerProtocol {
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Timeout
///////////////////////////////////////////////////////////////////////////////////////////
private void startTimeout(Function<AnimationTimer, Void> callback) {
stopTimeout();
timeoutTimer = Utilities.setTimeout(TIMEOUT_DELAY, callback);
timeoutTimer.start();
}
private void stopTimeout() {
if (timeoutTimer != null) {
timeoutTimer.stop();
timeoutTimer = null;
}
private void handleTaskRunnerFault(String errorMessage) {
cleanup();
}
}

View file

@ -18,7 +18,6 @@
package io.bitsquare.trade.protocol.trade.taker.models;
import io.bitsquare.fiat.FiatAccount;
import io.bitsquare.p2p.Peer;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionOutput;
@ -32,10 +31,6 @@ import java.util.List;
public class Offerer implements Serializable {
private static final long serialVersionUID = 1582902150121576205L;
// Those fields are set at constructor but not declared as final because constructor is not called in case model gets created from a persisted model
// Declared transient as they will be provided in any case at construction time
public Peer peer;
// written by tasks
public byte[] tradeWalletPubKey;
public Coin payoutAmount;

View file

@ -22,7 +22,6 @@ import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService;
import io.bitsquare.p2p.MailboxService;
import io.bitsquare.p2p.MessageService;
import io.bitsquare.p2p.Peer;
import io.bitsquare.persistence.Persistence;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.SharedTradeModel;
@ -48,7 +47,6 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable
private Transaction payoutTx;
public TakerAsSellerModel(Trade trade,
Peer offererPeer,
MessageService messageService,
MailboxService mailboxService,
WalletService walletService,
@ -82,8 +80,6 @@ public class TakerAsSellerModel extends SharedTradeModel implements Serializable
offerer = new Offerer();
}
offerer.peer = offererPeer;
taker.registrationPubKey = walletService.getRegistrationAddressEntry().getPubKey();
taker.registrationKeyPair = walletService.getRegistrationAddressEntry().getKeyPair();
taker.addressEntry = walletService.getAddressEntry(id);

View file

@ -47,14 +47,14 @@ public class BroadcastTakeOfferFeeTx extends Task<TakerAsSellerModel> {
public void onSuccess(Transaction transaction) {
log.debug("Take offer fee published successfully. Transaction ID = " + transaction.getHashAsString());
model.trade.setState(Trade.State.TAKE_OFFER_FEE_PUBLISHED);
model.trade.setProcessState(Trade.ProcessState.TAKE_OFFER_FEE_PUBLISHED);
complete();
}
@Override
public void onFailure(@NotNull Throwable t) {
model.trade.setState(Trade.State.TAKE_OFFER_FEE_PUBLISH_FAILED);
model.trade.setProcessState(Trade.ProcessState.TAKE_OFFER_FEE_PUBLISH_FAILED);
failed(t);
}
@ -63,7 +63,7 @@ public class BroadcastTakeOfferFeeTx extends Task<TakerAsSellerModel> {
appendToErrorMessage("Take offer fee payment failed. Maybe your network connection was lost. Please try again.");
appendToErrorMessage(e.getMessage());
model.trade.setState(Trade.State.FAULT);
model.trade.setProcessState(Trade.ProcessState.FAULT);
failed(e);
}

View file

@ -40,13 +40,13 @@ public class CreateTakeOfferFeeTx extends Task<TakerAsSellerModel> {
Transaction createTakeOfferFeeTx = model.tradeWalletService.createTakeOfferFeeTx(model.taker.addressEntry);
model.setTakeOfferFeeTx(createTakeOfferFeeTx);
model.trade.setState(Trade.State.TAKE_OFFER_FEE_TX_CREATED);
model.trade.setProcessState(Trade.ProcessState.TAKE_OFFER_FEE_TX_CREATED);
complete();
} catch (Exception e) {
appendToErrorMessage(e.getMessage());
model.trade.setState(Trade.State.FAULT);
model.trade.setProcessState(Trade.ProcessState.FAULT);
failed(e);
}

View file

@ -43,7 +43,7 @@ public class ProcessDepositTxPublishedMessage extends Task<TakerAsSellerModel> {
DepositTxPublishedMessage message = (DepositTxPublishedMessage) model.getTradeMessage();
model.trade.setDepositTx(checkNotNull(message.depositTx));
model.trade.setState(Trade.State.DEPOSIT_PUBLISHED);
model.trade.setProcessState(Trade.ProcessState.DEPOSIT_PUBLISHED);
complete();
} catch (Throwable t) {

View file

@ -46,11 +46,11 @@ public class ProcessFiatTransferStartedMessage extends Task<TakerAsSellerModel>
model.offerer.payoutAmount = positiveCoinOf(nonZeroCoinOf(message.offererPayoutAmount));
model.taker.payoutAmount = positiveCoinOf(nonZeroCoinOf(message.takerPayoutAmount));
model.offerer.payoutAddressString = nonEmptyStringOf(message.offererPayoutAddress);
model.trade.setState(Trade.State.FIAT_PAYMENT_STARTED);
model.trade.setProcessState(Trade.ProcessState.FIAT_PAYMENT_STARTED);
complete();
} catch (Throwable t) {
model.trade.setState(Trade.State.FAULT);
model.trade.setProcessState(Trade.ProcessState.FAULT);
failed(t);
}
}

View file

@ -36,7 +36,7 @@ public class SendPayoutTxToOfferer extends Task<TakerAsSellerModel> {
@Override
protected void doRun() {
PayoutTxPublishedMessage tradeMessage = new PayoutTxPublishedMessage(model.id, model.getPayoutTx());
model.messageService.sendMessage(model.offerer.peer,
model.messageService.sendMessage(model.trade.getTradingPeer(),
tradeMessage,
model.offerer.p2pSigPublicKey,
model.offerer.p2pEncryptPubKey,

View file

@ -47,7 +47,7 @@ public class SendRequestDepositTxInputsMessage extends Task<TakerAsSellerModel>
model.taker.tradeWalletPubKey
);
model.messageService.sendMessage(model.offerer.peer, msg, new SendMessageListener() {
model.messageService.sendMessage(model.trade.getTradingPeer(), msg, new SendMessageListener() {
@Override
public void handleResult() {
log.trace("Sending TakeOfferFeePayedMessage succeeded.");
@ -67,7 +67,7 @@ public class SendRequestDepositTxInputsMessage extends Task<TakerAsSellerModel>
"connection. " +
"We persisted the state of the trade, please try again later or cancel that trade.");
model.trade.setState(Trade.State.MESSAGE_SENDING_FAILED);
model.trade.setProcessState(Trade.ProcessState.MESSAGE_SENDING_FAILED);
failed();
}

View file

@ -49,7 +49,7 @@ public class SendSignedTakerDepositTx extends Task<TakerAsSellerModel> {
model.taker.outputs
);
model.messageService.sendMessage(model.offerer.peer, tradeMessage, new SendMessageListener() {
model.messageService.sendMessage(model.trade.getTradingPeer(), tradeMessage, new SendMessageListener() {
@Override
public void handleResult() {
complete();

View file

@ -20,7 +20,7 @@ package io.bitsquare.trade.protocol.trade.taker.tasks;
import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.offerer.models.OffererAsBuyerModel;
import io.bitsquare.trade.protocol.trade.taker.models.TakerAsSellerModel;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
@ -30,25 +30,26 @@ import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SetupListenerForBlockChainConfirmation extends Task<OffererAsBuyerModel> {
public class SetupListenerForBlockChainConfirmation extends Task<TakerAsSellerModel> {
private static final Logger log = LoggerFactory.getLogger(SetupListenerForBlockChainConfirmation.class);
private TransactionConfidence.Listener transactionConfidenceListener;
private TransactionConfidence transactionConfidence;
public SetupListenerForBlockChainConfirmation(TaskRunner taskHandler, OffererAsBuyerModel model) {
public SetupListenerForBlockChainConfirmation(TaskRunner taskHandler, TakerAsSellerModel model) {
super(taskHandler, model);
}
@Override
protected void doRun() {
try {
transactionConfidence = model.trade.getDepositTx().getConfidence();
transactionConfidenceListener = new TransactionConfidence.Listener() {
@Override
public void onConfidenceChanged(Transaction tx, ChangeReason reason) {
log.trace("onConfidenceChanged " + tx.getConfidence());
if (reason == ChangeReason.TYPE && tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
model.trade.setState(Trade.State.DEPOSIT_CONFIRMED);
model.trade.setProcessState(Trade.ProcessState.DEPOSIT_CONFIRMED);
// transactionConfidence use CopyOnWriteArrayList as listeners, but be safe and delay remove a bit.
Platform.runLater(() -> removeEventListener());
@ -58,10 +59,14 @@ public class SetupListenerForBlockChainConfirmation extends Task<OffererAsBuyerM
transactionConfidence.addEventListener(transactionConfidenceListener);
complete();
} catch (Throwable t) {
t.printStackTrace();
log.error(t.getMessage());
}
}
private void removeEventListener() {
if (!transactionConfidence.removeEventListener(transactionConfidenceListener))
throw new RuntimeException("Remove transactionConfidenceListener failed at SetupListenerForBlockChainConfirmation.");
log.error("Remove transactionConfidenceListener failed at SetupListenerForBlockChainConfirmation.");
}
}

View file

@ -55,7 +55,7 @@ public class SignAndPublishPayoutTx extends Task<TakerAsSellerModel> {
@Override
public void onSuccess(Transaction transaction) {
model.setPayoutTx(transaction);
model.trade.setState(Trade.State.PAYOUT_PUBLISHED);
model.trade.setProcessState(Trade.ProcessState.PAYOUT_PUBLISHED);
complete();
}

View file

@ -21,6 +21,8 @@ import io.bitsquare.common.taskrunner.Task;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.protocol.trade.taker.models.TakerAsSellerModel;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -34,7 +36,9 @@ public class TakerCommitDepositTx extends Task<TakerAsSellerModel> {
@Override
protected void doRun() {
try {
model.tradeWalletService.takerCommitsDepositTx(model.trade.getDepositTx());
// We need to put the tx into our wallet to have a fully setup tx
Transaction localDepositTx = model.tradeWalletService.takerCommitsDepositTx(model.trade.getDepositTx());
model.trade.setDepositTx(localDepositTx);
complete();
} catch (Throwable t) {

View file

@ -59,9 +59,9 @@ public class TakerCreatesAndSignsDepositTx extends Task<TakerAsSellerModel> {
complete();
} catch (Exception e) {
Trade.State state = Trade.State.FAULT;
state.setErrorMessage(errorMessage);
model.trade.setState(state);
Trade.ProcessState processState = Trade.ProcessState.FAULT;
processState.setErrorMessage(errorMessage);
model.trade.setProcessState(processState);
failed(e);
}