From d39ae903ffb18c51ed3d4f75e6b1f924897f2355 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 19 May 2014 19:16:11 +0200 Subject: [PATCH] payment process summary screen offerer, process imporved --- src/main/java/io/bitsquare/btc/Fees.java | 2 +- .../java/io/bitsquare/btc/WalletFacade.java | 41 ++- .../java/io/bitsquare/di/BitSquareModule.java | 62 +++- .../createOffer/CreateOfferController.java | 2 +- .../market/trade/TakerTradeController.java | 17 +- .../gui/orders/OrdersController.java | 285 +++++++++++------- .../io/bitsquare/gui/orders/OrdersView.fxml | 33 +- .../bitsquare/gui/setup/SetupController.java | 2 +- .../bitsquare/gui/util/ConfidenceDisplay.java | 54 +++- .../java/io/bitsquare/msg/TradeMessage.java | 26 +- src/main/java/io/bitsquare/trade/Trade.java | 56 +++- src/main/java/io/bitsquare/trade/Trading.java | 6 +- .../trade/offerer/OffererPaymentProtocol.java | 8 +- .../trade/taker/TakerPaymentProtocol.java | 11 +- src/main/resources/logback.xml | 21 +- 15 files changed, 428 insertions(+), 198 deletions(-) diff --git a/src/main/java/io/bitsquare/btc/Fees.java b/src/main/java/io/bitsquare/btc/Fees.java index 16ff4a1ef7..e27a50707c 100644 --- a/src/main/java/io/bitsquare/btc/Fees.java +++ b/src/main/java/io/bitsquare/btc/Fees.java @@ -8,7 +8,7 @@ import java.math.BigInteger; public class Fees { // min dust value lead to exception at for non standard to address pay scripts, so we use a value >= 7860 instead - public static BigInteger MS_TX_FEE = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; + public static BigInteger TX_FEE = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; public static BigInteger ACCOUNT_REGISTRATION_FEE = Utils.toNanoCoins("0.0002"); public static BigInteger OFFER_CREATION_FEE = Utils.toNanoCoins("0.0002"); public static BigInteger OFFER_TAKER_FEE = OFFER_CREATION_FEE; diff --git a/src/main/java/io/bitsquare/btc/WalletFacade.java b/src/main/java/io/bitsquare/btc/WalletFacade.java index 4357bb4c09..04187dbbef 100644 --- a/src/main/java/io/bitsquare/btc/WalletFacade.java +++ b/src/main/java/io/bitsquare/btc/WalletFacade.java @@ -91,7 +91,7 @@ public class WalletFacade // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen // or progress widget to keep the user engaged whilst we initialise, but we don't. - walletAppKit.setDownloadListener(new BlockChainDownloadListener()).setBlockingStartup(false).setUserAgent(BitSquare.ID, "0.1"); + walletAppKit.setDownloadListener(new BlockChainDownloadListener()).setBlockingStartup(false).setUserAgent("BitSquare", "0.1"); walletAppKit.startAsync(); walletAppKit.awaitRunning(); @@ -190,7 +190,8 @@ public class WalletFacade Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); // give fee to miners yet. Later it could be spent to other traders via lottery... - sendRequest.fee = Fees.ACCOUNT_REGISTRATION_FEE.subtract(Transaction.MIN_NONDUST_OUTPUT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); + sendRequest.fee = Fees.ACCOUNT_REGISTRATION_FEE.subtract(Transaction.MIN_NONDUST_OUTPUT).subtract(Fees.TX_FEE); + log.trace("sendRequest.fee: " + Utils.bitcoinValueToFriendlyString(sendRequest.fee)); Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); log.debug("Registration transaction: " + tx.toString()); printInputs("publishRegistrationTxWithExtraData", tx); @@ -215,6 +216,8 @@ public class WalletFacade // Trade process /////////////////////////////////////////////////////////////////////////////////////////// + //TODO refactor to similar solution like in PaymentChannelServerState + public String payOfferFee(BigInteger fee, FutureCallback callback) throws InsufficientMoneyException { log.debug("payOfferFee fee=" + Utils.bitcoinValueToFriendlyString(fee)); @@ -222,7 +225,7 @@ public class WalletFacade tx.addOutput(Transaction.MIN_NONDUST_OUTPUT, WalletUtil.getEmptyOP_RETURNScript()); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); - sendRequest.fee = fee.subtract(Transaction.MIN_NONDUST_OUTPUT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); + sendRequest.fee = fee.subtract(Transaction.MIN_NONDUST_OUTPUT).subtract(Fees.TX_FEE); Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); Futures.addCallback(sendResult.broadcastComplete, callback); @@ -344,11 +347,14 @@ public class WalletFacade tx.addOutput(tempTx.getOutput(1)); // We add the btc tx fee to the msOutputAmount and apply the change to the multiSig output - msOutputAmount = msOutputAmount.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); + msOutputAmount = msOutputAmount.add(Fees.TX_FEE); tx.getOutput(0).setValue(msOutputAmount); // Now we sign our input TransactionInput input = tx.getInput(1); + if (input == null || input.getConnectedOutput() == null) + log.error("input or input.getConnectedOutput() is null: " + input); + Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); Sha256Hash hash = tx.hashForSignature(1, scriptPubKey, Transaction.SigHash.ALL, false); @@ -436,11 +442,11 @@ public class WalletFacade //TODO handle non change output cases // add outputs from takers tx, they are already correct - - for (int i = 0; i < takersSignedTx.getOutputs().size(); i++) - { - tx.addOutput(takersSignedTx.getOutput(i)); - } + tx.addOutput(takersSignedTx.getOutput(0)); + if (takersSignedTx.getOutputs().size() > 1) + tx.addOutput(takersSignedTx.getOutput(1)); + if (takersSignedTx.getOutputs().size() == 3) + tx.addOutput(takersSignedTx.getOutput(2)); printInputs("tx", tx); log.trace("tx = " + tx.toString()); @@ -448,6 +454,9 @@ public class WalletFacade // sign the input TransactionInput input = tx.getInput(0); + if (input == null || input.getConnectedOutput() == null) + log.error("input or input.getConnectedOutput() is null: " + input); + Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); Sha256Hash hash = tx.hashForSignature(0, scriptPubKey, Transaction.SigHash.ALL, false); @@ -503,7 +512,19 @@ public class WalletFacade log.trace("depositTxID=" + depositTxAsHex); Transaction depositTx = new Transaction(params, Utils.parseAsHexOrBase58(depositTxAsHex)); log.trace("depositTx=" + depositTx); - wallet.commitTx(depositTx); + // boolean isAlreadyInWallet = wallet.maybeCommitTx(depositTx); + //log.trace("isAlreadyInWallet=" + isAlreadyInWallet); + + try + { + // Manually add the multisigContract to the wallet, overriding the isRelevant checks so we can track + // it and check for double-spends later + wallet.receivePending(depositTx, null, true); + } catch (VerificationException e) + { + throw new RuntimeException(e); // Cannot happen, we already called multisigContract.verify() + } + } // 5. step payout tx: Offerer creates payout tx and signs it diff --git a/src/main/java/io/bitsquare/di/BitSquareModule.java b/src/main/java/io/bitsquare/di/BitSquareModule.java index 7ad4ac2668..cafc23c0e9 100644 --- a/src/main/java/io/bitsquare/di/BitSquareModule.java +++ b/src/main/java/io/bitsquare/di/BitSquareModule.java @@ -2,10 +2,10 @@ package io.bitsquare.di; import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.core.Utils; import com.google.bitcoin.kits.WalletAppKit; import com.google.bitcoin.params.MainNetParams; import com.google.bitcoin.params.RegTestParams; -import com.google.bitcoin.params.TestNet3Params; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Provider; @@ -22,9 +22,12 @@ import io.bitsquare.trade.orderbook.OrderBook; import io.bitsquare.trade.orderbook.OrderBookFilter; import io.bitsquare.user.User; import io.bitsquare.util.Utilities; +import org.spongycastle.util.encoders.Hex; import java.io.File; +import static com.google.common.base.Preconditions.checkState; + public class BitSquareModule extends AbstractModule { @@ -88,7 +91,7 @@ class NetworkParametersProvider implements Provider result = MainNetParams.get(); break; case WalletFacade.TEST_NET: - result = TestNet3Params.get(); + result = TestNet3Params2.get(); break; case WalletFacade.REG_TEST_NET: result = RegTestParams.get(); @@ -96,4 +99,57 @@ class NetworkParametersProvider implements Provider } return result; } -} \ No newline at end of file +} + + +/** + * UnknownHostException with testnet-seed.bitcoin.petertodd.org so use testnet-seed.bluematt.me as primary DND seed server + * testnet-seed.bluematt.me delivers 1 dead node, so nothing works yet... ;-( + * http://sourceforge.net/p/bitcoin/mailman/message/32349208/ + */ +class TestNet3Params2 extends NetworkParameters +{ + public TestNet3Params2() + { + super(); + id = ID_TESTNET; + // Genesis hash is 000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943 + packetMagic = 0x0b110907; + interval = INTERVAL; + targetTimespan = TARGET_TIMESPAN; + proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL); + port = 18333; + addressHeader = 111; + p2shHeader = 196; + acceptableAddressCodes = new int[]{addressHeader, p2shHeader}; + dumpedPrivateKeyHeader = 239; + genesisBlock.setTime(1296688602L); + genesisBlock.setDifficultyTarget(0x1d00ffffL); + genesisBlock.setNonce(414098458); + spendableCoinbaseDepth = 100; + subsidyDecreaseBlockCount = 210000; + String genesisHash = genesisBlock.getHashAsString(); + checkState(genesisHash.equals("000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")); + alertSigningKey = Hex.decode("04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a"); + + dnsSeeds = new String[]{ + "testnet-seed.bluematt.me" + }; + } + + private static TestNet3Params2 instance; + + public static synchronized TestNet3Params2 get() + { + if (instance == null) + { + instance = new TestNet3Params2(); + } + return instance; + } + + public String getPaymentProtocolId() + { + return PAYMENT_PROTOCOL_ID_TESTNET; + } +} diff --git a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java index 46fd79b204..48f87cc9aa 100644 --- a/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java +++ b/src/main/java/io/bitsquare/gui/market/createOffer/CreateOfferController.java @@ -94,7 +94,7 @@ public class CreateOfferController implements Initializable, ChildController //TODO - amountTextField.setText("0,1"); + amountTextField.setText("0,001"); minAmountTextField.setText("0,001"); priceTextField.setText("300"); collateralTextField.setText("50"); diff --git a/src/main/java/io/bitsquare/gui/market/trade/TakerTradeController.java b/src/main/java/io/bitsquare/gui/market/trade/TakerTradeController.java index f7fb8a6437..862755f8ff 100644 --- a/src/main/java/io/bitsquare/gui/market/trade/TakerTradeController.java +++ b/src/main/java/io/bitsquare/gui/market/trade/TakerTradeController.java @@ -171,7 +171,7 @@ public class TakerTradeController implements Initializable, ChildController totalLabel = FormBuilder.addTextField(gridPane, "Total (" + offer.getCurrency() + "):", Formatter.formatVolume(getVolume()), ++row); collateralTextField = FormBuilder.addTextField(gridPane, "Collateral (BTC):", "", ++row); applyCollateral(); - FormBuilder.addTextField(gridPane, "Offer fee (BTC):", Utils.bitcoinValueToFriendlyString(Fees.OFFER_TAKER_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)), ++row); + FormBuilder.addTextField(gridPane, "Offer fee (BTC):", Utils.bitcoinValueToFriendlyString(Fees.OFFER_TAKER_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(Fees.TX_FEE)), ++row); totalToPayLabel = FormBuilder.addTextField(gridPane, "Total to pay (BTC):", getTotalToPay(), ++row); isOnlineTextField = FormBuilder.addTextField(gridPane, "Online status:", "Checking offerers online status...", ++row); @@ -361,14 +361,19 @@ public class TakerTradeController implements Initializable, ChildController gridPane.getChildren().clear(); row = -1; FormBuilder.addHeaderLabel(gridPane, "Trade successfully completed", ++row); - FormBuilder.addTextField(gridPane, "You have payed in total (BTC):", getTotalToPay(), ++row); + + String fiatReceived = Formatter.formatVolume(trade.getOffer().getPrice() * trade.getTradeAmount().doubleValue()); + + FormBuilder.addTextField(gridPane, "You have sold (BTC):", Utils.bitcoinValueToFriendlyString(trade.getTradeAmount()), ++row); if (takerIsSelling()) { - FormBuilder.addTextField(gridPane, "You got returned collateral (BTC):", BtcFormatter.formatSatoshis(getCollateralInSatoshis(), false), ++row); - FormBuilder.addTextField(gridPane, "You have received (" + offer.getCurrency() + "):", Formatter.formatVolume(getVolume()), ++row); + FormBuilder.addTextField(gridPane, "You have received (" + offer.getCurrency() + "):\"", fiatReceived, ++row); + FormBuilder.addTextField(gridPane, "Total fees (take offer fee + tx fee):", Utils.bitcoinValueToFriendlyString(Fees.OFFER_TAKER_FEE.add(Fees.TX_FEE)), ++row); + FormBuilder.addTextField(gridPane, "Refunded collateral:", Utils.bitcoinValueToFriendlyString(trade.getCollateralAmount()), ++row); } else { + //TODO FormBuilder.addTextField(gridPane, "You got returned collateral (BTC):", BtcFormatter.formatSatoshis(getCollateralInSatoshis(), false), ++row); FormBuilder.addTextField(gridPane, "You have received (" + offer.getCurrency() + "):", Formatter.formatVolume(getVolume()), ++row); FormBuilder.addTextField(gridPane, "You have received (BTC):", BtcFormatter.formatSatoshis(offer.getAmount(), false), ++row); @@ -433,11 +438,11 @@ public class TakerTradeController implements Initializable, ChildController { if (takerIsSelling()) { - return BtcFormatter.formatSatoshis(getAmountInSatoshis().add(Fees.OFFER_TAKER_FEE).add(Transaction.MIN_NONDUST_OUTPUT).add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).add(getCollateralInSatoshis()), false); + return BtcFormatter.formatSatoshis(getAmountInSatoshis().add(Fees.OFFER_TAKER_FEE).add(Transaction.MIN_NONDUST_OUTPUT).add(Fees.TX_FEE).add(getCollateralInSatoshis()), false); } else { - return BtcFormatter.formatSatoshis(Fees.OFFER_TAKER_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).add(getCollateralInSatoshis()), false) + "\n" + + return BtcFormatter.formatSatoshis(Fees.OFFER_TAKER_FEE.add(Transaction.MIN_NONDUST_OUTPUT).add(Fees.TX_FEE).add(getCollateralInSatoshis()), false) + "\n" + Formatter.formatVolume(getVolume(), offer.getCurrency()); } } diff --git a/src/main/java/io/bitsquare/gui/orders/OrdersController.java b/src/main/java/io/bitsquare/gui/orders/OrdersController.java index 6f7180238c..7d5820690f 100644 --- a/src/main/java/io/bitsquare/gui/orders/OrdersController.java +++ b/src/main/java/io/bitsquare/gui/orders/OrdersController.java @@ -1,19 +1,18 @@ package io.bitsquare.gui.orders; -import com.google.bitcoin.core.ECKey; -import com.google.bitcoin.core.Transaction; -import com.google.bitcoin.core.Wallet; -import com.google.bitcoin.core.WalletEventListener; +import com.google.bitcoin.core.*; import com.google.bitcoin.script.Script; import com.google.inject.Inject; import de.jensd.fx.fontawesome.AwesomeDude; import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccountType; +import io.bitsquare.btc.Fees; import io.bitsquare.btc.WalletFacade; import io.bitsquare.gui.ChildController; import io.bitsquare.gui.NavigationController; import io.bitsquare.gui.util.ConfidenceDisplay; +import io.bitsquare.gui.util.Formatter; import io.bitsquare.gui.util.Icons; import io.bitsquare.gui.util.Localisation; import io.bitsquare.trade.Direction; @@ -36,6 +35,7 @@ import javafx.scene.image.ImageView; import javafx.scene.input.Clipboard; import javafx.scene.input.ClipboardContent; import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; import javafx.util.Callback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,11 +51,14 @@ public class OrdersController implements Initializable, ChildController private Trading trading; private WalletFacade walletFacade; private Trade currentTrade; - + private NavigationController navigationController; private Image buyIcon = Icons.getIconImage(Icons.BUY); private Image sellIcon = Icons.getIconImage(Icons.SELL); private ConfidenceDisplay confidenceDisplay; + + @FXML + private VBox rootContainer; @FXML private TableView openTradesTable; @FXML @@ -63,7 +66,8 @@ public class OrdersController implements Initializable, ChildController @FXML private ProgressIndicator progressIndicator; @FXML - private Label confirmationLabel, txIDCopyIcon, holderNameCopyIcon, primaryBankAccountIDCopyIcon, secondaryBankAccountIDCopyIcon; + private Label txTitleLabel, txHeaderLabel, confirmationLabel, txIDCopyIcon, holderNameCopyIcon, primaryBankAccountIDCopyIcon, secondaryBankAccountIDCopyIcon, bankAccountDetailsHeaderLabel, + bankAccountTypeTitleLabel, holderNameTitleLabel, primaryBankAccountIDTitleLabel, secondaryBankAccountIDTitleLabel; @FXML private TextField txTextField, bankAccountTypeTextField, holderNameTextField, primaryBankAccountIDTextField, secondaryBankAccountIDTextField; @FXML @@ -82,6 +86,10 @@ public class OrdersController implements Initializable, ChildController } + /////////////////////////////////////////////////////////////////////////////////////////// + // Interface implementation: Initializable + /////////////////////////////////////////////////////////////////////////////////////////// + @Override public void initialize(URL url, ResourceBundle rb) { @@ -135,19 +143,19 @@ public class OrdersController implements Initializable, ChildController { openTradesTable.getSelectionModel().select(0); } - } }); } - private void showTradeDetails(TradesTableItem tradesTableItem) - { - fillData(tradesTableItem.getTrade()); - } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Interface implementation: ChildController + /////////////////////////////////////////////////////////////////////////////////////////// @Override public void setNavigationController(NavigationController navigationController) { + this.navigationController = navigationController; } @Override @@ -155,74 +163,83 @@ public class OrdersController implements Initializable, ChildController { } + + /////////////////////////////////////////////////////////////////////////////////////////// + // GUI handlers + /////////////////////////////////////////////////////////////////////////////////////////// + public void bankTransferInited(ActionEvent actionEvent) { trading.onBankTransferInited(currentTrade.getUid()); bankTransferInitedButton.setDisable(true); } - private void updateTx(Trade trade) + public void close(ActionEvent actionEvent) { - Transaction transaction = trade.getDepositTransaction(); - if (transaction != null) - { - confirmationLabel.setVisible(true); - progressIndicator.setVisible(true); - progressIndicator.setProgress(-1); - - txTextField.setText(transaction.getHashAsString()); - - confidenceDisplay = new ConfidenceDisplay(walletFacade.getWallet(), confirmationLabel, transaction, progressIndicator); - - int depthInBlocks = transaction.getConfidence().getDepthInBlocks(); - bankTransferInitedButton.setDisable(depthInBlocks == 0); - - walletFacade.getWallet().addEventListener(new WalletEventListener() - { - @Override - public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) - { - int depthInBlocks = tx.getConfidence().getDepthInBlocks(); - bankTransferInitedButton.setDisable(depthInBlocks == 0); - } - - @Override - public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) - { - } - - @Override - public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) - { - } - - @Override - public void onReorganize(Wallet wallet) - { - } - - - @Override - public void onWalletChanged(Wallet wallet) - { - } - - @Override - public void onKeysAdded(Wallet wallet, List keys) - { - } - - @Override - public void onScriptsAdded(Wallet wallet, List