Add locktime to payout tx

This commit is contained in:
Manfred Karrer 2015-04-06 21:44:01 +02:00
parent f24ebb9038
commit 08dde43ffc
33 changed files with 620 additions and 198 deletions

View File

@ -17,6 +17,7 @@
package io.bitsquare.app.bootstrap;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node;
import net.tomp2p.connection.ChannelClientConfiguration;
@ -52,7 +53,7 @@ public class BootstrapNode {
public void start() {
String name = env.getRequiredProperty(Node.NAME_KEY);
int port = env.getProperty(Node.PORT_KEY, Integer.class, Node.DEFAULT_PORT);
int port = env.getProperty(Node.PORT_KEY, Integer.class, BootstrapNodes.DEFAULT_PORT);
try {
Number160 peerId = Number160.createHash(name);

View File

@ -19,6 +19,7 @@ package io.bitsquare.app.bootstrap;
import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.app.BitsquareExecutable;
import io.bitsquare.p2p.BootstrapNodes;
import io.bitsquare.p2p.Node;
import joptsimple.OptionParser;
@ -34,7 +35,7 @@ public class BootstrapNodeMain extends BitsquareExecutable {
parser.accepts(Node.NAME_KEY, description("Name of this node", null))
.withRequiredArg()
.isRequired();
parser.accepts(Node.PORT_KEY, description("Port to listen on", Node.DEFAULT_PORT))
parser.accepts(Node.PORT_KEY, description("Port to listen on", BootstrapNodes.DEFAULT_PORT))
.withRequiredArg()
.ofType(int.class);
}

View File

@ -34,6 +34,7 @@ import com.google.inject.Guice;
import com.google.inject.Injector;
import java.io.IOException;
import java.io.InvalidObjectException;
import javafx.application.Application;
import javafx.scene.*;
@ -69,73 +70,80 @@ public class BitsquareApp extends Application {
this.primaryStage = primaryStage;
log.trace("BitsquareApp.start");
try {
bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(InjectorViewFactory.class).setInjector(injector);
bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(InjectorViewFactory.class).setInjector(injector);
// route uncaught exceptions to a user-facing dialog
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) ->
Popups.handleUncaughtExceptions(Throwables.getRootCause(throwable)));
// route uncaught exceptions to a user-facing dialog
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) ->
Popups.handleUncaughtExceptions(Throwables.getRootCause(throwable)));
// load the main view and create the main scene
log.trace("viewLoader.load(MainView.class)");
CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class);
View view = viewLoader.load(MainView.class);
// load the main view and create the main scene
log.trace("viewLoader.load(MainView.class)");
CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class);
View view = viewLoader.load(MainView.class);
scene = new Scene((Parent) view.getRoot(), 1000, 620);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css");
scene = new Scene((Parent) view.getRoot(), 1000, 620);
scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css");
// configure the system tray
// configure the system tray
SystemTray.create(primaryStage, this::stop);
primaryStage.setOnCloseRequest(e -> {
e.consume();
stop();
});
scene.setOnKeyReleased(keyEvent -> {
// For now we exit when closing/quit the app.
// Later we will only hide the window (systemTray.hideStage()) and use the exit item in the system tray for
// shut down.
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent) ||
new KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN).match(keyEvent))
SystemTray.create(primaryStage, this::stop);
primaryStage.setOnCloseRequest(e -> {
e.consume();
stop();
else if (new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN).match(keyEvent))
showDebugWindow();
});
});
scene.setOnKeyReleased(keyEvent -> {
// For now we exit when closing/quit the app.
// Later we will only hide the window (systemTray.hideStage()) and use the exit item in the system tray for
// shut down.
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent) ||
new KeyCodeCombination(KeyCode.Q, KeyCombination.SHORTCUT_DOWN).match(keyEvent))
stop();
else if (new KeyCodeCombination(KeyCode.D, KeyCombination.SHORTCUT_DOWN).match(keyEvent))
showDebugWindow();
});
// configure the primary stage
// configure the primary stage
primaryStage.setTitle(env.getRequiredProperty(APP_NAME_KEY));
primaryStage.setScene(scene);
primaryStage.setMinWidth(750);
primaryStage.setMinHeight(620);
primaryStage.setTitle(env.getRequiredProperty(APP_NAME_KEY));
primaryStage.setScene(scene);
primaryStage.setMinWidth(750);
primaryStage.setMinHeight(620);
// on windows the title icon is also used as task bar icon in a larger size
// on Linux no title icon is supported but also a large task bar icon is derived form that title icon
String iconPath;
if (Utilities.isOSX())
iconPath = ImageUtil.isRetina() ? "/images/window_icon@2x.png" : "/images/window_icon.png";
else if (Utilities.isWindows())
iconPath = "/images/task_bar_icon_windows.png";
else
iconPath = "/images/task_bar_icon_linux.png";
// on windows the title icon is also used as task bar icon in a larger size
// on Linux no title icon is supported but also a large task bar icon is derived form that title icon
String iconPath;
if (Utilities.isOSX())
iconPath = ImageUtil.isRetina() ? "/images/window_icon@2x.png" : "/images/window_icon.png";
else if (Utilities.isWindows())
iconPath = "/images/task_bar_icon_windows.png";
else
iconPath = "/images/task_bar_icon_linux.png";
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream(iconPath)));
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream(iconPath)));
// make the UI visible
// make the UI visible
log.trace("primaryStage.show");
primaryStage.show();
log.trace("primaryStage.show");
primaryStage.show();
//TODO just temp.
//showDebugWindow();
//TODO just temp.
//showDebugWindow();
} catch (Throwable t) {
if (t instanceof InvalidObjectException) {
Popups.openErrorPopup("There is a problem with different version of persisted objects. " +
"Please delete the db directory inside the app directory.");
}
log.error(t.toString());
}
}
private void showDebugWindow() {

View File

@ -23,11 +23,13 @@ import io.bitsquare.btc.exceptions.WalletException;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.BlockChainListener;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
@ -118,11 +120,6 @@ public class TradeWalletService {
return createOfferFeeTx;
}
public void broadcastCreateOfferFeeTx(Transaction createOfferFeeTx, FutureCallback<Transaction> callback) {
ListenableFuture<Transaction> future = walletAppKit.peerGroup().broadcastTransaction(createOfferFeeTx).future();
Futures.addCallback(future, callback);
}
public Transaction createTakeOfferFeeTx(AddressEntry sellerAddressEntry) throws InsufficientMoneyException {
Transaction takeOfferFeeTx = new Transaction(params);
Coin fee = FeePolicy.TAKE_OFFER_FEE.subtract(FeePolicy.TX_FEE);
@ -139,8 +136,8 @@ public class TradeWalletService {
return takeOfferFeeTx;
}
public void broadcastTakeOfferFeeTx(Transaction takeOfferFeeTx, FutureCallback<Transaction> callback) {
ListenableFuture<Transaction> future = walletAppKit.peerGroup().broadcastTransaction(takeOfferFeeTx).future();
public void broadcastTx(Transaction tx, FutureCallback<Transaction> callback) {
ListenableFuture<Transaction> future = walletAppKit.peerGroup().broadcastTransaction(tx).future();
Futures.addCallback(future, callback);
}
@ -419,6 +416,7 @@ public class TradeWalletService {
Coin sellerPayoutAmount,
AddressEntry buyerAddressEntry,
String sellerPayoutAddressString,
long lockTimeDelta,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
@ -438,6 +436,8 @@ public class TradeWalletService {
sellerPayoutAmount,
buyerAddressEntry.getAddressString(),
sellerPayoutAddressString);
preparedPayoutTx.setLockTime(wallet.getLastBlockSeenHeight() + lockTimeDelta);
preparedPayoutTx.getInputs().stream().forEach(i -> i.setSequenceNumber(0));
// MS redeemScript
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
@ -454,16 +454,16 @@ public class TradeWalletService {
return buyerSignature.encodeToDER();
}
public void signAndPublishPayoutTx(Transaction depositTx,
byte[] buyerSignature,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
String buyerAddressString,
AddressEntry sellerAddressEntry,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey,
FutureCallback<Transaction> callback)
public Transaction signAndFinalizePayoutTx(Transaction depositTx,
byte[] buyerSignature,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
String buyerAddressString,
AddressEntry sellerAddressEntry,
long lockTimeDelta,
byte[] buyerPubKey,
byte[] sellerPubKey,
byte[] arbitratorPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException, SigningException {
log.trace("signAndPublishPayoutTx called");
log.trace("depositTx " + depositTx.toString());
@ -482,6 +482,8 @@ public class TradeWalletService {
sellerPayoutAmount,
buyerAddressString,
sellerAddressEntry.getAddressString());
payoutTx.setLockTime(wallet.getLastBlockSeenHeight() + lockTimeDelta);
payoutTx.getInputs().stream().forEach(i -> i.setSequenceNumber(0));
Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature sellerSignature = sellerAddressEntry.getKeyPair().sign(sigHash).toCanonicalised();
@ -508,8 +510,25 @@ public class TradeWalletService {
printTxWithInputs("payoutTx", payoutTx);
ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(payoutTx).future();
Futures.addCallback(broadcastComplete, callback);
// As we use lockTime the tx will not be relayed as it is not considered standard.
// We need to broadcast on our own when we reahced the block height. Both peers will do the broadcast.
return payoutTx;
}
public ListenableFuture<StoredBlock> getBlockHeightFuture(Transaction transaction) {
return walletAppKit.chain().getHeightFuture((int) transaction.getLockTime());
}
public int getBestChainHeight() {
return walletAppKit.chain().getBestChainHeight();
}
public void addBlockChainListener(BlockChainListener blockChainListener) {
walletAppKit.chain().addListener(blockChainListener);
}
public void removeBlockChainListener(BlockChainListener blockChainListener) {
walletAppKit.chain().removeListener(blockChainListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -639,9 +658,9 @@ public class TradeWalletService {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////////////////////
public class Result {
private final List<TransactionOutput> connectedOutputsForAllInputs;

View File

@ -32,21 +32,27 @@ public class FiatAccount implements Serializable {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = 1L;
public static final long HOUR_IN_BLOCKS = 6;
public static final long DAY_IN_BLOCKS = HOUR_IN_BLOCKS * 24;
public static final long WEEK_IN_BLOCKS = DAY_IN_BLOCKS * 7;
public enum Type {
IRC("", ""),
SEPA("IBAN", "BIC"),
WIRE("primary ID", "secondary ID"),
INTERNATIONAL("primary ID", "secondary ID"),
OK_PAY("primary ID", "secondary ID"),
NET_TELLER("primary ID", "secondary ID"),
PERFECT_MONEY("primary ID", "secondary ID");
IRC("", "", 1),
SEPA("IBAN", "BIC", WEEK_IN_BLOCKS),
WIRE("primary ID", "secondary ID", WEEK_IN_BLOCKS),
INTERNATIONAL("primary ID", "secondary ID", 2 * WEEK_IN_BLOCKS),
OK_PAY("primary ID", "secondary ID", HOUR_IN_BLOCKS),
NET_TELLER("primary ID", "secondary ID", HOUR_IN_BLOCKS),
PERFECT_MONEY("primary ID", "secondary ID", HOUR_IN_BLOCKS);
public final String primaryId;
public final String secondaryId;
public final long lockTimeDelta;
Type(String primaryId, String secondaryId) {
Type(String primaryId, String secondaryId, long lockTimeDelta) {
this.primaryId = primaryId;
this.secondaryId = secondaryId;
this.lockTimeDelta = lockTimeDelta;
}
public static ArrayList<Type> getAllBankAccountTypes() {

View File

@ -32,7 +32,7 @@ import io.bitsquare.trade.protocol.trade.BuyerAsOffererProtocol;
import io.bitsquare.trade.protocol.trade.SellerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateAndSignPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateDepositTxInputs;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendDepositTxPublishedMessage;
@ -46,9 +46,9 @@ import io.bitsquare.trade.protocol.trade.tasks.seller.CreateAndSignDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessRequestPayDepositMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendRequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndPublishPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndFinalizePayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.CreateTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOfferFeePayment;
import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOffererAccount;
@ -111,7 +111,7 @@ public class DebugView extends InitializableView {
VerifyTakeOfferFeePayment.class,
SendFiatTransferStartedMessage.class,
ProcessPayoutTxPublishedMessage.class,
ProcessPayoutTxFinalizedMessage.class,
Boolean.class, /* used as seperator*/
@ -129,9 +129,9 @@ public class DebugView extends InitializableView {
ProcessFiatTransferStartedMessage.class,
SignAndPublishPayoutTx.class,
SignAndFinalizePayoutTx.class,
VerifyOfferFeePayment.class,
SendPayoutTxPublishedMessage.class
SendPayoutTxFinalizedMessage.class
)
);

View File

@ -255,7 +255,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
"Please try later again." + msg);
takeOfferRequested = false;
break;
case PAYOUT_PUBLISHED:
case PAYOUT_FINALIZED:
break;
case EXCEPTION:
errorMessage.set(msg);
@ -292,7 +292,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
"Please try later again." + msg);
takeOfferRequested = false;
break;
case PAYOUT_PUBLISHED:
case PAYOUT_FINALIZED:
break;
case EXCEPTION:
errorMessage.set(msg);

View File

@ -22,6 +22,7 @@ import io.bitsquare.gui.main.portfolio.pendingtrades.steps.CompletedView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.StartFiatView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.WaitFiatReceivedView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.WaitPayoutLockTimeView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.WaitTxInBlockchainView;
import io.bitsquare.locale.BSResources;
@ -34,6 +35,7 @@ public class BuyerSubView extends TradeSubView {
private TradeWizardItem waitTxInBlockchain;
private TradeWizardItem startFiat;
private TradeWizardItem waitFiatReceived;
private TradeWizardItem payoutUnlock;
private TradeWizardItem completed;
@ -60,9 +62,10 @@ public class BuyerSubView extends TradeSubView {
waitTxInBlockchain = new TradeWizardItem(WaitTxInBlockchainView.class, "Wait for blockchain confirmation");
startFiat = new TradeWizardItem(StartFiatView.class, "Start payment");
waitFiatReceived = new TradeWizardItem(WaitFiatReceivedView.class, "Wait until payment has arrived");
payoutUnlock = new TradeWizardItem(WaitPayoutLockTimeView.class, "Wait for payout unlock");
completed = new TradeWizardItem(CompletedView.class, "Completed");
leftVBox.getChildren().addAll(waitTxInBlockchain, startFiat, waitFiatReceived, completed);
leftVBox.getChildren().addAll(waitTxInBlockchain, startFiat, waitFiatReceived, payoutUnlock, completed);
}
@ -110,10 +113,20 @@ public class BuyerSubView extends TradeSubView {
"the Bitcoin sellers payment account, the payout transaction will be published.",
model.getCurrencyCode()));
break;
case BUYER_PAYOUT_FINALIZED:
waitTxInBlockchain.done();
startFiat.done();
waitFiatReceived.done();
showItem(payoutUnlock);
((WaitPayoutLockTimeView) tradeStepDetailsView).setInfoLabelText("The payout transaction is signed and finalized by both parties." +
"\nFor reducing bank chargeback risks you need to wait until the payout gets unlocked to transfer your Bitcoin.");
break;
case BUYER_COMPLETED:
waitTxInBlockchain.done();
startFiat.done();
waitFiatReceived.done();
payoutUnlock.done();
showItem(completed);
CompletedView completedView = (CompletedView) tradeStepDetailsView;

View File

@ -18,6 +18,7 @@
package io.bitsquare.gui.main.portfolio.pendingtrades;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.common.viewfx.model.Activatable;
import io.bitsquare.common.viewfx.model.DataModel;
@ -35,6 +36,7 @@ import io.bitsquare.trade.TradeManager;
import io.bitsquare.trade.states.TradeState;
import io.bitsquare.user.User;
import org.bitcoinj.core.BlockChainListener;
import org.bitcoinj.core.Coin;
import com.google.inject.Inject;
@ -60,6 +62,7 @@ class PendingTradesDataModel implements Activatable, DataModel {
private final TradeManager tradeManager;
private final WalletService walletService;
private TradeWalletService tradeWalletService;
private final User user;
private Navigation navigation;
@ -80,9 +83,11 @@ class PendingTradesDataModel implements Activatable, DataModel {
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, User user, Navigation navigation) {
public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, TradeWalletService tradeWalletService, User user, Navigation
navigation) {
this.tradeManager = tradeManager;
this.walletService = walletService;
this.tradeWalletService = tradeWalletService;
this.user = user;
this.navigation = navigation;
@ -281,5 +286,24 @@ class PendingTradesDataModel implements Activatable, DataModel {
return txId;
}
String getPayoutTxId() {
return trade.getPayoutTx().getHashAsString();
}
void addBlockChainListener(BlockChainListener blockChainListener) {
tradeWalletService.addBlockChainListener(blockChainListener);
}
void removeBlockChainListener(BlockChainListener blockChainListener) {
tradeWalletService.removeBlockChainListener(blockChainListener);
}
public long getLockTime() {
return trade.getPayoutTx().getLockTime();
}
public int getBestChainHeight() {
return tradeWalletService.getBestChainHeight();
}
}

View File

@ -27,6 +27,7 @@ import io.bitsquare.trade.Trade;
import io.bitsquare.trade.states.OffererTradeState;
import io.bitsquare.trade.states.TakerTradeState;
import org.bitcoinj.core.BlockChainListener;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
@ -58,11 +59,13 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
SELLER_WAIT_PAYMENT_STARTED,
SELLER_CONFIRM_RECEIVE_PAYMENT,
SELLER_SEND_PUBLISHED_MSG,
SELLER_PAYOUT_FINALIZED,
SELLER_COMPLETED,
BUYER_WAIT_TX_CONF,
BUYER_START_PAYMENT,
BUYER_WAIT_CONFIRM_PAYMENT_RECEIVED,
BUYER_PAYOUT_FINALIZED,
BUYER_COMPLETED,
MESSAGE_SENDING_FAILED,
@ -135,6 +138,10 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
return txId;
}
public String getPayoutTxId() {
return dataModel.getPayoutTxId();
}
public ReadOnlyBooleanProperty getWithdrawalButtonDisable() {
return withdrawalButtonDisable;
}
@ -224,6 +231,26 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
return formatter.formatDateTime(value);
}
public void addBlockChainListener(BlockChainListener blockChainListener) {
dataModel.addBlockChainListener(blockChainListener);
}
public void removeBlockChainListener(BlockChainListener blockChainListener) {
dataModel.removeBlockChainListener(blockChainListener);
}
public long getLockTime() {
return dataModel.getLockTime();
}
public int getBestChainHeight() {
return dataModel.getBestChainHeight();
}
public String getUnlockDate(long missingBlocks) {
return formatter.getUnlockDate(missingBlocks);
}
// payment
public String getPaymentMethod() {
assert dataModel.getContract() != null;
@ -304,12 +331,18 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
case FIAT_PAYMENT_RECEIVED:
break;
case PAYOUT_PUBLISHED:
case PAYOUT_FINALIZED:
viewState.set(ViewState.SELLER_SEND_PUBLISHED_MSG);
break;
case PAYOUT_PUBLISHED_MSG_SENT:
case PAYOUT_FINALIZED_MSG_SENT:
viewState.set(ViewState.SELLER_PAYOUT_FINALIZED);
break;
case PAYOUT_BROAD_CASTED:
viewState.set(ViewState.SELLER_COMPLETED);
break;
case PAYOUT_BROAD_CASTED_FAILED:
viewState.set(ViewState.EXCEPTION);
break;
case MESSAGE_SENDING_FAILED:
viewState.set(ViewState.MESSAGE_SENDING_FAILED);
@ -345,12 +378,18 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
case FIAT_PAYMENT_RECEIVED:
break;
case PAYOUT_PUBLISHED:
case PAYOUT_FINALIZED:
viewState.set(ViewState.SELLER_SEND_PUBLISHED_MSG);
break;
case PAYOUT_PUBLISHED_MSG_SENT:
case PAYOUT_FINALIZED_MSG_SENT:
viewState.set(ViewState.SELLER_PAYOUT_FINALIZED);
break;
case PAYOUT_BROAD_CASTED:
viewState.set(ViewState.SELLER_COMPLETED);
break;
case PAYOUT_BROAD_CASTED_FAILED:
viewState.set(ViewState.EXCEPTION);
break;
case MESSAGE_SENDING_FAILED:
viewState.set(ViewState.MESSAGE_SENDING_FAILED);
@ -393,9 +432,16 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
break;
case FIAT_PAYMENT_RECEIVED:
case PAYOUT_PUBLISHED:
case PAYOUT_FINALIZED:
case PAYOUT_FINALIZED_MSG_SENT:
viewState.set(ViewState.BUYER_PAYOUT_FINALIZED);
break;
case PAYOUT_BROAD_CASTED:
viewState.set(ViewState.BUYER_COMPLETED);
break;
case PAYOUT_BROAD_CASTED_FAILED:
viewState.set(ViewState.EXCEPTION);
break;
case MESSAGE_SENDING_FAILED:
viewState.set(ViewState.MESSAGE_SENDING_FAILED);
@ -432,9 +478,16 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
break;
case FIAT_PAYMENT_RECEIVED:
case PAYOUT_PUBLISHED:
case PAYOUT_FINALIZED:
case PAYOUT_FINALIZED_MSG_SENT:
viewState.set(ViewState.BUYER_PAYOUT_FINALIZED);
break;
case PAYOUT_BROAD_CASTED:
viewState.set(ViewState.BUYER_COMPLETED);
break;
case PAYOUT_BROAD_CASTED_FAILED:
viewState.set(ViewState.EXCEPTION);
break;
case MESSAGE_SENDING_FAILED:
viewState.set(ViewState.MESSAGE_SENDING_FAILED);

View File

@ -21,6 +21,7 @@ import io.bitsquare.gui.components.Popups;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.CompletedView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.ConfirmFiatReceivedView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.TradeWizardItem;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.WaitPayoutLockTimeView;
import io.bitsquare.gui.main.portfolio.pendingtrades.steps.WaitTxInBlockchainView;
import io.bitsquare.locale.BSResources;
@ -33,6 +34,7 @@ public class SellerSubView extends TradeSubView {
private TradeWizardItem waitTxInBlockchain;
private TradeWizardItem waitFiatStarted;
private TradeWizardItem confirmFiatReceived;
private TradeWizardItem payoutUnlock;
private TradeWizardItem completed;
@ -59,9 +61,10 @@ public class SellerSubView extends TradeSubView {
waitTxInBlockchain = new TradeWizardItem(WaitTxInBlockchainView.class, "Wait for blockchain confirmation");
waitFiatStarted = new TradeWizardItem(WaitTxInBlockchainView.class, "Wait for payment started");
confirmFiatReceived = new TradeWizardItem(ConfirmFiatReceivedView.class, "Confirm payment received");
payoutUnlock = new TradeWizardItem(WaitPayoutLockTimeView.class, "Wait for payout unlock");
completed = new TradeWizardItem(CompletedView.class, "Completed");
leftVBox.getChildren().addAll(waitTxInBlockchain, waitFiatStarted, confirmFiatReceived, completed);
leftVBox.getChildren().addAll(waitTxInBlockchain, waitFiatStarted, confirmFiatReceived, payoutUnlock, completed);
}
@ -76,6 +79,7 @@ public class SellerSubView extends TradeSubView {
waitTxInBlockchain.inactive();
waitFiatStarted.inactive();
confirmFiatReceived.inactive();
payoutUnlock.inactive();
completed.inactive();
if (tradeStepDetailsView != null)
@ -129,14 +133,23 @@ public class SellerSubView extends TradeSubView {
waitTxInBlockchain.done();
waitFiatStarted.done();
showItem(confirmFiatReceived);
((ConfirmFiatReceivedView) tradeStepDetailsView).setStatusText("Sending message to trading peer transaction...");
}
break;
case SELLER_PAYOUT_FINALIZED:
waitTxInBlockchain.done();
waitFiatStarted.done();
confirmFiatReceived.done();
showItem(payoutUnlock);
((ConfirmFiatReceivedView) tradeStepDetailsView).setStatusText("Sending message to trading peer transaction...");
((WaitPayoutLockTimeView) tradeStepDetailsView).setInfoLabelText("The payout transaction is signed and finalized by both parties." +
"\nFor reducing bank chargeback risks you need to wait until the payout gets unlocked to transfer your Bitcoin.");
break;
case SELLER_COMPLETED:
waitTxInBlockchain.done();
waitFiatStarted.done();
confirmFiatReceived.done();
payoutUnlock.done();
showItem(completed);
CompletedView completedView = (CompletedView) tradeStepDetailsView;

View File

@ -0,0 +1,132 @@
/*
* 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.gui.main.portfolio.pendingtrades.steps;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesViewModel;
import io.bitsquare.gui.util.Layout;
import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.BlockChainListener;
import org.bitcoinj.core.ScriptException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.VerificationException;
import java.util.List;
import javafx.scene.control.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.bitsquare.gui.util.ComponentBuilder.*;
public class WaitPayoutLockTimeView extends TradeStepDetailsView {
private static final Logger log = LoggerFactory.getLogger(WaitPayoutLockTimeView.class);
private final BlockChainListener blockChainListener;
private TextField blockTextField;
private Label infoLabel;
private TextField timeTextField;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialisation
///////////////////////////////////////////////////////////////////////////////////////////
public WaitPayoutLockTimeView(PendingTradesViewModel model) {
super(model);
blockChainListener = new BlockChainListener() {
@Override
public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
setLockTime(block.getHeight());
}
@Override
public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
setLockTime(model.getBestChainHeight());
}
@Override
public boolean isTransactionRelevant(Transaction tx) throws ScriptException {
return false;
}
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, AbstractBlockChain.NewBlockType blockType, int relativityOffset) throws
VerificationException {
}
@Override
public boolean notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, AbstractBlockChain.NewBlockType blockType, int relativityOffset)
throws VerificationException {
return false;
}
};
}
@Override
public void activate() {
super.activate();
model.addBlockChainListener(blockChainListener);
setLockTime(model.getBestChainHeight());
}
@Override
public void deactivate() {
super.deactivate();
model.removeBlockChainListener(blockChainListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setters
///////////////////////////////////////////////////////////////////////////////////////////
public void setInfoLabelText(String text) {
if (infoLabel != null)
infoLabel.setText(text);
}
private void setLockTime(int bestBlocKHeight) {
long missingBlocks = model.getLockTime() - (long) bestBlocKHeight;
blockTextField.setText(String.valueOf(missingBlocks));
timeTextField.setText(model.getUnlockDate(missingBlocks));
}
///////////////////////////////////////////////////////////////////////////////////////////
// Build view
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void buildGridEntries() {
getAndAddTitledGroupBg(gridPane, gridRow, 2, "Payout transaction lock time");
blockTextField = getAndAddLabelTextFieldPair(gridPane, gridRow++, "Block(s) to wait until unlock:", Layout.FIRST_ROW_DISTANCE).textField;
timeTextField = getAndAddLabelTextFieldPair(gridPane, gridRow++, "Approx. date when payout gets unlocked:").textField;
getAndAddTitledGroupBg(gridPane, gridRow, 1, "Information", Layout.GROUP_DISTANCE);
infoLabel = getAndAddInfoLabel(gridPane, gridRow++, Layout.FIRST_ROW_AND_GROUP_DISTANCE);
}
}

View File

@ -356,4 +356,11 @@ public class BSFormatter {
Double.parseDouble(input);
return input;
}
public String getUnlockDate(long missingBlocks) {
DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
DateFormat timeFormatter = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
Date unlockDate = new Date(new Date().getTime() + missingBlocks * 10 * 60 * 1000);
return dateFormatter.format(unlockDate) + " " + timeFormatter.format(unlockDate);
}
}

View File

@ -49,7 +49,7 @@ import static com.google.common.base.Preconditions.checkArgument;
public class OfferBook {
private static final Logger log = LoggerFactory.getLogger(OfferBook.class);
private static final int POLLING_INTERVAL = 5000; // in ms
private static final int POLLING_INTERVAL = 2000; // in ms
private final OfferBookService offerBookService;
private final User user;

View File

@ -252,7 +252,7 @@ public class FileManager<T> {
private void saveNowInternal(T serializable) {
long now = System.currentTimeMillis();
saveToFile(serializable, dir, storageFile);
log.info("Save {} completed in {}msec", storageFile, System.currentTimeMillis() - now);
Platform.runLater(() -> log.info("Save {} completed in {}msec", storageFile, System.currentTimeMillis() - now));
}
private void saveToFile(T serializable, File dir, File storageFile) {
@ -289,7 +289,6 @@ public class FileManager<T> {
renameTempFileToFile(tempFile, storageFile);
} catch (Throwable t) {
log.debug("storageFile " + storageFile.toString());
log.debug("currentThread " + Thread.currentThread());
t.printStackTrace();
log.error("Error at saveToFile: " + t.getMessage());
} finally {

View File

@ -169,10 +169,11 @@ abstract public class Trade implements Model, Serializable {
createProtocol();
if (mailboxMessage != null) {
tradeProtocol.applyMailboxMessage(mailboxMessage);
tradeProtocol.applyMailboxMessage(mailboxMessage, this);
// After applied to protocol we remove it
mailboxMessage = null;
}
tradeProtocol.checkPayoutTxTimeLock(this);
}
protected void initStateProperties() {
@ -333,6 +334,11 @@ abstract public class Trade implements Model, Serializable {
tradeVolumeProperty.set(getTradeVolume());
}
// TODO support case of multiple fiat accounts
public long getLockTimeDelta() {
return getOffer().getFiatAccountType().lockTimeDelta;
}
@Nullable
public Coin getTradeAmount() {
return tradeAmount;

View File

@ -54,7 +54,7 @@ public class BroadcastCreateOfferFeeTx extends Task<PlaceOfferModel> {
Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress());
if (balance.compareTo(totalsNeeded) >= 0) {
model.tradeWalletService.broadcastCreateOfferFeeTx(model.getTransaction(), new FutureCallback<Transaction>() {
model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString());

View File

@ -22,16 +22,17 @@ import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Peer;
import io.bitsquare.p2p.listener.SendMessageListener;
import io.bitsquare.trade.BuyerAsOffererTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.availability.messages.ReportOfferAvailabilityMessage;
import io.bitsquare.trade.protocol.availability.messages.RequestIsOfferAvailableMessage;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CommitPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateAndSignPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateDepositTxInputs;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendDepositTxPublishedMessage;
@ -41,6 +42,7 @@ import io.bitsquare.trade.protocol.trade.tasks.buyer.SignAndPublishDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.VerifyAndSignContract;
import io.bitsquare.trade.protocol.trade.tasks.offerer.VerifyTakeOfferFeePayment;
import io.bitsquare.trade.protocol.trade.tasks.offerer.VerifyTakerAccount;
import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener;
import io.bitsquare.trade.states.OffererTradeState;
import org.slf4j.Logger;
@ -72,13 +74,16 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void applyMailboxMessage(MailboxMessage mailboxMessage) {
public void applyMailboxMessage(MailboxMessage mailboxMessage, Trade trade) {
if (trade == null)
this.trade = trade;
log.debug("setMailboxMessage " + mailboxMessage);
// Might be called twice, so check that its only processed once
if (!processModel.isMailboxMessageProcessed()) {
processModel.mailboxMessageProcessed();
if (mailboxMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) mailboxMessage);
if (mailboxMessage instanceof PayoutTxFinalizedMessage) {
handle((PayoutTxFinalizedMessage) mailboxMessage);
}
}
}
@ -179,7 +184,7 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
// Incoming message handling
///////////////////////////////////////////////////////////////////////////////////////////
private void handle(PayoutTxPublishedMessage tradeMessage) {
private void handle(PayoutTxFinalizedMessage tradeMessage) {
processModel.setTradeMessage(tradeMessage);
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsOffererTrade,
@ -190,8 +195,11 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
},
this::handleTaskRunnerFault);
taskRunner.addTasks(ProcessPayoutTxPublishedMessage.class);
taskRunner.addTasks(CommitPayoutTx.class);
taskRunner.addTasks(
ProcessPayoutTxFinalizedMessage.class,
CommitPayoutTx.class,
SetupPayoutTxLockTimeReachedListener.class
);
taskRunner.run();
}
@ -216,8 +224,8 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
else if (tradeMessage instanceof RequestPublishDepositTxMessage) {
handle((RequestPublishDepositTxMessage) tradeMessage);
}
else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage);
else if (tradeMessage instanceof PayoutTxFinalizedMessage) {
handle((PayoutTxFinalizedMessage) tradeMessage);
}
else {
log.error("Incoming tradeMessage not supported. " + tradeMessage);

View File

@ -21,19 +21,21 @@ import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Peer;
import io.bitsquare.trade.BuyerAsTakerTrade;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxPublishedMessage;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CommitPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateAndSignPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.CreateDepositTxInputs;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.ProcessRequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SendRequestPayDepositMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.SignAndPublishDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.buyer.VerifyAndSignContract;
import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener;
import io.bitsquare.trade.protocol.trade.tasks.taker.BroadcastTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.CreateTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOfferFeePayment;
@ -69,13 +71,16 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void applyMailboxMessage(MailboxMessage mailboxMessage) {
public void applyMailboxMessage(MailboxMessage mailboxMessage, Trade trade) {
if (trade == null)
this.trade = trade;
log.debug("setMailboxMessage " + mailboxMessage);
// Might be called twice, so check that its only processed once
if (!processModel.isMailboxMessageProcessed()) {
processModel.mailboxMessageProcessed();
if (mailboxMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) mailboxMessage);
if (mailboxMessage instanceof PayoutTxFinalizedMessage) {
handle((PayoutTxFinalizedMessage) mailboxMessage);
}
}
}
@ -142,7 +147,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
// After peer has received Fiat tx
///////////////////////////////////////////////////////////////////////////////////////////
private void handle(PayoutTxPublishedMessage tradeMessage) {
private void handle(PayoutTxFinalizedMessage tradeMessage) {
processModel.setTradeMessage(tradeMessage);
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
@ -154,8 +159,9 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
this::handleTaskRunnerFault);
taskRunner.addTasks(
ProcessPayoutTxPublishedMessage.class,
CommitPayoutTx.class);
ProcessPayoutTxFinalizedMessage.class,
CommitPayoutTx.class,
SetupPayoutTxLockTimeReachedListener.class);
taskRunner.run();
}
@ -174,8 +180,8 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
if (tradeMessage instanceof RequestPublishDepositTxMessage) {
handle((RequestPublishDepositTxMessage) tradeMessage);
}
else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage);
else if (tradeMessage instanceof PayoutTxFinalizedMessage) {
handle((PayoutTxFinalizedMessage) tradeMessage);
}
else {
log.error("Incoming message not supported. " + tradeMessage);

View File

@ -69,7 +69,6 @@ public class ProcessModel implements Model, Serializable {
transient private boolean mailboxMessageProcessed;
transient private TradeMessage tradeMessage;
private String takeOfferFeeTxId;
private Transaction payoutTx;
private List<TransactionOutput> connectedOutputsForAllInputs;
private Coin payoutAmount;
private Transaction preparedDepositTx;
@ -166,15 +165,6 @@ public class ProcessModel implements Model, Serializable {
return mailboxMessageProcessed;
}
@Nullable
public Transaction getPayoutTx() {
return payoutTx;
}
public void setPayoutTx(Transaction payoutTx) {
this.payoutTx = payoutTx;
}
@Nullable
public Transaction getTakeOfferFeeTx() {
return takeOfferFeeTx;

View File

@ -22,6 +22,7 @@ import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Peer;
import io.bitsquare.p2p.listener.SendMessageListener;
import io.bitsquare.trade.SellerAsOffererTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.availability.messages.ReportOfferAvailabilityMessage;
import io.bitsquare.trade.protocol.availability.messages.RequestIsOfferAvailableMessage;
import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage;
@ -36,9 +37,10 @@ import io.bitsquare.trade.protocol.trade.tasks.seller.CreateAndSignDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessRequestPayDepositMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendRequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndPublishPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndFinalizePayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener;
import io.bitsquare.trade.states.OffererTradeState;
import org.slf4j.Logger;
@ -71,7 +73,10 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void applyMailboxMessage(MailboxMessage mailboxMessage) {
public void applyMailboxMessage(MailboxMessage mailboxMessage, Trade trade) {
if (trade == null)
this.trade = trade;
log.debug("setMailboxMessage " + mailboxMessage);
// Might be called twice, so check that its only processed once
if (!processModel.isMailboxMessageProcessed()) {
@ -201,8 +206,9 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
taskRunner.addTasks(
VerifyTakeOfferFeePayment.class,
SignAndPublishPayoutTx.class,
SendPayoutTxPublishedMessage.class
SignAndFinalizePayoutTx.class,
SendPayoutTxFinalizedMessage.class,
SetupPayoutTxLockTimeReachedListener.class
);
taskRunner.run();
}

View File

@ -21,6 +21,7 @@ import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.Peer;
import io.bitsquare.trade.SellerAsTakerTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.FiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.messages.RequestPayDepositMessage;
@ -31,10 +32,11 @@ import io.bitsquare.trade.protocol.trade.tasks.seller.CreateAndSignDepositTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessDepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessFiatTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.ProcessRequestPayDepositMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendPayoutTxFinalizedMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendRequestDepositTxInputsMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SendRequestPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndPublishPayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.seller.SignAndFinalizePayoutTx;
import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener;
import io.bitsquare.trade.protocol.trade.tasks.taker.BroadcastTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.CreateTakeOfferFeeTx;
import io.bitsquare.trade.protocol.trade.tasks.taker.VerifyOfferFeePayment;
@ -71,7 +73,10 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void applyMailboxMessage(MailboxMessage mailboxMessage) {
public void applyMailboxMessage(MailboxMessage mailboxMessage, Trade trade) {
if (trade == null)
this.trade = trade;
log.debug("setMailboxMessage " + mailboxMessage);
// Might be called twice, so check that its only processed once
if (!processModel.isMailboxMessageProcessed()) {
@ -177,8 +182,9 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
taskRunner.addTasks(
VerifyOfferFeePayment.class,
SignAndPublishPayoutTx.class,
SendPayoutTxPublishedMessage.class
SignAndFinalizePayoutTx.class,
SendPayoutTxFinalizedMessage.class,
SetupPayoutTxLockTimeReachedListener.class
);
taskRunner.run();
}

View File

@ -19,8 +19,10 @@ package io.bitsquare.trade.protocol.trade;
import io.bitsquare.p2p.MailboxMessage;
import io.bitsquare.p2p.MessageHandler;
import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.TakerTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.shared.SetupPayoutTxLockTimeReachedListener;
import io.bitsquare.trade.states.OffererTradeState;
import io.bitsquare.trade.states.TakerTradeState;
@ -51,7 +53,32 @@ public abstract class TradeProtocol {
processModel.getMessageService().removeMessageHandler(messageHandler);
}
abstract public void applyMailboxMessage(MailboxMessage mailboxMessage);
abstract public void applyMailboxMessage(MailboxMessage mailboxMessage, Trade trade);
public void checkPayoutTxTimeLock(Trade trade) {
if (trade == null)
this.trade = trade;
boolean needPayoutTxBroadcast = false;
if (trade instanceof TakerTrade)
needPayoutTxBroadcast = trade.processStateProperty().get() == TakerTradeState.ProcessState.PAYOUT_FINALIZED
|| trade.processStateProperty().get() == TakerTradeState.ProcessState.PAYOUT_FINALIZED_MSG_SENT;
else if (trade instanceof OffererTrade)
needPayoutTxBroadcast = trade.processStateProperty().get() == OffererTradeState.ProcessState.PAYOUT_FINALIZED
|| trade.processStateProperty().get() == OffererTradeState.ProcessState.PAYOUT_FINALIZED_MSG_SENT;
if (needPayoutTxBroadcast) {
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> {
log.debug("taskRunner needPayoutTxBroadcast completed");
processModel.onComplete();
},
this::handleTaskRunnerFault);
taskRunner.addTasks(SetupPayoutTxLockTimeReachedListener.class);
taskRunner.run();
}
}
protected void startTimeout() {
log.debug("startTimeout");

View File

@ -26,13 +26,13 @@ import java.io.Serializable;
import javax.annotation.concurrent.Immutable;
@Immutable
public class PayoutTxPublishedMessage extends TradeMessage implements MailboxMessage, Serializable {
public class PayoutTxFinalizedMessage extends TradeMessage implements MailboxMessage, Serializable {
// That object is sent over the wire, so we need to take care of version compatibility.
private static final long serialVersionUID = 1L;
public final Transaction payoutTx;
public PayoutTxPublishedMessage(String tradeId, Transaction payoutTx) {
public PayoutTxFinalizedMessage(String tradeId, Transaction payoutTx) {
super(tradeId);
this.payoutTx = payoutTx;
}

View File

@ -47,6 +47,7 @@ public class CreateAndSignPayoutTx extends TradeTask {
sellerPayoutAmount,
processModel.getAddressEntry(),
processModel.tradingPeer.getPayoutAddressString(),
trade.getLockTimeDelta(),
processModel.getTradeWalletPubKey(),
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getArbitratorPubKey());

View File

@ -22,7 +22,7 @@ import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.TakerTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.TradeTask;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxFinalizedMessage;
import io.bitsquare.trade.states.OffererTradeState;
import io.bitsquare.trade.states.TakerTradeState;
@ -32,26 +32,26 @@ import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.bitsquare.util.Validator.checkTradeId;
public class ProcessPayoutTxPublishedMessage extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(ProcessPayoutTxPublishedMessage.class);
public class ProcessPayoutTxFinalizedMessage extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(ProcessPayoutTxFinalizedMessage.class);
public ProcessPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
public ProcessPayoutTxFinalizedMessage(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void doRun() {
try {
PayoutTxPublishedMessage message = (PayoutTxPublishedMessage) processModel.getTradeMessage();
PayoutTxFinalizedMessage message = (PayoutTxFinalizedMessage) processModel.getTradeMessage();
checkTradeId(processModel.getId(), message);
checkNotNull(message);
trade.setPayoutTx(checkNotNull(message.payoutTx));
if (trade instanceof OffererTrade)
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_PUBLISHED);
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_FINALIZED);
else if (trade instanceof TakerTrade)
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_PUBLISHED);
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_FINALIZED);
complete();
} catch (Throwable t) {

View File

@ -23,7 +23,7 @@ import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.TakerTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.TradeTask;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.messages.PayoutTxFinalizedMessage;
import io.bitsquare.trade.states.OffererTradeState;
import io.bitsquare.trade.states.StateUtil;
import io.bitsquare.trade.states.TakerTradeState;
@ -31,17 +31,17 @@ import io.bitsquare.trade.states.TakerTradeState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SendPayoutTxPublishedMessage extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SendPayoutTxPublishedMessage.class);
public class SendPayoutTxFinalizedMessage extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SendPayoutTxFinalizedMessage.class);
public SendPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
public SendPayoutTxFinalizedMessage(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void doRun() {
try {
PayoutTxPublishedMessage tradeMessage = new PayoutTxPublishedMessage(processModel.getId(), processModel.getPayoutTx());
PayoutTxFinalizedMessage tradeMessage = new PayoutTxFinalizedMessage(processModel.getId(), trade.getPayoutTx());
processModel.getMessageService().sendMessage(trade.getTradingPeer(),
tradeMessage,
processModel.tradingPeer.getP2pSigPubKey(),
@ -52,9 +52,9 @@ public class SendPayoutTxPublishedMessage extends TradeTask {
log.trace("PayoutTxPublishedMessage successfully arrived at peer");
if (trade instanceof TakerTrade)
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_PUBLISHED_MSG_SENT);
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_FINALIZED_MSG_SENT);
else if (trade instanceof OffererTrade)
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_PUBLISHED_MSG_SENT);
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_FINALIZED_MSG_SENT);
complete();
}

View File

@ -27,53 +27,39 @@ import io.bitsquare.trade.states.TakerTradeState;
import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SignAndPublishPayoutTx extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SignAndPublishPayoutTx.class);
public class SignAndFinalizePayoutTx extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SignAndFinalizePayoutTx.class);
public SignAndPublishPayoutTx(TaskRunner taskHandler, Trade trade) {
public SignAndFinalizePayoutTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void doRun() {
try {
processModel.getTradeWalletService().signAndPublishPayoutTx(
Transaction transaction = processModel.getTradeWalletService().signAndFinalizePayoutTx(
trade.getDepositTx(),
processModel.tradingPeer.getSignature(),
processModel.tradingPeer.getPayoutAmount(),
processModel.getPayoutAmount(),
processModel.tradingPeer.getPayoutAddressString(),
processModel.getAddressEntry(),
trade.getLockTimeDelta(),
processModel.tradingPeer.getTradeWalletPubKey(),
processModel.getTradeWalletPubKey(),
processModel.getArbitratorPubKey(),
new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
processModel.setPayoutTx(transaction);
processModel.getArbitratorPubKey()
);
if (trade instanceof TakerTrade)
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_PUBLISHED);
else if (trade instanceof OffererTrade)
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_PUBLISHED);
trade.setPayoutTx(transaction);
if (trade instanceof TakerTrade)
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_FINALIZED);
else if (trade instanceof OffererTrade)
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_FINALIZED);
complete();
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
trade.setThrowable(t);
failed(t);
}
});
complete();
} catch (Throwable t) {
t.printStackTrace();
trade.setThrowable(t);

View File

@ -0,0 +1,105 @@
/*
* 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.tasks.shared;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.TakerTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.TradeTask;
import io.bitsquare.trade.states.OffererTradeState;
import io.bitsquare.trade.states.TakerTradeState;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.ExecutionException;
import javafx.application.Platform;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SetupPayoutTxLockTimeReachedListener extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(SetupPayoutTxLockTimeReachedListener.class);
public SetupPayoutTxLockTimeReachedListener(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void doRun() {
try {
if (processModel.getTradeWalletService().getBestChainHeight() >= trade.getPayoutTx().getLockTime()) {
broadcastTx();
}
else {
ListenableFuture<StoredBlock> blockHeightFuture = processModel.getTradeWalletService().getBlockHeightFuture(trade.getPayoutTx());
blockHeightFuture.addListener(
() -> {
try {
log.debug("Block height reached " + blockHeightFuture.get().getHeight());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
broadcastTx();
},
Platform::runLater);
}
} catch (Throwable t) {
t.printStackTrace();
trade.setThrowable(t);
failed(t);
}
}
protected void broadcastTx() {
processModel.getTradeWalletService().broadcastTx(trade.getPayoutTx(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.debug("BroadcastTx succeeded. Transaction:" + transaction);
if (trade instanceof TakerTrade)
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_BROAD_CASTED);
else if (trade instanceof OffererTrade)
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_BROAD_CASTED);
complete();
}
@Override
public void onFailure(@NotNull Throwable t) {
t.printStackTrace();
trade.setThrowable(t);
if (trade instanceof TakerTrade)
trade.setProcessState(TakerTradeState.ProcessState.PAYOUT_BROAD_CASTED_FAILED);
else if (trade instanceof OffererTrade)
trade.setProcessState(OffererTradeState.ProcessState.PAYOUT_BROAD_CASTED_FAILED);
failed(t);
}
});
}
}

View File

@ -42,7 +42,7 @@ public class BroadcastTakeOfferFeeTx extends TradeTask {
@Override
protected void doRun() {
try {
processModel.getTradeWalletService().broadcastTakeOfferFeeTx(processModel.getTakeOfferFeeTx(),
processModel.getTradeWalletService().broadcastTx(processModel.getTakeOfferFeeTx(),
new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {

View File

@ -40,9 +40,11 @@ public class OffererTradeState {
FIAT_PAYMENT_STARTED,
FIAT_PAYMENT_RECEIVED,
PAYOUT_PUBLISHED,
PAYOUT_PUBLISHED_MSG_SENT,
PAYOUT_FINALIZED,
PAYOUT_FINALIZED_MSG_SENT,
PAYOUT_BROAD_CASTED,
PAYOUT_BROAD_CASTED_FAILED,
MESSAGE_SENDING_FAILED,
TIMEOUT,
EXCEPTION

View File

@ -41,8 +41,10 @@ public class TakerTradeState {
FIAT_PAYMENT_STARTED,
FIAT_PAYMENT_RECEIVED,
PAYOUT_PUBLISHED,
PAYOUT_PUBLISHED_MSG_SENT,
PAYOUT_FINALIZED,
PAYOUT_FINALIZED_MSG_SENT,
PAYOUT_BROAD_CASTED,
PAYOUT_BROAD_CASTED_FAILED,
MESSAGE_SENDING_FAILED,
TIMEOUT,

View File

@ -34,12 +34,13 @@
<logger name="io.bitsquare.gui.util.Profiler" level="ERROR"/>
<logger name="io.bitsquare.locale.BSResources" level="ERROR"/>
<logger name="org.bitcoinj" level="INFO"/>
<logger name="net.tomp2p" level="INFO"/>
<logger name="org.bitcoinj" level="WARN"/>
<logger name="net.tomp2p" level="WARN"/>
<logger name="org.bitcoinj.core.BitcoinSerializer" level="WARN"/>
<logger name="org.bitcoinj.core.Peer" level="WARN"/>
<!--
<logger name="com.vinumeris.updatefx" level="OFF"/>
<logger name="io.netty" level="OFF"/>