From 7ce5beb54f2dc961e0bd00d3a39a813bf44edc7f Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 6 Apr 2016 00:28:43 +0200 Subject: [PATCH] Reorganize savingswallet --- .../bitsquare/arbitration/DisputeManager.java | 7 +- .../java/io/bitsquare/btc/AddressEntry.java | 38 +- .../bitsquare/btc/AddressEntryException.java | 7 + .../io/bitsquare/btc/AddressEntryList.java | 26 +- .../bitsquare/btc/BitsquareCoinSelector.java | 130 +++++++ .../main/java/io/bitsquare/btc/FeePolicy.java | 4 +- ...r.java => MultiAddressesCoinSelector.java} | 54 +-- .../btc/SavingsWalletCoinSelector.java | 137 ++----- .../btc/TradeWalletCoinSelector.java | 64 ++++ .../io/bitsquare/btc/TradeWalletService.java | 99 +++--- .../java/io/bitsquare/btc/WalletService.java | 176 ++++----- .../io/bitsquare/trade/TradableHelper.java | 101 +----- .../java/io/bitsquare/trade/TradeManager.java | 8 +- .../trade/offer/OpenOfferManager.java | 4 +- .../placeoffer/tasks/CreateOfferFeeTx.java | 9 +- .../trade/protocol/trade/ProcessModel.java | 14 - .../trade/protocol/trade/TradingPeer.java | 10 +- .../trade/messages/PayDepositRequest.java | 10 +- .../messages/PublishDepositTxRequest.java | 8 +- ...ffererCreatesAndSignsDepositTxAsBuyer.java | 15 +- .../buyer/SendFiatTransferStartedMessage.java | 3 +- .../tasks/buyer/SignAndFinalizePayoutTx.java | 8 +- .../buyer/SignAndPublishDepositTxAsBuyer.java | 21 +- .../TakerCreatesDepositTxInputsAsBuyer.java | 8 +- .../tasks/offerer/CreateAndSignContract.java | 7 +- .../offerer/ProcessPayDepositRequest.java | 2 +- .../offerer/SendPublishDepositTxRequest.java | 5 +- .../offerer/SetupDepositBalanceListener.java | 3 +- ...fererCreatesAndSignsDepositTxAsSeller.java | 15 +- .../seller/SendFinalizePayoutTxRequest.java | 3 +- .../SignAndPublishDepositTxAsSeller.java | 22 +- .../trade/tasks/seller/SignPayoutTx.java | 11 +- .../TakerCreatesDepositTxInputsAsSeller.java | 8 +- .../tasks/taker/CreateTakeOfferFeeTx.java | 9 +- .../taker/ProcessPublishDepositTxRequest.java | 2 +- .../tasks/taker/SendPayDepositRequest.java | 6 +- .../tasks/taker/VerifyAndSignContract.java | 7 +- .../java/io/bitsquare/app/BitsquareApp.java | 1 - .../io/bitsquare/gui/main/MainViewModel.java | 66 +--- .../ArbitratorRegistrationViewModel.java | 2 +- .../bitsquare/gui/main/funds/FundsView.fxml | 3 +- .../bitsquare/gui/main/funds/FundsView.java | 9 +- .../gui/main/funds/deposit/DepositView.java | 7 +- .../gui/main/funds/locked/LockedListItem.java | 103 ++++++ .../gui/main/funds/locked/LockedView.fxml | 38 ++ .../gui/main/funds/locked/LockedView.java | 334 ++++++++++++++++++ .../main/funds/reserved/ReservedListItem.java | 50 +-- .../gui/main/funds/reserved/ReservedView.fxml | 2 +- .../gui/main/funds/reserved/ReservedView.java | 29 +- .../funds/withdrawal/WithdrawalListItem.java | 24 +- .../main/funds/withdrawal/WithdrawalView.java | 22 +- .../createoffer/CreateOfferDataModel.java | 5 +- .../offer/takeoffer/TakeOfferDataModel.java | 6 +- .../windows/DisputeSummaryWindow.java | 14 +- .../overlays/windows/EmptyWalletWindow.java | 2 +- .../pendingtrades/PendingTradesDataModel.java | 2 +- .../steps/buyer/BuyerStep5View.java | 13 +- .../p2p/network/LocalhostNetworkNode.java | 2 +- 58 files changed, 1143 insertions(+), 652 deletions(-) create mode 100644 core/src/main/java/io/bitsquare/btc/AddressEntryException.java create mode 100644 core/src/main/java/io/bitsquare/btc/BitsquareCoinSelector.java rename core/src/main/java/io/bitsquare/btc/{AddressBasedCoinSelector.java => MultiAddressesCoinSelector.java} (53%) create mode 100644 core/src/main/java/io/bitsquare/btc/TradeWalletCoinSelector.java create mode 100644 gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedListItem.java create mode 100644 gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.fxml create mode 100644 gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.java diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java b/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java index cb62887a8a..66843ddbba 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java @@ -22,6 +22,7 @@ import com.google.inject.Inject; import io.bitsquare.app.Log; import io.bitsquare.arbitration.messages.*; import io.bitsquare.arbitration.payload.Attachment; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.exceptions.TransactionVerificationException; @@ -490,7 +491,7 @@ public class DisputeManager { try { log.debug("do payout Transaction "); - Transaction signedDisputedPayoutTx = tradeWalletService.signAndFinalizeDisputedPayoutTx( + Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx( dispute.getDepositTxSerialized(), disputeResult.getArbitratorSignature(), disputeResult.getBuyerPayoutAmount(), @@ -499,7 +500,7 @@ public class DisputeManager { contract.getBuyerPayoutAddressString(), contract.getSellerPayoutAddressString(), disputeResult.getArbitratorAddressAsString(), - walletService.getTradeAddressEntry(dispute.getTradeId()), + walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG), contract.getBuyerBtcPubKey(), contract.getSellerBtcPubKey(), disputeResult.getArbitratorPubKey() @@ -576,7 +577,7 @@ public class DisputeManager { } private boolean isArbitrator(DisputeResult disputeResult) { - return disputeResult.getArbitratorAddressAsString().equals(walletService.getArbitratorAddressEntry().getAddressString()); + return disputeResult.getArbitratorAddressAsString().equals(walletService.getOrCreateAddressEntry(AddressEntry.Context.ARBITRATOR).getAddressString()); } diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntry.java b/core/src/main/java/io/bitsquare/btc/AddressEntry.java index 171be4ac78..80d9fe2f10 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntry.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntry.java @@ -20,6 +20,7 @@ package io.bitsquare.btc; import io.bitsquare.app.Version; import io.bitsquare.common.persistance.Persistable; import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.params.MainNetParams; @@ -44,10 +45,17 @@ public final class AddressEntry implements Persistable { private static final Logger log = LoggerFactory.getLogger(AddressEntry.class); public enum Context { - SAVINGS, - TRADE, ARBITRATOR, - DAO + + AVAILABLE, + + OFFER_FUNDING, + RESERVED_FOR_TRADE, //reserved + MULTI_SIG, //locked + TRADE_PAYOUT, + + DAO_SHARE, + DAO_DIVIDEND } // keyPair can be null in case the object is created from deserialization as it is transient. @@ -64,6 +72,8 @@ public final class AddressEntry implements Persistable { private final byte[] pubKey; private final byte[] pubKeyHash; private final String paramId; + @Nullable + private Coin lockedTradeAmount; transient private NetworkParameters params; @@ -77,7 +87,7 @@ public final class AddressEntry implements Persistable { } // If created with offerId - public AddressEntry(DeterministicKey keyPair, NetworkParameters params, Context context, @Nullable String offerId) { + public AddressEntry(@Nullable DeterministicKey keyPair, NetworkParameters params, Context context, @Nullable String offerId) { this.keyPair = keyPair; this.params = params; this.context = context; @@ -153,6 +163,26 @@ public final class AddressEntry implements Persistable { return pubKey; } + public boolean isOpenOffer() { + return context == Context.OFFER_FUNDING || context == Context.RESERVED_FOR_TRADE; + } + + public boolean isTrade() { + return context == Context.MULTI_SIG || context == Context.TRADE_PAYOUT; + } + + public boolean isTradable() { + return isOpenOffer() || isTrade(); + } + + public void setLockedTradeAmount(Coin lockedTradeAmount) { + this.lockedTradeAmount = lockedTradeAmount; + } + + public Coin getLockedTradeAmount() { + return lockedTradeAmount; + } + @Override public String toString() { return "AddressEntry{" + diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntryException.java b/core/src/main/java/io/bitsquare/btc/AddressEntryException.java new file mode 100644 index 0000000000..e5e2255f63 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/AddressEntryException.java @@ -0,0 +1,7 @@ +package io.bitsquare.btc; + +public class AddressEntryException extends Exception { + public AddressEntryException(String message) { + super(message); + } +} diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java index 8621a53478..86c0a3a5c3 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java @@ -67,16 +67,7 @@ public final class AddressEntryList extends ArrayList implements P } } - public AddressEntry getNewTradeAddressEntry(String offerId) { - log.trace("getNewAddressEntry called with offerId " + offerId); - AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.TRADE, offerId); - add(addressEntry); - storage.queueUpForSave(); - return addressEntry; - } - - public AddressEntry getNewSavingsAddressEntry() { - AddressEntry addressEntry = new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), AddressEntry.Context.SAVINGS); + public AddressEntry addAddressEntry(AddressEntry addressEntry) { add(addressEntry); storage.queueUpForSave(); return addressEntry; @@ -87,17 +78,20 @@ public final class AddressEntryList extends ArrayList implements P Optional addressEntryOptional = this.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId())).findAny(); if (addressEntryOptional.isPresent()) { AddressEntry addressEntry = addressEntryOptional.get(); - add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.SAVINGS)); + add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.AVAILABLE)); remove(addressEntry); storage.queueUpForSave(); } } + public void swapToAvailable(AddressEntry addressEntry) { + remove(addressEntry); + add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.AVAILABLE)); + remove(addressEntry); + storage.queueUpForSave(); + } - public AddressEntry getArbitratorAddressEntry() { - if (size() > 0) - return get(0); - else - return null; + public void queueUpForSave() { + storage.queueUpForSave(); } } diff --git a/core/src/main/java/io/bitsquare/btc/BitsquareCoinSelector.java b/core/src/main/java/io/bitsquare/btc/BitsquareCoinSelector.java new file mode 100644 index 0000000000..469010ab61 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/BitsquareCoinSelector.java @@ -0,0 +1,130 @@ +/* + * 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 . + */ + +package io.bitsquare.btc; + +import com.google.common.annotations.VisibleForTesting; +import org.bitcoinj.core.*; +import org.bitcoinj.wallet.CoinSelection; +import org.bitcoinj.wallet.CoinSelector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation. + * We lookup for spendable outputs which matches our address of our addressEntry. + */ +abstract class BitsquareCoinSelector implements CoinSelector { + private static final Logger log = LoggerFactory.getLogger(BitsquareCoinSelector.class); + protected final NetworkParameters params; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + protected BitsquareCoinSelector(NetworkParameters params) { + this.params = params; + } + + abstract protected boolean matchesRequirement(TransactionOutput transactionOutput); + + private static boolean isInBlockChainOrPending(Transaction tx) { + // Pick chain-included transactions and transactions that are pending. + TransactionConfidence confidence = tx.getConfidence(); + TransactionConfidence.ConfidenceType type = confidence.getConfidenceType(); + + log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers()); + return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || + type.equals(TransactionConfidence.ConfidenceType.PENDING); + } + + /** + * Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. + */ + protected boolean shouldSelect(Transaction tx) { + return isInBlockChainOrPending(tx); + } + + @Override + public CoinSelection select(Coin target, List candidates) { + log.trace("candidates.size: " + candidates.size()); + long targetAsLong = target.longValue(); + log.trace("value needed: " + targetAsLong); + HashSet selected = new HashSet<>(); + // Sort the inputs by age*value so we get the highest "coindays" spent. + ArrayList sortedOutputs = new ArrayList<>(candidates); + // When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting + // them in order to improve performance. + if (!target.equals(NetworkParameters.MAX_MONEY)) { + sortOutputs(sortedOutputs); + } + // Now iterate over the sorted outputs until we have got as close to the target as possible or a little + // bit over (excessive value will be change). + long total = 0; + for (TransactionOutput output : sortedOutputs) { + if (total >= targetAsLong) { + break; + } + // Only pick chain-included transactions, or transactions that are ours and pending. + // Only select outputs from our defined address(es) + if (!shouldSelect(output.getParentTransaction()) || !matchesRequirement(output)) { + continue; + } + + selected.add(output); + total += output.getValue().longValue(); + + log.debug("adding up outputs: output/total: " + output.getValue().longValue() + "/" + total); + } + // Total may be lower than target here, if the given candidates were insufficient to create to requested + // transaction. + return new CoinSelection(Coin.valueOf(total), selected); + } + + @VisibleForTesting + private static void sortOutputs(ArrayList outputs) { + Collections.sort(outputs, (a, b) -> { + int depth1 = a.getParentTransactionDepthInBlocks(); + int depth2 = b.getParentTransactionDepthInBlocks(); + Coin aValue = a.getValue(); + Coin bValue = b.getValue(); + BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1)); + BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2)); + int c1 = bCoinDepth.compareTo(aCoinDepth); + if (c1 != 0) return c1; + // The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size. + int c2 = bValue.compareTo(aValue); + if (c2 != 0) return c2; + // They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering. + checkNotNull(a.getParentTransactionHash(), "a.getParentTransactionHash() must not be null"); + checkNotNull(b.getParentTransactionHash(), "b.getParentTransactionHash() must not be null"); + BigInteger aHash = a.getParentTransactionHash().toBigInteger(); + BigInteger bHash = b.getParentTransactionHash().toBigInteger(); + return aHash.compareTo(bHash); + }); + } + +} diff --git a/core/src/main/java/io/bitsquare/btc/FeePolicy.java b/core/src/main/java/io/bitsquare/btc/FeePolicy.java index 2f1f00ab30..abcc134c61 100644 --- a/core/src/main/java/io/bitsquare/btc/FeePolicy.java +++ b/core/src/main/java/io/bitsquare/btc/FeePolicy.java @@ -84,8 +84,8 @@ public class FeePolicy { // TODO will be increased once we get higher limits - // 0.02 BTC; about 8 EUR @ 400 EUR/BTC + // 0.01 BTC; about 4 EUR @ 400 EUR/BTC public static Coin getSecurityDeposit() { - return Coin.valueOf(2_000_000); + return Coin.valueOf(1_000_000); } } diff --git a/core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java b/core/src/main/java/io/bitsquare/btc/MultiAddressesCoinSelector.java similarity index 53% rename from core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java rename to core/src/main/java/io/bitsquare/btc/MultiAddressesCoinSelector.java index 0154dc073b..7136b58cc3 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java +++ b/core/src/main/java/io/bitsquare/btc/MultiAddressesCoinSelector.java @@ -20,37 +20,26 @@ package io.bitsquare.btc; import org.bitcoinj.core.Address; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.TransactionOutput; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.util.Set; /** * We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation. * We lookup for spendable outputs which matches our address of our addressEntry. */ -class AddressBasedCoinSelector extends SavingsWalletCoinSelector { - private static final Logger log = LoggerFactory.getLogger(AddressBasedCoinSelector.class); - @Nullable - private Set addressEntries; - @Nullable - private AddressEntry addressEntry; +class MultiAddressesCoinSelector extends BitsquareCoinSelector { + private static final Logger log = LoggerFactory.getLogger(MultiAddressesCoinSelector.class); + private final Set addressEntries; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public AddressBasedCoinSelector(NetworkParameters params) { - super(params); - } - - public AddressBasedCoinSelector(NetworkParameters params, @Nullable AddressEntry addressEntry) { - super(params); - this.addressEntry = addressEntry; - } - - public AddressBasedCoinSelector(NetworkParameters params, @Nullable Set addressEntries) { + public MultiAddressesCoinSelector(NetworkParameters params, @NotNull Set addressEntries) { super(params); this.addressEntries = addressEntries; } @@ -61,26 +50,19 @@ class AddressBasedCoinSelector extends SavingsWalletCoinSelector { Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); log.trace("matchesRequiredAddress(es)?"); log.trace(addressOutput.toString()); - if (addressEntry != null && addressEntry.getAddress() != null) { - log.trace(addressEntry.getAddress().toString()); - if (addressOutput.equals(addressEntry.getAddress())) + log.trace(addressEntries.toString()); + for (AddressEntry entry : addressEntries) { + if (addressOutput.equals(entry.getAddress())) return true; - else { - log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " + addressOutput.toString - () + " / " + addressEntry.getAddress().toString()); - } - } else if (addressEntries != null) { - log.trace(addressEntries.toString()); - for (AddressEntry entry : addressEntries) { - if (addressOutput.equals(entry.getAddress())) - return true; - } - - log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString - () + " / " + addressEntries.toString()); } - } - return false; - } + log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString + () + " / " + addressEntries.toString()); + return false; + } else { + log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash"); + return false; + } + + } } diff --git a/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java b/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java index d72d8ff219..e3511e6238 100644 --- a/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java +++ b/core/src/main/java/io/bitsquare/btc/SavingsWalletCoinSelector.java @@ -17,144 +17,53 @@ package io.bitsquare.btc; -import com.google.common.annotations.VisibleForTesting; -import org.bitcoinj.core.*; -import org.bitcoinj.wallet.CoinSelection; -import org.bitcoinj.wallet.CoinSelector; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.TransactionOutput; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; -import java.math.BigInteger; -import java.util.*; +import java.util.List; +import java.util.Set; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; - /** * We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation. * We lookup for spendable outputs which matches our address of our addressEntry. */ -class SavingsWalletCoinSelector implements CoinSelector { +class SavingsWalletCoinSelector extends BitsquareCoinSelector { private static final Logger log = LoggerFactory.getLogger(SavingsWalletCoinSelector.class); - protected final NetworkParameters params; - @Nullable - private AddressEntryList addressEntryList; - @Nullable - private Set
savingsWalletAddressSet; + private final Set
savingsWalletAddressSet; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public SavingsWalletCoinSelector(NetworkParameters params, AddressEntryList addressEntryList) { - this.params = params; - this.addressEntryList = addressEntryList; - } - - protected SavingsWalletCoinSelector(NetworkParameters params) { - this.params = params; + public SavingsWalletCoinSelector(NetworkParameters params, @NotNull List addressEntryList) { + super(params); + savingsWalletAddressSet = addressEntryList.stream() + .filter(addressEntry -> addressEntry.getContext() == AddressEntry.Context.AVAILABLE) + .map(AddressEntry::getAddress) + .collect(Collectors.toSet()); } protected boolean matchesRequirement(TransactionOutput transactionOutput) { if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) { - Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); + Address address = transactionOutput.getScriptPubKey().getToAddress(params); log.trace("only lookup in savings wallet address entries"); - log.trace(addressOutput.toString()); + log.trace(address.toString()); - if (savingsWalletAddressSet != null && savingsWalletAddressSet.contains(addressOutput)) { - return true; - } else { - log.trace("No match found at matchesRequiredAddress addressOutput / addressEntry " + - addressOutput.toString() + " / " + addressOutput.toString()); - return false; - } + boolean matches = savingsWalletAddressSet.contains(address); + if (!matches) + log.trace("No match found at matchesRequiredAddress address / addressEntry " + + address.toString() + " / " + address.toString()); + + return matches; } else { + log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash"); return false; } } - - private static boolean isInBlockChainOrPending(Transaction tx) { - // Pick chain-included transactions and transactions that are pending. - TransactionConfidence confidence = tx.getConfidence(); - TransactionConfidence.ConfidenceType type = confidence.getConfidenceType(); - - log.debug("numBroadcastPeers = " + confidence.numBroadcastPeers()); - return type.equals(TransactionConfidence.ConfidenceType.BUILDING) || - type.equals(TransactionConfidence.ConfidenceType.PENDING); - } - - /** - * Sub-classes can override this to just customize whether transactions are usable, but keep age sorting. - */ - protected boolean shouldSelect(Transaction tx) { - return isInBlockChainOrPending(tx); - } - - @Override - public CoinSelection select(Coin target, List candidates) { - log.trace("candidates.size: " + candidates.size()); - long targetAsLong = target.longValue(); - log.trace("value needed: " + targetAsLong); - HashSet selected = new HashSet<>(); - // Sort the inputs by age*value so we get the highest "coindays" spent. - ArrayList sortedOutputs = new ArrayList<>(candidates); - // When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting - // them in order to improve performance. - if (!target.equals(NetworkParameters.MAX_MONEY)) { - sortOutputs(sortedOutputs); - } - // Now iterate over the sorted outputs until we have got as close to the target as possible or a little - // bit over (excessive value will be change). - long total = 0; - if (addressEntryList != null) { - savingsWalletAddressSet = addressEntryList.stream() - .filter(addressEntry1 -> addressEntry1.getContext() == AddressEntry.Context.SAVINGS) - .map(AddressEntry::getAddress) - .collect(Collectors.toSet()); - } - for (TransactionOutput output : sortedOutputs) { - if (total >= targetAsLong) { - break; - } - // Only pick chain-included transactions, or transactions that are ours and pending. - // Only select outputs from our defined address(es) - if (!shouldSelect(output.getParentTransaction()) || !matchesRequirement(output)) { - continue; - } - - selected.add(output); - total += output.getValue().longValue(); - - log.debug("adding up outputs: output/total: " + output.getValue().longValue() + "/" + total); - } - // Total may be lower than target here, if the given candidates were insufficient to create to requested - // transaction. - return new CoinSelection(Coin.valueOf(total), selected); - } - - @VisibleForTesting - private static void sortOutputs(ArrayList outputs) { - Collections.sort(outputs, (a, b) -> { - int depth1 = a.getParentTransactionDepthInBlocks(); - int depth2 = b.getParentTransactionDepthInBlocks(); - Coin aValue = a.getValue(); - Coin bValue = b.getValue(); - BigInteger aCoinDepth = BigInteger.valueOf(aValue.value).multiply(BigInteger.valueOf(depth1)); - BigInteger bCoinDepth = BigInteger.valueOf(bValue.value).multiply(BigInteger.valueOf(depth2)); - int c1 = bCoinDepth.compareTo(aCoinDepth); - if (c1 != 0) return c1; - // The "coin*days" destroyed are equal, sort by value alone to get the lowest transaction size. - int c2 = bValue.compareTo(aValue); - if (c2 != 0) return c2; - // They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering. - checkNotNull(a.getParentTransactionHash(), "a.getParentTransactionHash() must not be null"); - checkNotNull(b.getParentTransactionHash(), "b.getParentTransactionHash() must not be null"); - BigInteger aHash = a.getParentTransactionHash().toBigInteger(); - BigInteger bHash = b.getParentTransactionHash().toBigInteger(); - return aHash.compareTo(bHash); - }); - } - } diff --git a/core/src/main/java/io/bitsquare/btc/TradeWalletCoinSelector.java b/core/src/main/java/io/bitsquare/btc/TradeWalletCoinSelector.java new file mode 100644 index 0000000000..f04947fc0c --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/TradeWalletCoinSelector.java @@ -0,0 +1,64 @@ +/* + * 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 . + */ + +package io.bitsquare.btc; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.TransactionOutput; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation. + * We lookup for spendable outputs which matches our address of our address. + */ +class TradeWalletCoinSelector extends BitsquareCoinSelector { + private static final Logger log = LoggerFactory.getLogger(TradeWalletCoinSelector.class); + private final Address address; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public TradeWalletCoinSelector(NetworkParameters params, @NotNull Address address) { + super(params); + this.address = address; + } + + @Override + protected boolean matchesRequirement(TransactionOutput transactionOutput) { + if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) { + Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); + log.trace("matchesRequiredAddress?"); + log.trace("addressOutput " + addressOutput.toString()); + log.trace("address " + address.toString()); + boolean matches = addressOutput.equals(address); + if (!matches) + log.trace("No match found at matchesRequiredAddress addressOutput / address " + addressOutput.toString + () + " / " + address.toString()); + + return matches; + } else { + log.warn("transactionOutput.getScriptPubKey() not isSentToAddress or isPayToScriptHash"); + return false; + } + + } +} diff --git a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java index 8965ad04e0..e838f58ea3 100644 --- a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java +++ b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java @@ -134,14 +134,14 @@ public class TradeWalletService { /////////////////////////////////////////////////////////////////////////////////////////// /** - * @param addressEntry From where we want to spend the transaction fee. Used also as change address. + * @param reservedForTradeAddress From where we want to spend the transaction fee. Used also as change reservedForTradeAddress. * @param useSavingsWallet - * @param tradingFee The amount of the trading fee. - * @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). @return The broadcasted transaction + * @param tradingFee The amount of the trading fee. + * @param feeReceiverAddresses The reservedForTradeAddress of the receiver of the trading fee (arbitrator). @return The broadcasted transaction * @throws InsufficientMoneyException * @throws AddressFormatException */ - public Transaction createTradingFeeTx(AddressEntry addressEntry, Address changeAddress, Coin reservedFundsForOffer, + public Transaction createTradingFeeTx(Address fundingAddress, Address reservedForTradeAddress, Address changeAddress, Coin reservedFundsForOffer, boolean useSavingsWallet, Coin tradingFee, String feeReceiverAddresses) throws InsufficientMoneyException, AddressFormatException { Transaction tradingFeeTx = new Transaction(params); @@ -149,8 +149,8 @@ public class TradeWalletService { "You cannot send an amount which are smaller than the fee + dust output."); Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades()); tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses)); - // the reserved amount we need for the trade we send to our trade address - tradingFeeTx.addOutput(reservedFundsForOffer, addressEntry.getAddress()); + // the reserved amount we need for the trade we send to our trade reservedForTradeAddress + tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress); // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to // wait for 1 confirmation) @@ -159,9 +159,9 @@ public class TradeWalletService { sendRequest.shuffleOutputs = false; sendRequest.aesKey = aesKey; if (useSavingsWallet) - sendRequest.coinSelector = new SavingsWalletCoinSelector(params, addressEntryList); + sendRequest.coinSelector = new SavingsWalletCoinSelector(params, getAddressEntryListAsImmutableList()); else - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); + sendRequest.coinSelector = new TradeWalletCoinSelector(params, fundingAddress); // We use a fixed fee sendRequest.feePerKb = Coin.ZERO; sendRequest.fee = FeePolicy.getFixedTxFeeForTrades(); @@ -199,7 +199,7 @@ public class TradeWalletService { */ public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry takersAddressEntry, Address takersChangeAddress) throws TransactionVerificationException, WalletException, AddressFormatException { - log.trace("createTakerDepositTxInputs called"); + log.trace("takerCreatesDepositsTxInputs called"); log.trace("inputAmount " + inputAmount.toFriendlyString()); log.trace("takersAddressEntry " + takersAddressEntry.toString()); @@ -297,7 +297,7 @@ public class TradeWalletService { byte[] sellerPubKey, byte[] arbitratorPubKey) throws SigningException, TransactionVerificationException, WalletException, AddressFormatException { - log.trace("createAndSignDepositTx called"); + log.trace("offererCreatesAndSignsDepositTx called"); log.trace("offererIsBuyer " + offererIsBuyer); log.trace("offererInputAmount " + offererInputAmount.toFriendlyString()); log.trace("msOutputAmount " + msOutputAmount.toFriendlyString()); @@ -522,8 +522,8 @@ public class TradeWalletService { * @param depositTx Deposit transaction * @param buyerPayoutAmount Payout amount for buyer * @param sellerPayoutAmount Payout amount for seller - * @param buyerAddressString Address for buyer - * @param sellerAddressEntry AddressEntry for seller + * @param buyerPayoutAddressString Address for buyer + * @param sellerPayoutAddressEntry AddressEntry for seller * @param lockTime Lock time * @param buyerPubKey The public key of the buyer. * @param sellerPubKey The public key of the seller. @@ -535,19 +535,21 @@ public class TradeWalletService { public byte[] sellerSignsPayoutTx(Transaction depositTx, Coin buyerPayoutAmount, Coin sellerPayoutAmount, - String buyerAddressString, - AddressEntry sellerAddressEntry, + String buyerPayoutAddressString, + AddressEntry sellerPayoutAddressEntry, + AddressEntry multiSigAddressEntry, long lockTime, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) throws AddressFormatException, TransactionVerificationException { - log.trace("signPayoutTx called"); + log.trace("sellerSignsPayoutTx called"); log.trace("depositTx " + depositTx.toString()); log.trace("buyerPayoutAmount " + buyerPayoutAmount.toFriendlyString()); log.trace("sellerPayoutAmount " + sellerPayoutAmount.toFriendlyString()); - log.trace("buyerAddressString " + buyerAddressString); - log.trace("sellerAddressEntry " + sellerAddressEntry.toString()); + log.trace("buyerPayoutAddressString " + buyerPayoutAddressString); + log.trace("sellerPayoutAddressEntry " + sellerPayoutAddressEntry.toString()); + log.trace("multiSigAddressEntry " + multiSigAddressEntry.toString()); log.trace("lockTime " + lockTime); log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString()); log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString()); @@ -556,15 +558,15 @@ public class TradeWalletService { Transaction preparedPayoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, - buyerAddressString, - sellerAddressEntry.getAddressString(), + buyerPayoutAddressString, + sellerPayoutAddressEntry.getAddressString(), lockTime ); // MS redeemScript Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey); // MS output from prev. tx is index 0 Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); - DeterministicKey keyPair = sellerAddressEntry.getKeyPair(); + DeterministicKey keyPair = multiSigAddressEntry.getKeyPair(); checkNotNull(keyPair); ECKey.ECDSASignature sellerSignature = keyPair.sign(sigHash, aesKey).toCanonicalised(); @@ -582,7 +584,7 @@ public class TradeWalletService { * @param sellerSignature DER encoded canonical signature of seller * @param buyerPayoutAmount Payout amount for buyer * @param sellerPayoutAmount Payout amount for seller - * @param buyerAddressEntry AddressEntry for buyer + * @param buyerPayoutAddressEntry AddressEntry for buyer * @param sellerAddressString Address for seller * @param lockTime Lock time * @param buyerPubKey The public key of the buyer. @@ -597,20 +599,22 @@ public class TradeWalletService { byte[] sellerSignature, Coin buyerPayoutAmount, Coin sellerPayoutAmount, - AddressEntry buyerAddressEntry, + AddressEntry buyerPayoutAddressEntry, + AddressEntry multiSigAddressEntry, String sellerAddressString, long lockTime, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) throws AddressFormatException, TransactionVerificationException, WalletException { - log.trace("signAndFinalizePayoutTx called"); + log.trace("buyerSignsAndFinalizesPayoutTx called"); log.trace("depositTx " + depositTx.toString()); log.trace("sellerSignature r " + ECKey.ECDSASignature.decodeFromDER(sellerSignature).r.toString()); log.trace("sellerSignature s " + ECKey.ECDSASignature.decodeFromDER(sellerSignature).s.toString()); log.trace("buyerPayoutAmount " + buyerPayoutAmount.toFriendlyString()); log.trace("sellerPayoutAmount " + sellerPayoutAmount.toFriendlyString()); - log.trace("buyerAddressEntry " + buyerAddressEntry); + log.trace("buyerPayoutAddressEntry " + buyerPayoutAddressEntry); + log.trace("multiSigAddressEntry " + multiSigAddressEntry); log.trace("sellerAddressString " + sellerAddressString); log.trace("lockTime " + lockTime); log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString()); @@ -620,15 +624,15 @@ public class TradeWalletService { Transaction payoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, - buyerAddressEntry.getAddressString(), + buyerPayoutAddressEntry.getAddressString(), sellerAddressString, lockTime); // MS redeemScript Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey); // MS output from prev. tx is index 0 Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); - checkNotNull(buyerAddressEntry.getKeyPair(), "buyerAddressEntry.getKeyPair() must not be null"); - ECKey.ECDSASignature buyerSignature = buyerAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised(); + checkNotNull(multiSigAddressEntry.getKeyPair(), "multiSigAddressEntry.getKeyPair() must not be null"); + ECKey.ECDSASignature buyerSignature = multiSigAddressEntry.getKeyPair().sign(sigHash, aesKey).toCanonicalised(); TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature), Transaction.SigHash.ALL, false); TransactionSignature buyerTxSig = new TransactionSignature(buyerSignature, Transaction.SigHash.ALL, false); @@ -638,14 +642,14 @@ public class TradeWalletService { TransactionInput input = payoutTx.getInput(0); input.setScriptSig(inputScript); + printTxWithInputs("payoutTx", payoutTx); + verifyTransaction(payoutTx); checkWalletConsistency(); checkScriptSig(payoutTx, input, 0); checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null"); input.verify(input.getConnectedOutput()); - printTxWithInputs("payoutTx", payoutTx); - // 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; @@ -733,7 +737,7 @@ public class TradeWalletService { * @param buyerAddressString The address of the buyer. * @param sellerAddressString The address of the seller. * @param arbitratorAddressString The address of the arbitrator. - * @param tradersAddressEntry The addressEntry of the trader who calls that method + * @param tradersMultiSigAddressEntry The addressEntry of the trader who calls that method * @param buyerPubKey The public key of the buyer. * @param sellerPubKey The public key of the seller. * @param arbitratorPubKey The public key of the arbitrator. @@ -742,18 +746,18 @@ public class TradeWalletService { * @throws TransactionVerificationException * @throws WalletException */ - public Transaction signAndFinalizeDisputedPayoutTx(byte[] depositTxSerialized, - byte[] arbitratorSignature, - Coin buyerPayoutAmount, - Coin sellerPayoutAmount, - Coin arbitratorPayoutAmount, - String buyerAddressString, - String sellerAddressString, - String arbitratorAddressString, - AddressEntry tradersAddressEntry, - byte[] buyerPubKey, - byte[] sellerPubKey, - byte[] arbitratorPubKey) + public Transaction traderSignAndFinalizeDisputedPayoutTx(byte[] depositTxSerialized, + byte[] arbitratorSignature, + Coin buyerPayoutAmount, + Coin sellerPayoutAmount, + Coin arbitratorPayoutAmount, + String buyerAddressString, + String sellerAddressString, + String arbitratorAddressString, + AddressEntry tradersMultiSigAddressEntry, + byte[] buyerPubKey, + byte[] sellerPubKey, + byte[] arbitratorPubKey) throws AddressFormatException, TransactionVerificationException, WalletException { Transaction depositTx = new Transaction(params, depositTxSerialized); @@ -767,7 +771,7 @@ public class TradeWalletService { log.trace("buyerAddressString " + buyerAddressString); log.trace("sellerAddressString " + sellerAddressString); log.trace("arbitratorAddressString " + arbitratorAddressString); - log.trace("tradersAddressEntry " + tradersAddressEntry); + log.trace("tradersMultiSigAddressEntry " + tradersMultiSigAddressEntry); log.trace("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString()); log.trace("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString()); log.trace("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString()); @@ -786,7 +790,7 @@ public class TradeWalletService { // take care of sorting! Script redeemScript = getMultiSigRedeemScript(buyerPubKey, sellerPubKey, arbitratorPubKey); Sha256Hash sigHash = payoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false); - DeterministicKey keyPair = tradersAddressEntry.getKeyPair(); + DeterministicKey keyPair = tradersMultiSigAddressEntry.getKeyPair(); checkNotNull(keyPair); ECKey.ECDSASignature tradersSignature = keyPair.sign(sigHash, aesKey).toCanonicalised(); @@ -1053,7 +1057,7 @@ public class TradeWalletService { sendRequest.feePerKb = Coin.ZERO; sendRequest.fee = FeePolicy.getFixedTxFeeForTrades(); // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); + sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.getAddress()); // We use always the same address in a trade for all transactions sendRequest.changeAddress = changeAddress; // With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection. @@ -1069,4 +1073,9 @@ public class TradeWalletService { public void setAddressEntryList(AddressEntryList addressEntryList) { this.addressEntryList = addressEntryList; } + + public List getAddressEntryListAsImmutableList() { + return ImmutableList.copyOf(addressEntryList); + } + } diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index d29f1e52be..75d88e02b1 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -89,7 +89,6 @@ public class WalletService { private WalletAppKit walletAppKit; private Wallet wallet; - private AddressEntry arbitratorAddressEntry; private final IntegerProperty numPeers = new SimpleIntegerProperty(0); private final ObjectProperty> connectedPeers = new SimpleObjectProperty<>(); public final BooleanProperty shutDownDone = new SimpleBooleanProperty(); @@ -149,7 +148,6 @@ public class WalletService { wallet.addEventListener(walletEventListener); addressEntryList.onWalletReady(wallet); - arbitratorAddressEntry = addressEntryList.getArbitratorAddressEntry(); walletAppKit.peerGroup().addEventListener(new PeerEventListener() { @Override @@ -325,105 +323,76 @@ public class WalletService { /////////////////////////////////////////////////////////////////////////////////////////// - // Trade AddressEntry + // AddressEntry /////////////////////////////////////////////////////////////////////////////////////////// - public List getAddressEntryList() { - return ImmutableList.copyOf(addressEntryList); - } - - public AddressEntry getArbitratorAddressEntry() { - return arbitratorAddressEntry; - } - - public AddressEntry getTradeAddressEntry(String offerId) { - Optional addressEntry = getAddressEntryList().stream() + public AddressEntry getOrCreateAddressEntry(String offerId, AddressEntry.Context context) { + Optional addressEntry = getAddressEntryListAsImmutableList().stream() .filter(e -> offerId.equals(e.getOfferId())) + .filter(e -> context == e.getContext()) .findAny(); if (addressEntry.isPresent()) return addressEntry.get(); else - return addressEntryList.getNewTradeAddressEntry(offerId); + return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId)); } - private Optional getAddressEntryByAddress(String address) { - return getAddressEntryList().stream() - .filter(e -> e.getAddressString() != null && e.getAddressString().equals(address)) + public AddressEntry getOrCreateAddressEntry(AddressEntry.Context context) { + Optional addressEntry = getAddressEntryListAsImmutableList().stream() + .filter(e -> context == e.getContext()) + .findAny(); + if (addressEntry.isPresent()) + return addressEntry.get(); + else + return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context)); + } + + public Optional findAddressEntry(String address, AddressEntry.Context context) { + return getAddressEntryListAsImmutableList().stream() + .filter(e -> address.equals(e.getAddressString())) + .filter(e -> context == e.getContext()) .findAny(); } - public List getTradeAddressEntryList() { - return getAddressEntryList().stream() - .filter(e -> e.getContext().equals(AddressEntry.Context.TRADE)) + public List getAvailableAddressEntries() { + return getAddressEntryListAsImmutableList().stream() + .filter(addressEntry -> AddressEntry.Context.AVAILABLE == addressEntry.getContext()) .collect(Collectors.toList()); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // SavingsAddressEntry - /////////////////////////////////////////////////////////////////////////////////////////// - - public AddressEntry getNewSavingsAddressEntry() { - return addressEntryList.getNewSavingsAddressEntry(); - } - - public List getSavingsAddressEntryList() { - return getAddressEntryList().stream() - .filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS)) + public List getAddressEntries(AddressEntry.Context context) { + return getAddressEntryListAsImmutableList().stream() + .filter(addressEntry -> context == addressEntry.getContext()) .collect(Collectors.toList()); } - public AddressEntry getUnusedSavingsAddressEntry() { - List unusedSavingsAddressEntries = getUnusedSavingsAddressEntries(); - if (!unusedSavingsAddressEntries.isEmpty()) - return unusedSavingsAddressEntries.get(0); - else - return getNewSavingsAddressEntry(); - } - - public List getUnusedSavingsAddressEntries() { - return getSavingsAddressEntryList().stream() - .filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) == 0) + public List getFundedAvailableAddressEntries() { + return getAvailableAddressEntries().stream() + .filter(addressEntry -> getBalanceForAddress(addressEntry.getAddress()).isPositive()) .collect(Collectors.toList()); } - public List getUsedSavingsAddressEntries() { - return getSavingsAddressEntryList().stream() - .filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0) - .collect(Collectors.toList()); - } - - public List
getUsedSavingsAddresses() { - return getSavingsAddressEntryList().stream() - .filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0) - .map(addressEntry -> addressEntry.getAddress()) - .collect(Collectors.toList()); - } - - public List getUsedSavingWalletTransactions() { - List transactions = new ArrayList<>(); - List transactionOutputs = new ArrayList<>(); - List
usedSavingsAddresses = getUsedSavingsAddresses(); - log.debug("usedSavingsAddresses = " + usedSavingsAddresses); - wallet.getTransactions(true).stream().forEach(transaction -> transactionOutputs.addAll(transaction.getOutputs())); - for (TransactionOutput transactionOutput : transactionOutputs) { - if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) { - Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); - - if (usedSavingsAddresses.contains(addressOutput) && transactionOutput.getParentTransaction() != null) { - log.debug("transactionOutput.getParentTransaction() = " + transactionOutput.getParentTransaction().getHashAsString()); - transactions.add(transactionOutput.getParentTransaction()); - } - } - } - - return transactions; + public List getAddressEntryListAsImmutableList() { + return ImmutableList.copyOf(addressEntryList); } public void swapTradeToSavings(String offerId) { + getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); addressEntryList.swapTradeToSavings(offerId); } + public void swapTradeEntryToAvailableEntry(String offerId, AddressEntry.Context context) { + Optional addressEntryOptional = getAddressEntryListAsImmutableList().stream() + .filter(e -> offerId.equals(e.getOfferId())) + .filter(e -> context == e.getContext()) + .findAny(); + addressEntryOptional.ifPresent(addressEntryList::swapToAvailable); + } + + public void saveAddressEntryList() { + addressEntryList.queueUpForSave(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // TransactionConfidence @@ -518,10 +487,6 @@ public class WalletService { return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO; } - public Coin getBalanceForAddressEntryWithTradeId(String tradeId) { - return getBalanceForAddress(getTradeAddressEntry(tradeId).getAddress()); - } - private Coin getBalance(List transactionOutputs, Address address) { Coin balance = Coin.ZERO; for (TransactionOutput transactionOutput : transactionOutputs) { @@ -535,11 +500,9 @@ public class WalletService { } public Coin getSavingWalletBalance() { - Coin balance = Coin.ZERO; - for (AddressEntry addressEntry : getSavingsAddressEntryList()) { - balance = balance.add(getBalanceForAddress(addressEntry.getAddress())); - } - return balance; + return Coin.valueOf(getFundedAvailableAddressEntries().stream() + .mapToLong(addressEntry -> getBalanceForAddress(addressEntry.getAddress()).value) + .sum()); } public int getNumTxOutputsForAddress(Address address) { @@ -564,10 +527,11 @@ public class WalletService { public Coin getRequiredFee(String fromAddress, String toAddress, Coin amount, - @Nullable KeyParameter aesKey) throws AddressFormatException, IllegalArgumentException { + @Nullable KeyParameter aesKey, + AddressEntry.Context context) throws AddressFormatException, AddressEntryException { Coin fee; try { - wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey)); + wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey, context)); // We use the min fee for now as the mix of savingswallet/trade wallet has some nasty edge cases... fee = FeePolicy.getFixedTxFeeForTrades(); } catch (InsufficientMoneyException e) { @@ -582,7 +546,7 @@ public class WalletService { String toAddress, Coin amount, @Nullable KeyParameter aesKey) throws AddressFormatException, - IllegalArgumentException { + AddressEntryException { Coin fee; try { wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey)); @@ -599,8 +563,9 @@ public class WalletService { private Wallet.SendRequest getSendRequest(String fromAddress, String toAddress, Coin amount, - @Nullable KeyParameter aesKey) throws AddressFormatException, - IllegalArgumentException, InsufficientMoneyException { + @Nullable KeyParameter aesKey, + AddressEntry.Context context) throws AddressFormatException, + AddressEntryException, InsufficientMoneyException { Transaction tx = new Transaction(params); Preconditions.checkArgument(Restrictions.isAboveDust(amount), "You cannot send an amount which are smaller than 546 satoshis."); @@ -609,11 +574,12 @@ public class WalletService { Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); sendRequest.aesKey = aesKey; sendRequest.shuffleOutputs = false; - Optional addressEntry = getAddressEntryByAddress(fromAddress); + Optional addressEntry = findAddressEntry(fromAddress, context); if (!addressEntry.isPresent()) - throw new IllegalArgumentException("WithdrawFromAddress is not found in our wallets."); + throw new AddressEntryException("WithdrawFromAddress is not found in our wallet."); - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry.get()); + checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null"); + sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress()); sendRequest.changeAddress = addressEntry.get().getAddress(); sendRequest.feePerKb = FeePolicy.getFeePerKb(); return sendRequest; @@ -624,7 +590,7 @@ public class WalletService { Coin amount, @Nullable String changeAddress, @Nullable KeyParameter aesKey) throws - AddressFormatException, IllegalArgumentException, InsufficientMoneyException { + AddressFormatException, AddressEntryException, InsufficientMoneyException { Transaction tx = new Transaction(params); Preconditions.checkArgument(Restrictions.isAboveDust(amount), "You cannot send an amount which are smaller than 546 satoshis."); @@ -634,18 +600,23 @@ public class WalletService { sendRequest.aesKey = aesKey; sendRequest.shuffleOutputs = false; Set addressEntries = fromAddresses.stream() - .map(this::getAddressEntryByAddress) + .map(address -> { + Optional addressEntryOptional = findAddressEntry(address, AddressEntry.Context.AVAILABLE); + if (!addressEntryOptional.isPresent()) + addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING); + return addressEntryOptional; + }) .filter(Optional::isPresent) - .map(Optional::get).collect(Collectors.toSet()); + .map(Optional::get) + .collect(Collectors.toSet()); if (addressEntries.isEmpty()) - throw new IllegalArgumentException("No withdrawFromAddresses not found in our wallets.\n\t" + - "fromAddresses=" + fromAddresses); + throw new AddressEntryException("No Addresses for withdraw found in our wallet"); - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntries); + sendRequest.coinSelector = new MultiAddressesCoinSelector(params, addressEntries); Optional addressEntryOptional = Optional.empty(); AddressEntry changeAddressAddressEntry = null; if (changeAddress != null) - addressEntryOptional = getAddressEntryByAddress(changeAddress); + addressEntryOptional = findAddressEntry(changeAddress, AddressEntry.Context.AVAILABLE); if (addressEntryOptional.isPresent()) { changeAddressAddressEntry = addressEntryOptional.get(); @@ -664,10 +635,11 @@ public class WalletService { String toAddress, Coin amount, @Nullable KeyParameter aesKey, + AddressEntry.Context context, FutureCallback callback) throws AddressFormatException, - IllegalArgumentException, InsufficientMoneyException { - Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey); - Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey)); + AddressEntryException, InsufficientMoneyException { + Coin fee = getRequiredFee(fromAddress, toAddress, amount, aesKey, context); + Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, amount.subtract(fee), aesKey, context)); Futures.addCallback(sendResult.broadcastComplete, callback); printTxWithInputs("sendFunds", sendResult.tx); @@ -680,7 +652,7 @@ public class WalletService { @Nullable String changeAddress, @Nullable KeyParameter aesKey, FutureCallback callback) throws AddressFormatException, - IllegalArgumentException, InsufficientMoneyException { + AddressEntryException, InsufficientMoneyException { Coin fee = getRequiredFeeForMultipleAddresses(fromAddresses, toAddress, amount, aesKey); Wallet.SendResult sendResult = wallet.sendCoins(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount.subtract(fee), changeAddress, aesKey)); diff --git a/core/src/main/java/io/bitsquare/trade/TradableHelper.java b/core/src/main/java/io/bitsquare/trade/TradableHelper.java index 1ba75c4bcb..dcdf146950 100644 --- a/core/src/main/java/io/bitsquare/trade/TradableHelper.java +++ b/core/src/main/java/io/bitsquare/trade/TradableHelper.java @@ -1,110 +1,35 @@ package io.bitsquare.trade; import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.WalletService; -import io.bitsquare.trade.closed.ClosedTradableManager; -import io.bitsquare.trade.failed.FailedTradesManager; -import io.bitsquare.trade.offer.Offer; -import io.bitsquare.trade.offer.OpenOffer; -import io.bitsquare.trade.offer.OpenOfferManager; import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; public class TradableHelper { private static final Logger log = LoggerFactory.getLogger(TradableHelper.class); - public static List getAddressEntriesForAvailableBalance(OpenOfferManager openOfferManager, TradeManager tradeManager, WalletService walletService) { - Set reservedTradeIds = getNotCompletedTradableItems(openOfferManager, tradeManager).stream() - .map(tradable -> tradable.getOffer().getId()) - .collect(Collectors.toSet()); - - return walletService.getAddressEntryList().stream() - .filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive()) - .filter(e -> !reservedTradeIds.contains(e.getOfferId())) - .collect(Collectors.toList()); + public static Stream getAddressEntriesForAvailableBalanceStream(WalletService walletService) { + Stream availableOrPayout = Stream.concat(walletService.getAddressEntries(AddressEntry.Context.TRADE_PAYOUT).stream(), walletService.getFundedAvailableAddressEntries().stream()); + Stream available = Stream.concat(availableOrPayout, walletService.getAddressEntries(AddressEntry.Context.ARBITRATOR).stream()); + available = Stream.concat(available, walletService.getAddressEntries(AddressEntry.Context.OFFER_FUNDING).stream()); + return available + .filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isPositive()); } - public static Set getNotCompletedTradableItems(OpenOfferManager openOfferManager, TradeManager tradeManager) { - return Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) - .filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID) - .collect(Collectors.toSet()); + public static Stream getLockedTradeStream(TradeManager tradeManager) { + return tradeManager.getTrades().stream() + .filter(trade -> trade.getState().getPhase().ordinal() >= Trade.Phase.DEPOSIT_PAID.ordinal() && + trade.getState().getPhase().ordinal() < Trade.Phase.PAYOUT_PAID.ordinal()); } - public static Coin getBalanceInOpenOffer(OpenOffer openOffer) { - Offer offer = openOffer.getOffer(); - Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb()); - // For the seller we add the trade amount - if (offer.getDirection() == Offer.Direction.SELL) - balance = balance.add(offer.getAmount()); - - return balance; - } - - public static Coin getBalanceInTrade(Trade trade, WalletService walletService) { - AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId()); - Coin balance = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb()); - // For the seller we add the trade amount - if (trade.getContract() != null && - trade.getTradeAmount() != null && - trade.getContract().getSellerPayoutAddressString().equals(addressEntry.getAddressString())) - balance = balance.add(trade.getTradeAmount()); - return balance; - } - - public static Coin getAvailableBalance(AddressEntry addressEntry, WalletService walletService, - OpenOfferManager openOfferManager, TradeManager tradeManager, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager) { - Coin balance; - Coin totalBalance = walletService.getBalanceForAddress(addressEntry.getAddress()); - String id = addressEntry.getOfferId(); - Optional openOfferOptional = openOfferManager.getOpenOfferById(id); - Optional tradeOptional = tradeManager.getTradeById(id); - Optional closedTradableOptional = closedTradableManager.getTradableById(id); - Optional failedTradesOptional = failedTradesManager.getTradeById(id); - - if (openOfferOptional.isPresent()) { - balance = totalBalance.subtract(TradableHelper.getBalanceInOpenOffer(openOfferOptional.get())); - } else if (tradeOptional.isPresent()) { - Trade trade = tradeOptional.get(); - if (trade.getState().getPhase() != Trade.Phase.PAYOUT_PAID) - balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(trade, walletService)); - else - balance = totalBalance; - } else if (closedTradableOptional.isPresent()) { - Tradable tradable = closedTradableOptional.get(); - Coin balanceInTrade = Coin.ZERO; - if (tradable instanceof OpenOffer) - balanceInTrade = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable); - else if (tradable instanceof Trade) - balanceInTrade = TradableHelper.getBalanceInTrade((Trade) tradable, walletService); - balance = totalBalance.subtract(balanceInTrade); - } else if (failedTradesOptional.isPresent()) { - balance = totalBalance.subtract(TradableHelper.getBalanceInTrade(failedTradesOptional.get(), walletService)); - } else { - balance = totalBalance; - } - return balance; + public static AddressEntry getLockedTradeAddressEntry(Trade trade, WalletService walletService) { + return walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); } public static Coin getReservedBalance(Tradable tradable, WalletService walletService) { - AddressEntry addressEntry = walletService.getTradeAddressEntry(tradable.getId()); - Coin balance = walletService.getBalanceForAddress(addressEntry.getAddress()); - if (tradable instanceof Trade) { - Trade trade = (Trade) tradable; - if (trade.getState().getPhase().ordinal() < Trade.Phase.PAYOUT_PAID.ordinal()) - balance = TradableHelper.getBalanceInTrade(trade, walletService); - } else if (tradable instanceof OpenOffer) { - balance = TradableHelper.getBalanceInOpenOffer((OpenOffer) tradable); - } - return balance; + return walletService.getBalanceForAddress(walletService.getOrCreateAddressEntry(tradable.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress()); } } diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index 50d3907e76..d2ab532ed6 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.FutureCallback; import io.bitsquare.app.Log; import io.bitsquare.arbitration.ArbitratorManager; import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.AddressEntryException; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.common.crypto.KeyRing; @@ -306,8 +307,7 @@ public class TradeManager { /////////////////////////////////////////////////////////////////////////////////////////// public void onWithdrawRequest(String toAddress, KeyParameter aesKey, Trade trade, ResultHandler resultHandler, FaultHandler faultHandler) { - AddressEntry addressEntry = walletService.getTradeAddressEntry(trade.getId()); - String fromAddress = addressEntry.getAddressString(); + String fromAddress = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(); FutureCallback callback = new FutureCallback() { @Override @@ -328,8 +328,8 @@ public class TradeManager { } }; try { - walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), aesKey, callback); - } catch (AddressFormatException | InsufficientMoneyException e) { + walletService.sendFunds(fromAddress, toAddress, trade.getPayoutAmount(), aesKey, AddressEntry.Context.TRADE_PAYOUT, callback); + } catch (AddressFormatException | InsufficientMoneyException | AddressEntryException e) { e.printStackTrace(); log.error(e.getMessage()); faultHandler.handleFault("An exception occurred at requestWithdraw.", e); diff --git a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java index d59db8a2b0..7a3847d75b 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java @@ -19,6 +19,7 @@ package io.bitsquare.trade.offer; import com.google.inject.Inject; import io.bitsquare.app.Log; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.common.Timer; @@ -275,7 +276,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe openOffer.setState(OpenOffer.State.CANCELED); openOffers.remove(openOffer); closedTradableManager.add(openOffer); - walletService.swapTradeToSavings(offer.getId()); + walletService.swapTradeEntryToAvailableEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING); + walletService.swapTradeEntryToAvailableEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); resultHandler.handleResult(); }, errorMessageHandler); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java index b39ee8624e..f3d9a4e15b 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java @@ -18,7 +18,9 @@ package io.bitsquare.trade.protocol.placeoffer.tasks; import io.bitsquare.arbitration.Arbitrator; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.WalletService; import io.bitsquare.common.taskrunner.Task; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.p2p.NodeAddress; @@ -46,9 +48,12 @@ public class CreateOfferFeeTx extends Task { log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateOfferFeeTx"); + WalletService walletService = model.walletService; + String id = model.offer.getId(); Transaction transaction = model.tradeWalletService.createTradingFeeTx( - model.walletService.getTradeAddressEntry(model.offer.getId()), - model.walletService.getUnusedSavingsAddressEntry().getAddress(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(), + walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(), model.reservedFundsForOffer, model.useSavingsWallet, FeePolicy.getCreateOfferFee(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java index f2edb03259..1f4c8b804e 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java @@ -20,7 +20,6 @@ package io.bitsquare.trade.protocol.trade; import io.bitsquare.app.Version; import io.bitsquare.arbitration.Arbitrator; import io.bitsquare.arbitration.ArbitratorManager; -import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.data.RawTransactionInput; @@ -38,7 +37,6 @@ import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.user.User; -import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.slf4j.Logger; @@ -201,18 +199,6 @@ public class ProcessModel implements Model, Serializable { return user.getAccountId(); } - public AddressEntry getAddressEntry() { - return walletService.getTradeAddressEntry(offer.getId()); - } - - public Address getUnusedSavingsAddress() { - return walletService.getUnusedSavingsAddressEntry().getAddress(); - } - - public byte[] getTradeWalletPubKey() { - return getAddressEntry().getPubKey(); - } - @Nullable public byte[] getPayoutTxSignature() { return payoutTxSignature; diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java index 3d36016763..30e682950d 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java @@ -46,7 +46,7 @@ public final class TradingPeer implements Persistable { private String contractSignature; private byte[] signature; private PubKeyRing pubKeyRing; - private byte[] tradeWalletPubKey; + private byte[] multiSigPubKey; private List rawTransactionInputs; private long changeOutputValue; @Nullable @@ -81,12 +81,12 @@ public final class TradingPeer implements Persistable { this.accountId = accountId; } - public byte[] getTradeWalletPubKey() { - return tradeWalletPubKey; + public byte[] getMultiSigPubKey() { + return multiSigPubKey; } - public void setTradeWalletPubKey(byte[] tradeWalletPubKey) { - this.tradeWalletPubKey = tradeWalletPubKey; + public void setMultiSigPubKey(byte[] multiSigPubKey) { + this.multiSigPubKey = multiSigPubKey; } public PaymentAccountContractData getPaymentAccountContractData() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PayDepositRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PayDepositRequest.java index 60b36531a8..8ca2a81770 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PayDepositRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PayDepositRequest.java @@ -35,7 +35,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess private static final long serialVersionUID = Version.P2P_NETWORK_VERSION; public final long tradeAmount; - public final byte[] takerTradeWalletPubKey; + public final byte[] takerMultiSigPubKey; public final ArrayList rawTransactionInputs; public final long changeOutputValue; public final String changeOutputAddress; @@ -55,7 +55,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess ArrayList rawTransactionInputs, long changeOutputValue, String changeOutputAddress, - byte[] takerTradeWalletPubKey, + byte[] takerMultiSigPubKey, String takerPayoutAddressString, PubKeyRing takerPubKeyRing, PaymentAccountContractData takerPaymentAccountContractData, @@ -71,7 +71,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess this.changeOutputAddress = changeOutputAddress; this.takerPayoutAddressString = takerPayoutAddressString; this.takerPubKeyRing = takerPubKeyRing; - this.takerTradeWalletPubKey = takerTradeWalletPubKey; + this.takerMultiSigPubKey = takerMultiSigPubKey; this.takerPaymentAccountContractData = takerPaymentAccountContractData; this.takerAccountId = takerAccountId; this.takeOfferFeeTxId = takeOfferFeeTxId; @@ -100,7 +100,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess if (tradeAmount != that.tradeAmount) return false; if (changeOutputValue != that.changeOutputValue) return false; - if (!Arrays.equals(takerTradeWalletPubKey, that.takerTradeWalletPubKey)) return false; + if (!Arrays.equals(takerMultiSigPubKey, that.takerMultiSigPubKey)) return false; if (rawTransactionInputs != null ? !rawTransactionInputs.equals(that.rawTransactionInputs) : that.rawTransactionInputs != null) return false; if (changeOutputAddress != null ? !changeOutputAddress.equals(that.changeOutputAddress) : that.changeOutputAddress != null) @@ -129,7 +129,7 @@ public final class PayDepositRequest extends TradeMessage implements MailboxMess public int hashCode() { int result = super.hashCode(); result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32)); - result = 31 * result + (takerTradeWalletPubKey != null ? Arrays.hashCode(takerTradeWalletPubKey) : 0); + result = 31 * result + (takerMultiSigPubKey != null ? Arrays.hashCode(takerMultiSigPubKey) : 0); result = 31 * result + (rawTransactionInputs != null ? rawTransactionInputs.hashCode() : 0); result = 31 * result + (int) (changeOutputValue ^ (changeOutputValue >>> 32)); result = 31 * result + (changeOutputAddress != null ? changeOutputAddress.hashCode() : 0); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PublishDepositTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PublishDepositTxRequest.java index 6c7cf72840..3d8390eed7 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PublishDepositTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/messages/PublishDepositTxRequest.java @@ -43,12 +43,12 @@ public final class PublishDepositTxRequest extends TradeMessage { public final String offererPayoutAddressString; public final byte[] preparedDepositTx; public final ArrayList offererInputs; - public final byte[] offererTradeWalletPubKey; + public final byte[] offererMultiSigPubKey; public PublishDepositTxRequest(String tradeId, PaymentAccountContractData offererPaymentAccountContractData, String offererAccountId, - byte[] offererTradeWalletPubKey, + byte[] offererMultiSigPubKey, String offererContractAsJson, String offererContractSignature, String offererPayoutAddressString, @@ -57,7 +57,7 @@ public final class PublishDepositTxRequest extends TradeMessage { super(tradeId); this.offererPaymentAccountContractData = offererPaymentAccountContractData; this.offererAccountId = offererAccountId; - this.offererTradeWalletPubKey = offererTradeWalletPubKey; + this.offererMultiSigPubKey = offererMultiSigPubKey; this.offererContractAsJson = offererContractAsJson; this.offererContractSignature = offererContractSignature; this.offererPayoutAddressString = offererPayoutAddressString; @@ -65,7 +65,7 @@ public final class PublishDepositTxRequest extends TradeMessage { this.offererInputs = offererInputs; log.trace("offererPaymentAccount size " + Utilities.serialize(offererPaymentAccountContractData).length); - log.trace("offererTradeWalletPubKey size " + offererTradeWalletPubKey.length); + log.trace("offererTradeWalletPubKey size " + offererMultiSigPubKey.length); log.trace("preparedDepositTx size " + preparedDepositTx.length); log.trace("offererInputs size " + Utilities.serialize(new ArrayList<>(offererInputs)).length); } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java index e59835296d..47324ef1bd 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java @@ -17,7 +17,9 @@ package io.bitsquare.trade.protocol.trade.tasks.buyer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.WalletService; import io.bitsquare.btc.data.PreparedDepositTxAndOffererInputs; import io.bitsquare.common.crypto.Hash; import io.bitsquare.common.taskrunner.TaskRunner; @@ -51,6 +53,11 @@ public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask { byte[] contractHash = Hash.getHash(trade.getContractAsJson()); trade.setContractHash(contractHash); + WalletService walletService = processModel.getWalletService(); + String id = processModel.getOffer().getId(); + AddressEntry buyerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); + buyerMultiSigAddressEntry.setLockedTradeAmount(buyerInputAmount); + walletService.saveAddressEntryList(); PreparedDepositTxAndOffererInputs result = processModel.getTradeWalletService().offererCreatesAndSignsDepositTx( true, contractHash, @@ -59,10 +66,10 @@ public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask { processModel.tradingPeer.getRawTransactionInputs(), processModel.tradingPeer.getChangeOutputValue(), processModel.tradingPeer.getChangeOutputAddress(), - processModel.getAddressEntry(), - processModel.getUnusedSavingsAddress(), - processModel.getTradeWalletPubKey(), - processModel.tradingPeer.getTradeWalletPubKey(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE), + walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(), + buyerMultiSigAddressEntry.getPubKey(), + processModel.tradingPeer.getMultiSigPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); processModel.setPreparedDepositTx(result.depositTransaction); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java index 32e7a2b89d..5be594ef84 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SendFiatTransferStartedMessage.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.buyer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.p2p.messaging.SendMailboxMessageListener; import io.bitsquare.trade.Trade; @@ -41,7 +42,7 @@ public class SendFiatTransferStartedMessage extends TradeTask { processModel.tradingPeer.getPubKeyRing(), new FiatTransferStartedMessage( processModel.getId(), - processModel.getAddressEntry().getAddressString(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(), processModel.getMyAddress() ), new SendMailboxMessageListener() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java index 42c32787e6..25c8998906 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndFinalizePayoutTx.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.buyer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.trade.Trade; @@ -48,11 +49,12 @@ public class SignAndFinalizePayoutTx extends TradeTask { processModel.tradingPeer.getSignature(), buyerPayoutAmount, sellerPayoutAmount, - processModel.getAddressEntry(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG), processModel.tradingPeer.getPayoutAddressString(), trade.getLockTimeAsBlockHeight(), - processModel.getTradeWalletPubKey(), - processModel.tradingPeer.getTradeWalletPubKey(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(), + processModel.tradingPeer.getMultiSigPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()) ); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTxAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTxAsBuyer.java index a138617dbd..3b4fb9592a 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTxAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/SignAndPublishDepositTxAsBuyer.java @@ -18,15 +18,22 @@ package io.bitsquare.trade.protocol.trade.tasks.buyer; import com.google.common.util.concurrent.FutureCallback; +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.data.RawTransactionInput; import io.bitsquare.common.crypto.Hash; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.trade.Trade; +import io.bitsquare.trade.protocol.trade.TradingPeer; import io.bitsquare.trade.protocol.trade.tasks.TradeTask; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; + public class SignAndPublishDepositTxAsBuyer extends TradeTask { private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsBuyer.class); @@ -47,14 +54,20 @@ public class SignAndPublishDepositTxAsBuyer extends TradeTask { byte[] contractHash = Hash.getHash(trade.getContractAsJson()); trade.setContractHash(contractHash); + ArrayList buyerInputs = processModel.getRawTransactionInputs(); + WalletService walletService = processModel.getWalletService(); + AddressEntry buyerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG); + buyerMultiSigAddressEntry.setLockedTradeAmount(Coin.valueOf(buyerInputs.stream().mapToLong(input -> input.value).sum())); + walletService.saveAddressEntryList(); + TradingPeer tradingPeer = processModel.tradingPeer; processModel.getTradeWalletService().takerSignsAndPublishesDepositTx( false, contractHash, processModel.getPreparedDepositTx(), - processModel.getRawTransactionInputs(), - processModel.tradingPeer.getRawTransactionInputs(), - processModel.getTradeWalletPubKey(), - processModel.tradingPeer.getTradeWalletPubKey(), + buyerInputs, + tradingPeer.getRawTransactionInputs(), + buyerMultiSigAddressEntry.getPubKey(), + tradingPeer.getMultiSigPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()), new FutureCallback() { @Override diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java index 8a25486294..b9e6bf79eb 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.buyer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.data.InputsAndChangeOutput; import io.bitsquare.common.taskrunner.TaskRunner; @@ -38,9 +39,10 @@ public class TakerCreatesDepositTxInputsAsBuyer extends TradeTask { try { runInterceptHook(); Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()); - InputsAndChangeOutput result = processModel.getTradeWalletService() - .takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry(), - processModel.getUnusedSavingsAddress()); + InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs( + takerInputAmount, + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE), + processModel.getWalletService().getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress()); processModel.setRawTransactionInputs(result.rawTransactionInputs); processModel.setChangeOutputValue(result.changeOutputValue); processModel.setChangeOutputAddress(result.changeOutputAddress); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/CreateAndSignContract.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/CreateAndSignContract.java index ed8fa00cbd..b0219d9798 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/CreateAndSignContract.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/CreateAndSignContract.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.offerer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.common.crypto.Sig; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.common.util.Utilities; @@ -70,10 +71,10 @@ public class CreateAndSignContract extends TradeTask { takerPaymentAccountContractData, processModel.getPubKeyRing(), taker.getPubKeyRing(), - processModel.getAddressEntry().getAddressString(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(), taker.getPayoutAddressString(), - processModel.getTradeWalletPubKey(), - taker.getTradeWalletPubKey() + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(), + taker.getMultiSigPubKey() ); String contractAsJson = Utilities.objectToJson(contract); String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/ProcessPayDepositRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/ProcessPayDepositRequest.java index 3a1b7f6419..2e56649499 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/ProcessPayDepositRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/ProcessPayDepositRequest.java @@ -57,7 +57,7 @@ public class ProcessPayDepositRequest extends TradeTask { if (payDepositRequest.changeOutputAddress != null) processModel.tradingPeer.setChangeOutputAddress(payDepositRequest.changeOutputAddress); - processModel.tradingPeer.setTradeWalletPubKey(checkNotNull(payDepositRequest.takerTradeWalletPubKey)); + processModel.tradingPeer.setMultiSigPubKey(checkNotNull(payDepositRequest.takerMultiSigPubKey)); processModel.tradingPeer.setPayoutAddressString(nonEmptyStringOf(payDepositRequest.takerPayoutAddressString)); processModel.tradingPeer.setPubKeyRing(checkNotNull(payDepositRequest.takerPubKeyRing)); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SendPublishDepositTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SendPublishDepositTxRequest.java index 24b64dfe65..cd0a19ee5c 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SendPublishDepositTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SendPublishDepositTxRequest.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.offerer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.p2p.messaging.SendDirectMessageListener; import io.bitsquare.trade.Trade; @@ -40,10 +41,10 @@ public class SendPublishDepositTxRequest extends TradeTask { processModel.getId(), processModel.getPaymentAccountContractData(trade), processModel.getAccountId(), - processModel.getTradeWalletPubKey(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(), trade.getContractAsJson(), trade.getOffererContractSignature(), - processModel.getAddressEntry().getAddressString(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(), processModel.getPreparedDepositTx(), processModel.getRawTransactionInputs() ); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java index 6196688250..f2180e3d07 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/offerer/SetupDepositBalanceListener.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.offerer; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.common.UserThread; @@ -51,7 +52,7 @@ public class SetupDepositBalanceListener extends TradeTask { runInterceptHook(); WalletService walletService = processModel.getWalletService(); - Address address = walletService.getTradeAddressEntry(trade.getId()).getAddress(); + Address address = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(); balanceListener = new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java index c35a982786..7637f701cb 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java @@ -17,7 +17,9 @@ package io.bitsquare.trade.protocol.trade.tasks.seller; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.WalletService; import io.bitsquare.btc.data.PreparedDepositTxAndOffererInputs; import io.bitsquare.common.crypto.Hash; import io.bitsquare.common.taskrunner.TaskRunner; @@ -51,6 +53,11 @@ public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask { byte[] contractHash = Hash.getHash(trade.getContractAsJson()); trade.setContractHash(contractHash); + WalletService walletService = processModel.getWalletService(); + String id = processModel.getOffer().getId(); + AddressEntry sellerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG); + sellerMultiSigAddressEntry.setLockedTradeAmount(sellerInputAmount); + walletService.saveAddressEntryList(); PreparedDepositTxAndOffererInputs result = processModel.getTradeWalletService().offererCreatesAndSignsDepositTx( false, contractHash, @@ -59,10 +66,10 @@ public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask { processModel.tradingPeer.getRawTransactionInputs(), processModel.tradingPeer.getChangeOutputValue(), processModel.tradingPeer.getChangeOutputAddress(), - processModel.getAddressEntry(), - processModel.getUnusedSavingsAddress(), - processModel.tradingPeer.getTradeWalletPubKey(), - processModel.getTradeWalletPubKey(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE), + walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(), + processModel.tradingPeer.getMultiSigPubKey(), + sellerMultiSigAddressEntry.getPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); processModel.setPreparedDepositTx(result.depositTransaction); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java index 6062b542a0..a341d3fe23 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SendFinalizePayoutTxRequest.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.seller; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.p2p.messaging.SendMailboxMessageListener; import io.bitsquare.trade.Trade; @@ -40,7 +41,7 @@ public class SendFinalizePayoutTxRequest extends TradeTask { FinalizePayoutTxRequest message = new FinalizePayoutTxRequest( processModel.getId(), processModel.getPayoutTxSignature(), - processModel.getAddressEntry().getAddressString(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(), trade.getLockTimeAsBlockHeight(), processModel.getMyAddress() ); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignAndPublishDepositTxAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignAndPublishDepositTxAsSeller.java index c6f25b2b90..221214ae34 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignAndPublishDepositTxAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignAndPublishDepositTxAsSeller.java @@ -18,15 +18,22 @@ package io.bitsquare.trade.protocol.trade.tasks.seller; import com.google.common.util.concurrent.FutureCallback; +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.data.RawTransactionInput; import io.bitsquare.common.crypto.Hash; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.trade.Trade; +import io.bitsquare.trade.protocol.trade.TradingPeer; import io.bitsquare.trade.protocol.trade.tasks.TradeTask; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; + public class SignAndPublishDepositTxAsSeller extends TradeTask { private static final Logger log = LoggerFactory.getLogger(SignAndPublishDepositTxAsSeller.class); @@ -45,14 +52,21 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask { byte[] contractHash = Hash.getHash(trade.getContractAsJson()); trade.setContractHash(contractHash); + + ArrayList sellerInputs = processModel.getRawTransactionInputs(); + WalletService walletService = processModel.getWalletService(); + AddressEntry sellerMultiSigAddressEntry = walletService.getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG); + sellerMultiSigAddressEntry.setLockedTradeAmount(Coin.valueOf(sellerInputs.stream().mapToLong(input -> input.value).sum())); + walletService.saveAddressEntryList(); + TradingPeer tradingPeer = processModel.tradingPeer; processModel.getTradeWalletService().takerSignsAndPublishesDepositTx( true, contractHash, processModel.getPreparedDepositTx(), - processModel.tradingPeer.getRawTransactionInputs(), - processModel.getRawTransactionInputs(), - processModel.tradingPeer.getTradeWalletPubKey(), - processModel.getTradeWalletPubKey(), + tradingPeer.getRawTransactionInputs(), + sellerInputs, + tradingPeer.getMultiSigPubKey(), + sellerMultiSigAddressEntry.getPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress()), new FutureCallback() { @Override diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java index 09d8ec0ddc..369653854d 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/SignPayoutTx.java @@ -17,7 +17,9 @@ package io.bitsquare.trade.protocol.trade.tasks.seller; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.WalletService; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.trade.Trade; import io.bitsquare.trade.protocol.trade.tasks.TradeTask; @@ -53,15 +55,18 @@ public class SignPayoutTx extends TradeTask { lockTimeAsBlockHeight = processModel.getTradeWalletService().getLastBlockSeenHeight() + lockTime; trade.setLockTimeAsBlockHeight(lockTimeAsBlockHeight); + WalletService walletService = processModel.getWalletService(); + String id = processModel.getOffer().getId(); byte[] payoutTxSignature = processModel.getTradeWalletService().sellerSignsPayoutTx( trade.getDepositTx(), buyerPayoutAmount, sellerPayoutAmount, processModel.tradingPeer.getPayoutAddressString(), - processModel.getAddressEntry(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.TRADE_PAYOUT), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG), lockTimeAsBlockHeight, - processModel.tradingPeer.getTradeWalletPubKey(), - processModel.getTradeWalletPubKey(), + processModel.tradingPeer.getMultiSigPubKey(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.MULTI_SIG).getPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); processModel.setPayoutTxSignature(payoutTxSignature); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java index b381fd3ac5..1e5c6601f1 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.seller; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.data.InputsAndChangeOutput; import io.bitsquare.common.taskrunner.TaskRunner; @@ -40,10 +41,9 @@ public class TakerCreatesDepositTxInputsAsSeller extends TradeTask { if (trade.getTradeAmount() != null) { Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount()); - InputsAndChangeOutput result = processModel.getTradeWalletService() - .takerCreatesDepositsTxInputs(takerInputAmount, - processModel.getAddressEntry(), - processModel.getUnusedSavingsAddress()); + InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.RESERVED_FOR_TRADE), + processModel.getWalletService().getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress()); processModel.setRawTransactionInputs(result.rawTransactionInputs); processModel.setChangeOutputValue(result.changeOutputValue); processModel.setChangeOutputAddress(result.changeOutputAddress); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java index 16b92b7199..c692d45bca 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java @@ -18,7 +18,9 @@ package io.bitsquare.trade.protocol.trade.tasks.taker; import io.bitsquare.arbitration.Arbitrator; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; +import io.bitsquare.btc.WalletService; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.p2p.NodeAddress; import io.bitsquare.trade.Trade; @@ -48,9 +50,12 @@ public class CreateTakeOfferFeeTx extends TradeTask { log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx"); + WalletService walletService = processModel.getWalletService(); + String id = model.getOffer().getId(); Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx( - processModel.getAddressEntry(), - processModel.getUnusedSavingsAddress(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress(), + walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress(), + walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress(), processModel.getFundsNeededForTrade(), processModel.getUseSavingsWallet(), FeePolicy.getTakeOfferFee(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/ProcessPublishDepositTxRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/ProcessPublishDepositTxRequest.java index f39db93f9c..8cbf762dde 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/ProcessPublishDepositTxRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/ProcessPublishDepositTxRequest.java @@ -59,7 +59,7 @@ public class ProcessPublishDepositTxRequest extends TradeTask { } processModel.tradingPeer.setAccountId(nonEmptyStringOf(publishDepositTxRequest.offererAccountId)); - processModel.tradingPeer.setTradeWalletPubKey(checkNotNull(publishDepositTxRequest.offererTradeWalletPubKey)); + processModel.tradingPeer.setMultiSigPubKey(checkNotNull(publishDepositTxRequest.offererMultiSigPubKey)); processModel.tradingPeer.setContractAsJson(nonEmptyStringOf(publishDepositTxRequest.offererContractAsJson)); processModel.tradingPeer.setContractSignature(nonEmptyStringOf(publishDepositTxRequest.offererContractSignature)); processModel.tradingPeer.setPayoutAddressString(nonEmptyStringOf(publishDepositTxRequest.offererPayoutAddressString)); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/SendPayDepositRequest.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/SendPayDepositRequest.java index c6b8302366..161d80f0b7 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/SendPayDepositRequest.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/SendPayDepositRequest.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.taker; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.p2p.messaging.SendMailboxMessageListener; import io.bitsquare.trade.Trade; @@ -43,7 +44,6 @@ public class SendPayDepositRequest extends TradeTask { checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); checkNotNull(trade.getTakeOfferFeeTxId(), "TakeOfferFeeTxId must not be null"); - checkNotNull(processModel.getAddressEntry(), "AddressEntry must not be null"); PayDepositRequest payDepositRequest = new PayDepositRequest( processModel.getMyAddress(), @@ -52,8 +52,8 @@ public class SendPayDepositRequest extends TradeTask { processModel.getRawTransactionInputs(), processModel.getChangeOutputValue(), processModel.getChangeOutputAddress(), - processModel.getTradeWalletPubKey(), - processModel.getAddressEntry().getAddressString(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(), processModel.getPubKeyRing(), processModel.getPaymentAccountContractData(trade), processModel.getAccountId(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyAndSignContract.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyAndSignContract.java index f918253f0d..96edc32936 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyAndSignContract.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/VerifyAndSignContract.java @@ -17,6 +17,7 @@ package io.bitsquare.trade.protocol.trade.tasks.taker; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.common.crypto.Sig; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.common.util.Utilities; @@ -72,9 +73,9 @@ public class VerifyAndSignContract extends TradeTask { offerer.getPubKeyRing(), processModel.getPubKeyRing(), offerer.getPayoutAddressString(), - processModel.getAddressEntry().getAddressString(), - offerer.getTradeWalletPubKey(), - processModel.getTradeWalletPubKey() + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.TRADE_PAYOUT).getAddressString(), + offerer.getMultiSigPubKey(), + processModel.getWalletService().getOrCreateAddressEntry(processModel.getOffer().getId(), AddressEntry.Context.MULTI_SIG).getPubKey() ); String contractAsJson = Utilities.objectToJson(contract); String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson); diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index 0e39139788..badde5bcfd 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -235,7 +235,6 @@ public class BitsquareApp extends Application { primaryStage.show(); } try { - throwable.printStackTrace(); try { if (!popupOpened) { String message = throwable.getMessage(); diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index 429a39dcdb..3620ca0ee3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -26,7 +26,6 @@ import io.bitsquare.app.Version; import io.bitsquare.arbitration.ArbitratorManager; import io.bitsquare.arbitration.Dispute; import io.bitsquare.arbitration.DisputeManager; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; @@ -76,11 +75,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; -import java.util.stream.Stream; public class MainViewModel implements ViewModel { private static final Logger log = LoggerFactory.getLogger(MainViewModel.class); @@ -450,6 +451,7 @@ public class MainViewModel implements ViewModel { }); openOfferManager.getOpenOffers().addListener((ListChangeListener) c -> updateBalance()); + tradeManager.getTrades().addListener((ListChangeListener) c -> updateBalance()); openOfferManager.onAllServicesInitialized(); arbitratorManager.onAllServicesInitialized(); alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> displayAlertIfPresent(newValue)); @@ -460,7 +462,6 @@ public class MainViewModel implements ViewModel { updateBalance(); setupDevDummyPaymentAccount(); setupMarketPriceFeed(); - swapPendingTradeAddressEntriesToSavingsWallet(); showAppScreen.set(true); @@ -666,12 +667,6 @@ public class MainViewModel implements ViewModel { typeProperty.bind(priceFeed.typeProperty()); } - private void swapPendingTradeAddressEntriesToSavingsWallet() { - TradableHelper.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService).stream() - .filter(addressEntry -> addressEntry.getOfferId() != null) - .forEach(addressEntry -> walletService.swapTradeToSavings(addressEntry.getOfferId())); - } - private void displayAlertIfPresent(Alert alert) { boolean alreadyDisplayed = alert != null && alert.equals(user.getDisplayedAlert()); user.setDisplayedAlert(alert); @@ -689,32 +684,15 @@ public class MainViewModel implements ViewModel { } private void updateAvailableBalance() { - Optional totalAvailableOptional = Stream.concat(walletService.getSavingsAddressEntryList().stream(), walletService.getTradeAddressEntryList().stream()) - .filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isPositive()) - .map(addressEntry -> TradableHelper.getAvailableBalance(addressEntry, - walletService, - openOfferManager, - tradeManager, - closedTradableManager, - failedTradesManager)) - .filter(balance -> balance.isPositive()) - .reduce(Coin::add); - - - /*Optional totalAvailableOptional = TradableHelper.getAddressEntriesForAvailableBalance(openOfferManager, tradeManager, walletService) - .stream() - .map(e -> walletService.getBalanceForAddress(e.getAddress())) - .reduce(Coin::add);*/ - if (totalAvailableOptional.isPresent()) - availableBalance.set(formatter.formatCoinWithCode(totalAvailableOptional.get())); - else - availableBalance.set(formatter.formatCoinWithCode(Coin.ZERO)); + Coin totalAvailableBalance = Coin.valueOf(TradableHelper.getAddressEntriesForAvailableBalanceStream(walletService) + .mapToLong(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).getValue()) + .sum()); + availableBalance.set(formatter.formatCoinWithCode(totalAvailableBalance)); } private void updateReservedBalance() { - Coin sum = Coin.valueOf(TradableHelper.getNotCompletedTradableItems(openOfferManager, tradeManager).stream() - .filter(tradable -> tradable instanceof OpenOffer) - .map(tradable -> TradableHelper.getReservedBalance(tradable, walletService)) + Coin sum = Coin.valueOf(openOfferManager.getOpenOffers().stream() + .map(openOffer -> TradableHelper.getReservedBalance(openOffer, walletService)) .mapToLong(Coin::getValue) .sum()); @@ -722,26 +700,8 @@ public class MainViewModel implements ViewModel { } private void updateLockedBalance() { - Coin sum = Coin.valueOf(tradeManager.getTrades().stream() - .map(trade -> { - switch (trade.getState().getPhase()) { - case DEPOSIT_REQUESTED: - case DEPOSIT_PAID: - case FIAT_SENT: - case FIAT_RECEIVED: - Coin balanceInDeposit = FeePolicy.getSecurityDeposit().add(FeePolicy.getFeePerKb()); - if (trade.getContract() != null && - trade.getTradeAmount() != null && - trade.getContract().getSellerPayoutAddressString() - .equals(walletService.getTradeAddressEntry(trade.getId()).getAddressString())) { - balanceInDeposit = balanceInDeposit.add(trade.getTradeAmount()); - } - return balanceInDeposit; - default: - return Coin.ZERO; - } - }) - .mapToLong(Coin::getValue) + Coin sum = Coin.valueOf(TradableHelper.getLockedTradeStream(tradeManager) + .mapToLong(trade -> TradableHelper.getLockedTradeAddressEntry(trade, walletService).getLockedTradeAmount().getValue()) .sum()); lockedBalance.set(formatter.formatCoinWithCode(sum)); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/arbitratorregistration/ArbitratorRegistrationViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/account/arbitratorregistration/ArbitratorRegistrationViewModel.java index 5f5b617da2..0433a84b28 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/arbitratorregistration/ArbitratorRegistrationViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/arbitratorregistration/ArbitratorRegistrationViewModel.java @@ -139,7 +139,7 @@ class ArbitratorRegistrationViewModel extends ActivatableViewModel { void onRegister(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { updateDisableStates(); if (allDataValid) { - AddressEntry arbitratorDepositAddressEntry = walletService.getArbitratorAddressEntry(); + AddressEntry arbitratorDepositAddressEntry = walletService.getOrCreateAddressEntry(AddressEntry.Context.ARBITRATOR); String registrationSignature = arbitratorManager.signStorageSignaturePubKey(registrationKey); Arbitrator arbitrator = new Arbitrator( p2PService.getAddress(), diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml index a66fd45d48..83ffe78b9a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.fxml @@ -25,7 +25,8 @@ - + + \ No newline at end of file diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java index 9182b11604..688230669f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/FundsView.java @@ -22,6 +22,7 @@ import io.bitsquare.gui.common.model.Activatable; import io.bitsquare.gui.common.view.*; import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.funds.deposit.DepositView; +import io.bitsquare.gui.main.funds.locked.LockedView; import io.bitsquare.gui.main.funds.reserved.ReservedView; import io.bitsquare.gui.main.funds.transactions.TransactionsView; import io.bitsquare.gui.main.funds.withdrawal.WithdrawalView; @@ -36,7 +37,7 @@ import javax.inject.Inject; public class FundsView extends ActivatableViewAndModel { @FXML - Tab depositTab, withdrawalTab, reservedTab, transactionsTab; + Tab depositTab, withdrawalTab, reservedTab, lockedTab, transactionsTab; private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; @@ -65,6 +66,8 @@ public class FundsView extends ActivatableViewAndModel { navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); else if (newValue == reservedTab) navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); + else if (newValue == lockedTab) + navigation.navigateTo(MainView.class, FundsView.class, LockedView.class); else if (newValue == transactionsTab) navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); }; @@ -81,6 +84,8 @@ public class FundsView extends ActivatableViewAndModel { navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); else if (root.getSelectionModel().getSelectedItem() == reservedTab) navigation.navigateTo(MainView.class, FundsView.class, ReservedView.class); + else if (root.getSelectionModel().getSelectedItem() == lockedTab) + navigation.navigateTo(MainView.class, FundsView.class, LockedView.class); else if (root.getSelectionModel().getSelectedItem() == transactionsTab) navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class); } @@ -105,6 +110,8 @@ public class FundsView extends ActivatableViewAndModel { currentTab = withdrawalTab; else if (view instanceof ReservedView) currentTab = reservedTab; + else if (view instanceof LockedView) + currentTab = lockedTab; else if (view instanceof TransactionsView) currentTab = transactionsTab; diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java index 310f51603f..16f9adb58f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java @@ -111,7 +111,7 @@ public class DepositView extends ActivatableView { @Override public void initialize() { // trigger creation of at least 1 savings address - walletService.getUnusedSavingsAddressEntry(); + walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setPlaceholder(new Label("No deposit addresses are generated yet")); @@ -186,8 +186,7 @@ public class DepositView extends ActivatableView { new Popup().warning("You have already at least one address which is not used yet in any transaction.\n" + "Please select in the address table an unused address.").show(); } else { - AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry(); - //fillForm(newSavingsAddressEntry.getAddressString()); + AddressEntry newSavingsAddressEntry = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE); updateList(); observableList.stream() .filter(depositListItem -> depositListItem.getAddressString().equals(newSavingsAddressEntry.getAddressString())) @@ -289,7 +288,7 @@ public class DepositView extends ActivatableView { private void updateList() { observableList.clear(); - walletService.getSavingsAddressEntryList().stream() + walletService.getAvailableAddressEntries().stream() .forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter))); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedListItem.java new file mode 100644 index 0000000000..3c36761e18 --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedListItem.java @@ -0,0 +1,103 @@ +/* + * 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 . + */ + +package io.bitsquare.gui.main.funds.locked; + +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.listeners.BalanceListener; +import io.bitsquare.gui.util.BSFormatter; +import io.bitsquare.trade.Tradable; +import io.bitsquare.trade.Trade; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.control.Label; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LockedListItem { + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private final StringProperty date = new SimpleStringProperty(); + private final BalanceListener balanceListener; + private final Label balanceLabel; + private final Trade trade; + private final AddressEntry addressEntry; + private final WalletService walletService; + private final BSFormatter formatter; + private final String addressString; + private Coin balance; + + public LockedListItem(Trade trade, AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) { + this.trade = trade; + this.addressEntry = addressEntry; + this.walletService = walletService; + this.formatter = formatter; + addressString = addressEntry.getAddressString(); + + date.set(formatter.formatDateTime(trade.getDate())); + + // balance + balanceLabel = new Label(); + balanceListener = new BalanceListener(getAddress()) { + @Override + public void onBalanceChanged(Coin balance, Transaction tx) { + updateBalance(); + } + }; + walletService.addBalanceListener(balanceListener); + updateBalance(); + } + + public void cleanup() { + walletService.removeBalanceListener(balanceListener); + } + + private void updateBalance() { + balance = addressEntry.getLockedTradeAmount(); + if (balance != null) + balanceLabel.setText(formatter.formatCoin(this.balance)); + } + + private Address getAddress() { + return addressEntry.getAddress(); + } + + public AddressEntry getAddressEntry() { + return addressEntry; + } + + public Label getBalanceLabel() { + return balanceLabel; + } + + public Coin getBalance() { + return balance; + } + + public String getAddressString() { + return addressString; + } + + public Tradable getTrade() { + return trade; + } + +} diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.fxml new file mode 100644 index 0000000000..bac993cb1d --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.fxml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.java new file mode 100644 index 0000000000..ae5cae9631 --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/locked/LockedView.java @@ -0,0 +1,334 @@ +/* + * 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 . + */ + +package io.bitsquare.gui.main.funds.locked; + +import de.jensd.fx.fontawesome.AwesomeIcon; +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.listeners.BalanceListener; +import io.bitsquare.common.util.Utilities; +import io.bitsquare.gui.common.view.ActivatableView; +import io.bitsquare.gui.common.view.FxmlView; +import io.bitsquare.gui.components.HyperlinkWithIcon; +import io.bitsquare.gui.main.overlays.popups.Popup; +import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; +import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; +import io.bitsquare.gui.util.BSFormatter; +import io.bitsquare.trade.Tradable; +import io.bitsquare.trade.TradableHelper; +import io.bitsquare.trade.Trade; +import io.bitsquare.trade.TradeManager; +import io.bitsquare.trade.offer.OpenOffer; +import io.bitsquare.trade.offer.OpenOfferManager; +import io.bitsquare.user.Preferences; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import javafx.util.Callback; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.Transaction; + +import javax.inject.Inject; +import java.util.Optional; +import java.util.stream.Collectors; + +@FxmlView +public class LockedView extends ActivatableView { + @FXML + TableView tableView; + @FXML + TableColumn dateColumn, detailsColumn, addressColumn, balanceColumn, confidenceColumn; + + private final WalletService walletService; + private final TradeManager tradeManager; + private final OpenOfferManager openOfferManager; + private final Preferences preferences; + private final BSFormatter formatter; + private final OfferDetailsWindow offerDetailsWindow; + private final TradeDetailsWindow tradeDetailsWindow; + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); + private BalanceListener balanceListener; + private ListChangeListener openOfferListChangeListener; + private ListChangeListener tradeListChangeListener; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor, lifecycle + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + private LockedView(WalletService walletService, TradeManager tradeManager, OpenOfferManager openOfferManager, Preferences preferences, + BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { + this.walletService = walletService; + this.tradeManager = tradeManager; + this.openOfferManager = openOfferManager; + this.preferences = preferences; + this.formatter = formatter; + this.offerDetailsWindow = offerDetailsWindow; + this.tradeDetailsWindow = tradeDetailsWindow; + } + + @Override + public void initialize() { + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setPlaceholder(new Label("No funds are locked in trades")); + + setDateColumnCellFactory(); + setDetailsColumnCellFactory(); + setAddressColumnCellFactory(); + setBalanceColumnCellFactory(); + + addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); + detailsColumn.setComparator((o1, o2) -> o1.getTrade().getId().compareTo(o2.getTrade().getId())); + balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance())); + dateColumn.setComparator((o1, o2) -> { + if (getTradable(o1).isPresent() && getTradable(o2).isPresent()) + return getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate()); + else + return 0; + }); + tableView.getSortOrder().add(dateColumn); + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + + balanceListener = new BalanceListener() { + @Override + public void onBalanceChanged(Coin balance, Transaction tx) { + updateList(); + } + }; + openOfferListChangeListener = c -> updateList(); + tradeListChangeListener = c -> updateList(); + } + + @Override + protected void activate() { + openOfferManager.getOpenOffers().addListener(openOfferListChangeListener); + tradeManager.getTrades().addListener(tradeListChangeListener); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); + updateList(); + + walletService.addBalanceListener(balanceListener); + } + + @Override + protected void deactivate() { + openOfferManager.getOpenOffers().removeListener(openOfferListChangeListener); + tradeManager.getTrades().removeListener(tradeListChangeListener); + sortedList.comparatorProperty().unbind(); + observableList.forEach(LockedListItem::cleanup); + walletService.removeBalanceListener(balanceListener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void updateList() { + observableList.forEach(LockedListItem::cleanup); + observableList.setAll(TradableHelper.getLockedTradeStream(tradeManager) + .map(trade -> new LockedListItem(trade, + TradableHelper.getLockedTradeAddressEntry(trade, walletService), + walletService, + formatter)) + .collect(Collectors.toList())); + } + + private void openBlockExplorer(LockedListItem item) { + try { + Utilities.openWebPage(preferences.getBlockChainExplorer().addressUrl + item.getAddressString()); + } catch (Exception e) { + log.error(e.getMessage()); + new Popup().warning("Opening browser failed. Please check your internet " + + "connection.").show(); + } + } + + private Optional getTradable(LockedListItem item) { + String offerId = item.getAddressEntry().getOfferId(); + Optional tradeOptional = tradeManager.getTradeById(offerId); + if (tradeOptional.isPresent()) { + return Optional.of(tradeOptional.get()); + } else if (openOfferManager.getOpenOfferById(offerId).isPresent()) { + return Optional.of(openOfferManager.getOpenOfferById(offerId).get()); + } else { + return Optional.empty(); + } + } + + private void openDetailPopup(LockedListItem item) { + Optional tradableOptional = getTradable(item); + if (tradableOptional.isPresent()) { + Tradable tradable = tradableOptional.get(); + if (tradable instanceof Trade) { + tradeDetailsWindow.show((Trade) tradable); + } else if (tradable instanceof OpenOffer) { + offerDetailsWindow.show(tradable.getOffer()); + } + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // ColumnCellFactories + /////////////////////////////////////////////////////////////////////////////////////////// + + private void setDateColumnCellFactory() { + dateColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + dateColumn.setCellFactory(new Callback, + TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final LockedListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + if (getTradable(item).isPresent()) + setText(formatter.formatDateTime(getTradable(item).get().getDate())); + else + setText("No date available"); + } else { + setText(""); + } + } + }; + } + }); + } + + private void setDetailsColumnCellFactory() { + detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + detailsColumn.setCellFactory(new Callback, + TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + private HyperlinkWithIcon field; + + @Override + public void updateItem(final LockedListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + Optional tradableOptional = getTradable(item); + if (tradableOptional.isPresent()) { + AddressEntry addressEntry = item.getAddressEntry(); + String details; + if (addressEntry.isTrade()) { + details = "Trade ID: " + addressEntry.getShortOfferId(); + } else if (addressEntry.isOpenOffer()) { + details = "Offer ID: " + addressEntry.getShortOfferId(); + } else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) { + details = "Arbitration fee"; + } else { + details = "-"; + } + + field = new HyperlinkWithIcon(details + " (Locked in trade (MultiSig))", + AwesomeIcon.INFO_SIGN); + field.setOnAction(event -> openDetailPopup(item)); + field.setTooltip(new Tooltip("Open popup for details")); + setGraphic(field); + } else if (item.getAddressEntry().getContext() == AddressEntry.Context.ARBITRATOR) { + setGraphic(new Label("Arbitrators fee")); + } else { + setGraphic(new Label("No details available")); + } + + } else { + setGraphic(null); + if (field != null) + field.setOnAction(null); + } + } + }; + } + }); + } + + private void setAddressColumnCellFactory() { + addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + addressColumn.setCellFactory( + new Callback, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + private HyperlinkWithIcon hyperlinkWithIcon; + + @Override + public void updateItem(final LockedListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + String address = item.getAddressString(); + hyperlinkWithIcon = new HyperlinkWithIcon(address, AwesomeIcon.EXTERNAL_LINK); + hyperlinkWithIcon.setOnAction(event -> openBlockExplorer(item)); + hyperlinkWithIcon.setTooltip(new Tooltip("Open external blockchain explorer for " + + "address: " + address)); + setGraphic(hyperlinkWithIcon); + } else { + setGraphic(null); + if (hyperlinkWithIcon != null) + hyperlinkWithIcon.setOnAction(null); + } + } + }; + } + }); + } + + private void setBalanceColumnCellFactory() { + balanceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + balanceColumn.setCellFactory( + new Callback, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + @Override + public void updateItem(final LockedListItem item, boolean empty) { + super.updateItem(item, empty); + setGraphic((item != null && !empty) ? item.getBalanceLabel() : null); + } + }; + } + }); + } + +} + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java index 3b6077b23e..79b2c3b4fb 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java @@ -23,7 +23,6 @@ import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.trade.Tradable; import io.bitsquare.trade.TradableHelper; -import io.bitsquare.trade.Trade; import io.bitsquare.trade.offer.OpenOffer; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -40,22 +39,21 @@ public class ReservedListItem { private final StringProperty date = new SimpleStringProperty(); private final BalanceListener balanceListener; private final Label balanceLabel; - private String fundsInfo; - private final Tradable tradable; + private final OpenOffer openOffer; private final AddressEntry addressEntry; private final WalletService walletService; private final BSFormatter formatter; private final String addressString; private Coin balance; - public ReservedListItem(Tradable tradable, AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) { - this.tradable = tradable; + public ReservedListItem(OpenOffer openOffer, AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) { + this.openOffer = openOffer; this.addressEntry = addressEntry; this.walletService = walletService; this.formatter = formatter; addressString = addressEntry.getAddressString(); - date.set(formatter.formatDateTime(tradable.getDate())); + date.set(formatter.formatDateTime(openOffer.getDate())); // balance balanceLabel = new Label(); @@ -74,39 +72,9 @@ public class ReservedListItem { } private void updateBalance() { - balance = TradableHelper.getReservedBalance(tradable, walletService); + balance = TradableHelper.getReservedBalance(openOffer, walletService); if (balance != null) balanceLabel.setText(formatter.formatCoin(this.balance)); - - if (tradable instanceof Trade) { - Trade trade = (Trade) tradable; - Trade.Phase phase = trade.getState().getPhase(); - switch (phase) { - case PREPARATION: - case TAKER_FEE_PAID: - fundsInfo = "Reserved in local wallet"; - break; - case DEPOSIT_REQUESTED: - case DEPOSIT_PAID: - case FIAT_SENT: - case FIAT_RECEIVED: - fundsInfo = "Locked in MultiSig"; - break; - case PAYOUT_PAID: - fundsInfo = "Received in local wallet"; - break; - case WITHDRAWN: - log.error("Invalid state at updateBalance (WITHDRAWN)"); - break; - case DISPUTE: - log.error("Invalid state at updateBalance (DISPUTE)"); - break; - default: - log.warn("Not supported tradePhase: " + phase); - } - } else if (tradable instanceof OpenOffer) { - fundsInfo = "Reserved in local wallet"; - } } private Address getAddress() { @@ -129,12 +97,8 @@ public class ReservedListItem { return addressString; } - public String getFundsInfo() { - return fundsInfo; - } - - public Tradable getTradable() { - return tradable; + public Tradable getOpenOffer() { + return openOffer; } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml index 62d6d3f525..9b6bda8e5b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml @@ -29,7 +29,7 @@ - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java index 4faea3f8fb..f2a90c112f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java @@ -30,7 +30,6 @@ import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.trade.Tradable; -import io.bitsquare.trade.TradableHelper; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.offer.OpenOffer; @@ -92,7 +91,7 @@ public class ReservedView extends ActivatableView { @Override public void initialize() { tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - tableView.setPlaceholder(new Label("No funds are reserved in open offers or trades")); + tableView.setPlaceholder(new Label("No funds are reserved in open offers")); setDateColumnCellFactory(); setDetailsColumnCellFactory(); @@ -100,7 +99,7 @@ public class ReservedView extends ActivatableView { setBalanceColumnCellFactory(); addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); - detailsColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId())); + detailsColumn.setComparator((o1, o2) -> o1.getOpenOffer().getId().compareTo(o2.getOpenOffer().getId())); balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance())); dateColumn.setComparator((o1, o2) -> { if (getTradable(o1).isPresent() && getTradable(o2).isPresent()) @@ -148,8 +147,11 @@ public class ReservedView extends ActivatableView { private void updateList() { observableList.forEach(ReservedListItem::cleanup); - observableList.setAll(TradableHelper.getNotCompletedTradableItems(openOfferManager, tradeManager).stream() - .map(tradable -> new ReservedListItem(tradable, walletService.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter)) + observableList.setAll(openOfferManager.getOpenOffers().stream() + .map(openOffer -> new ReservedListItem(openOffer, + walletService.getOrCreateAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE), + walletService, + formatter)) .collect(Collectors.toList())); } @@ -240,24 +242,17 @@ public class ReservedView extends ActivatableView { if (tradableOptional.isPresent()) { AddressEntry addressEntry = item.getAddressEntry(); String details; - if (addressEntry.getContext() == AddressEntry.Context.TRADE) { - String prefix; - Tradable tradable = tradableOptional.get(); - if (tradable instanceof Trade) - prefix = "Trade ID: "; - else if (tradable instanceof OpenOffer) - prefix = "Offer ID: "; - else - prefix = ""; - - details = prefix + addressEntry.getShortOfferId(); + if (addressEntry.isTrade()) { + details = "Trade ID: " + addressEntry.getShortOfferId(); + } else if (addressEntry.isOpenOffer()) { + details = "Offer ID: " + addressEntry.getShortOfferId(); } else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) { details = "Arbitration fee"; } else { details = "-"; } - field = new HyperlinkWithIcon(details + " (" + item.getFundsInfo() + ")", + field = new HyperlinkWithIcon(details + " (Reserved in offer (local wallet))", AwesomeIcon.INFO_SIGN); field.setOnAction(event -> openDetailPopup(item)); field.setTooltip(new Tooltip("Open popup for details")); diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java index 5839130fb3..d5193b2ea2 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalListItem.java @@ -21,7 +21,6 @@ import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.gui.util.BSFormatter; -import io.bitsquare.trade.TradableHelper; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.closed.ClosedTradableManager; import io.bitsquare.trade.failed.FailedTradesManager; @@ -76,25 +75,20 @@ public class WithdrawalListItem { } private void updateBalance() { - balance = TradableHelper.getAvailableBalance(addressEntry, - walletService, - openOfferManager, - tradeManager, - closedTradableManager, - failedTradesManager); - + balance = walletService.getBalanceForAddress(addressEntry.getAddress()); if (balance != null) balanceLabel.setText(formatter.formatCoin(this.balance)); } public final String getLabel() { - switch (addressEntry.getContext()) { - case TRADE: - return addressEntry.getShortOfferId(); - case ARBITRATOR: - return "Arbitration fee"; - } - return ""; + if (addressEntry.isOpenOffer()) + return "Offer ID: " + addressEntry.getShortOfferId(); + else if (addressEntry.isTrade()) + return "Trade ID: " + addressEntry.getShortOfferId(); + else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) + return "Arbitration fee"; + else + return "-"; } @Override diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java index cd9d45ea11..8f5ae9920c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java @@ -20,6 +20,8 @@ package io.bitsquare.gui.main.funds.withdrawal; import com.google.common.util.concurrent.FutureCallback; import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.app.BitsquareApp; +import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.AddressEntryException; import io.bitsquare.btc.Restrictions; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; @@ -34,6 +36,7 @@ import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.validation.BtcAddressValidator; import io.bitsquare.trade.Tradable; +import io.bitsquare.trade.TradableHelper; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.closed.ClosedTradableManager; @@ -60,7 +63,6 @@ import org.spongycastle.crypto.params.KeyParameter; import javax.inject.Inject; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; @FxmlView public class WithdrawalView extends ActivatableView { @@ -119,7 +121,7 @@ public class WithdrawalView extends ActivatableView { @Override public void initialize() { tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - tableView.setPlaceholder(new Label("No funds for withdrawal are available")); + tableView.setPlaceholder(new Label("No funds are available for withdrawal")); tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); setAddressColumnCellFactory(); @@ -182,7 +184,7 @@ public class WithdrawalView extends ActivatableView { trades.stream() .filter(trade -> trade.getState().getPhase() == Trade.Phase.PAYOUT_PAID) .forEach(trade -> { - if (walletService.getBalanceForAddress(walletService.getTradeAddressEntry(trade.getId()).getAddress()).isZero()) + if (walletService.getBalanceForAddress(walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT).getAddress()).isZero()) tradeManager.addTradeToClosedTrades(trade); }); } @@ -212,13 +214,13 @@ public class WithdrawalView extends ActivatableView { .show(); } - } catch (AddressFormatException e) { + } catch (Throwable e) { e.printStackTrace(); log.error(e.getMessage()); + new Popup().error(e.getMessage()).show(); } } else { - new Popup() - .warning("The amount to transfer is lower than the transaction fee and the min. possible tx value (dust).") + new Popup().warning("The amount to transfer is lower than the transaction fee and the min. possible tx value (dust).") .show(); } } @@ -283,11 +285,9 @@ public class WithdrawalView extends ActivatableView { private void updateList() { observableList.forEach(WithdrawalListItem::cleanup); - observableList.setAll(Stream.concat(walletService.getSavingsAddressEntryList().stream(), walletService.getTradeAddressEntryList().stream()) - .filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isPositive()) + observableList.setAll(TradableHelper.getAddressEntriesForAvailableBalanceStream(walletService) .map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, openOfferManager, tradeManager, closedTradableManager, failedTradesManager, formatter)) - .filter(item -> item.getBalance().isPositive()) .collect(Collectors.toList())); } @@ -306,6 +306,8 @@ public class WithdrawalView extends ActivatableView { updateList(); } catch (AddressFormatException e) { new Popup().warning("The address is not correct. Please check the address format.").show(); + } catch (AddressEntryException e) { + new Popup().error(e.getMessage()).show(); } catch (InsufficientMoneyException e) { log.warn(e.getMessage()); new Popup().warning("You don't have enough fund in your wallet.").show(); @@ -328,7 +330,7 @@ public class WithdrawalView extends ActivatableView { withdrawToTextField.setPromptText("Fill in your destination address"); if (BitsquareApp.DEV_MODE) - withdrawToTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi"); + withdrawToTextField.setText("mjYhQYSbET2bXJDyCdNqYhqSye5QX2WHPz"); } private Optional getTradable(WithdrawalListItem item) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index 2671aead29..678192214a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -135,7 +135,7 @@ class CreateOfferDataModel extends ActivatableDataModel { // isMainNet.set(preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET); offerId = UUID.randomUUID().toString(); - addressEntry = walletService.getTradeAddressEntry(offerId); + addressEntry = walletService.getOrCreateAddressEntry(offerId, AddressEntry.Context.OFFER_FUNDING); offerFeeAsCoin = FeePolicy.getCreateOfferFee(); networkFeeAsCoin = FeePolicy.getFixedTxFeeForTrades(); securityDepositAsCoin = FeePolicy.getSecurityDeposit(); @@ -467,6 +467,7 @@ class CreateOfferDataModel extends ActivatableDataModel { } public void swapTradeToSavings() { - walletService.swapTradeToSavings(offerId); + walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING); + walletService.swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE); } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java index 296c07633f..4db9dc990f 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/takeoffer/TakeOfferDataModel.java @@ -162,7 +162,7 @@ class TakeOfferDataModel extends ActivatableDataModel { void initWithData(Offer offer) { this.offer = offer; - addressEntry = walletService.getTradeAddressEntry(offer.getId()); + addressEntry = walletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING); checkNotNull(addressEntry, "addressEntry must not be null"); ObservableList possiblePaymentAccounts = getPossiblePaymentAccounts(); @@ -381,8 +381,8 @@ class TakeOfferDataModel extends ActivatableDataModel { } public void swapTradeToSavings() { - walletService.swapTradeToSavings(offer.getId()); - //setFeeFromFundingTx(Coin.NEGATIVE_SATOSHI); + walletService.swapTradeEntryToAvailableEntry(offer.getId(), AddressEntry.Context.OFFER_FUNDING); + walletService.swapTradeEntryToAvailableEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); } /* private void setFeeFromFundingTx(Coin fee) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java index 5045110f89..2af5c57a3a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java @@ -356,9 +356,21 @@ public class DisputeSummaryWindow extends Overlay { closeTicketButton.setOnAction(e -> { if (dispute.getDepositTxSerialized() != null) { try { - AddressEntry arbitratorAddressEntry = walletService.getArbitratorAddressEntry(); + AddressEntry arbitratorAddressEntry = walletService.getOrCreateAddressEntry(AddressEntry.Context.ARBITRATOR); disputeResult.setArbitratorAddressAsString(arbitratorAddressEntry.getAddressString()); disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey()); + + /* byte[] depositTxSerialized, + Coin buyerPayoutAmount, + Coin sellerPayoutAmount, + Coin arbitratorPayoutAmount, + String buyerAddressString, + String sellerAddressString, + AddressEntry arbitratorAddressEntry, + byte[] buyerPubKey, + byte[] sellerPubKey, + byte[] arbitratorPubKey) + */ byte[] arbitratorSignature = tradeWalletService.signDisputedPayoutTx( dispute.getDepositTxSerialized(), disputeResult.getBuyerPayoutAmount(), diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/EmptyWalletWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/EmptyWalletWindow.java index 30e51c9904..f056a7f22c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/EmptyWalletWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/EmptyWalletWindow.java @@ -104,7 +104,7 @@ public class EmptyWalletWindow extends Overlay { Tuple2 tuple = addLabelInputTextField(gridPane, ++rowIndex, "Your destination address:"); addressInputTextField = tuple.second; if (BitsquareApp.DEV_MODE) - addressInputTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi"); + addressInputTextField.setText("mjYhQYSbET2bXJDyCdNqYhqSye5QX2WHPz"); emptyWalletButton = new Button("Empty wallet"); boolean isBalanceSufficient = Restrictions.isAboveDust(totalBalance); diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java index 7214577e5a..6055189200 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -354,7 +354,7 @@ public class PendingTradesDataModel extends ActivatableDataModel { payoutTxSerialized = payoutTx.bitcoinSerialize(); payoutTxHashAsString = payoutTx.getHashAsString(); } else { - log.warn("payoutTx is null at doOpenDispute"); + log.debug("payoutTx is null at doOpenDispute"); } Dispute dispute = new Dispute(disputeManager.getDisputeStorage(), diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java index 9528f315f9..edebc27778 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java @@ -20,6 +20,7 @@ package io.bitsquare.gui.main.portfolio.pendingtrades.steps.buyer; import io.bitsquare.app.BitsquareApp; import io.bitsquare.app.Log; import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.AddressEntryException; import io.bitsquare.btc.Restrictions; import io.bitsquare.btc.WalletService; import io.bitsquare.common.util.Tuple2; @@ -135,14 +136,15 @@ public class BuyerStep5View extends TradeStepView { gridPane.getChildren().add(hBox); useSavingsWalletButton.setOnAction(e -> { - model.dataModel.walletService.swapTradeToSavings(trade.getId()); + model.dataModel.walletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); + handleTradeCompleted(); model.dataModel.tradeManager.addTradeToClosedTrades(trade); }); withdrawToExternalWalletButton.setOnAction(e -> reviewWithdrawal()); if (BitsquareApp.DEV_MODE) { - withdrawAddressTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi"); + withdrawAddressTextField.setText("mjYhQYSbET2bXJDyCdNqYhqSye5QX2WHPz"); } else { String key = "tradeCompleted" + trade.getId(); if (!BitsquareApp.DEV_MODE && preferences.showAgain(key)) { @@ -158,7 +160,8 @@ public class BuyerStep5View extends TradeStepView { private void reviewWithdrawal() { Coin senderAmount = trade.getPayoutAmount(); WalletService walletService = model.dataModel.walletService; - AddressEntry fromAddressesEntry = walletService.getTradeAddressEntry(trade.getId()); + + AddressEntry fromAddressesEntry = walletService.getOrCreateAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); String fromAddresses = fromAddressesEntry.getAddressString(); String toAddresses = withdrawAddressTextField.getText(); @@ -176,7 +179,7 @@ public class BuyerStep5View extends TradeStepView { if (BitsquareApp.DEV_MODE) { doWithdrawal(); } else { - Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null); + Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null, AddressEntry.Context.TRADE_PAYOUT); Coin receiverAmount = senderAmount.subtract(requiredFee); BSFormatter formatter = model.formatter; String key = "reviewWithdrawalAtTradeComplete"; @@ -203,6 +206,8 @@ public class BuyerStep5View extends TradeStepView { } } catch (AddressFormatException e) { validateWithdrawAddress(); + } catch (AddressEntryException e) { + log.error(e.getMessage()); } } else { new Popup() diff --git a/network/src/main/java/io/bitsquare/p2p/network/LocalhostNetworkNode.java b/network/src/main/java/io/bitsquare/p2p/network/LocalhostNetworkNode.java index c94e40e301..d2ac32b966 100644 --- a/network/src/main/java/io/bitsquare/p2p/network/LocalhostNetworkNode.java +++ b/network/src/main/java/io/bitsquare/p2p/network/LocalhostNetworkNode.java @@ -28,7 +28,7 @@ public class LocalhostNetworkNode extends NetworkNode { private static final Logger log = LoggerFactory.getLogger(LocalhostNetworkNode.class); private static volatile int simulateTorDelayTorNode = 100; - private static volatile int simulateTorDelayHiddenService = 500; + private static volatile int simulateTorDelayHiddenService = 100; public static void setSimulateTorDelayTorNode(int simulateTorDelayTorNode) { LocalhostNetworkNode.simulateTorDelayTorNode = simulateTorDelayTorNode;