Savings wallet, fixed wrong access to trade wallet (WIP)

This commit is contained in:
Manfred Karrer 2016-04-01 11:36:20 +02:00
parent 59b41c7a4f
commit f07aa9ba6a
8 changed files with 161 additions and 109 deletions

View file

@ -24,11 +24,10 @@ import org.bitcoinj.wallet.CoinSelector;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.*;
import java.util.Collections; import java.util.stream.Collectors;
import java.util.HashSet;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -39,13 +38,100 @@ import static com.google.common.base.Preconditions.checkNotNull;
class SavingsWalletCoinSelector implements CoinSelector { class SavingsWalletCoinSelector implements CoinSelector {
private static final Logger log = LoggerFactory.getLogger(SavingsWalletCoinSelector.class); private static final Logger log = LoggerFactory.getLogger(SavingsWalletCoinSelector.class);
protected final NetworkParameters params; protected final NetworkParameters params;
@Nullable
private AddressEntryList addressEntryList;
@Nullable
private Set<Address> savingsWalletAddressSet;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public SavingsWalletCoinSelector(NetworkParameters params) { public SavingsWalletCoinSelector(NetworkParameters params, AddressEntryList addressEntryList) {
this.params = params; this.params = params;
this.addressEntryList = addressEntryList;
}
protected SavingsWalletCoinSelector(NetworkParameters params) {
this.params = params;
}
protected boolean matchesRequirement(TransactionOutput transactionOutput) {
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
log.trace("only lookup in savings wallet address entries");
log.trace(addressOutput.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;
}
} else {
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<TransactionOutput> candidates) {
log.trace("candidates.size: " + candidates.size());
long targetAsLong = target.longValue();
log.trace("value needed: " + targetAsLong);
HashSet<TransactionOutput> selected = new HashSet<>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
ArrayList<TransactionOutput> 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 @VisibleForTesting
@ -71,61 +157,4 @@ class SavingsWalletCoinSelector implements CoinSelector {
}); });
} }
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);
}
protected boolean matchesRequirement(TransactionOutput transactionOutput) {
return (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash());
}
@Override
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
log.trace("candidates.size: " + candidates.size());
long targetAsLong = target.longValue();
log.trace("value needed: " + targetAsLong);
HashSet<TransactionOutput> selected = new HashSet<>();
// Sort the inputs by age*value so we get the highest "coindays" spent.
ArrayList<TransactionOutput> 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);
}
} }

View file

@ -101,6 +101,7 @@ public class TradeWalletService {
private WalletAppKit walletAppKit; private WalletAppKit walletAppKit;
@Nullable @Nullable
private KeyParameter aesKey; private KeyParameter aesKey;
private AddressEntryList addressEntryList;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -158,7 +159,7 @@ public class TradeWalletService {
sendRequest.shuffleOutputs = false; sendRequest.shuffleOutputs = false;
sendRequest.aesKey = aesKey; sendRequest.aesKey = aesKey;
if (useSavingsWallet) if (useSavingsWallet)
sendRequest.coinSelector = new SavingsWalletCoinSelector(params); sendRequest.coinSelector = new SavingsWalletCoinSelector(params, addressEntryList);
else else
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry);
// We use a fixed fee // We use a fixed fee
@ -1064,4 +1065,8 @@ public class TradeWalletService {
throw new WalletException(t); throw new WalletException(t);
} }
} }
public void setAddressEntryList(AddressEntryList addressEntryList) {
this.addressEntryList = addressEntryList;
}
} }

View file

@ -188,6 +188,7 @@ public class WalletService {
// set after wallet is ready // set after wallet is ready
tradeWalletService.setWalletAppKit(walletAppKit); tradeWalletService.setWalletAppKit(walletAppKit);
tradeWalletService.setAddressEntryList(addressEntryList);
timeoutTimer.stop(); timeoutTimer.stop();
// onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay // onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay

View file

@ -205,25 +205,6 @@ public class DepositView extends ActivatableView<VBox, Void> {
}; };
} }
private Coin getAmountAsCoin() {
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
senderAmount = Coin.ZERO;
/* new Popup()
.warning("The amount is lower than the transaction fee and the min. possible tx value (dust).")
.show();*/
}
return senderAmount;
}
@NotNull
private String getBitcoinURI() {
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
getAmountAsCoin(),
paymentLabel,
null);
}
@Override @Override
protected void activate() { protected void activate() {
tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener); tableView.getSelectionModel().selectedItemProperty().addListener(tableViewSelectionListener);
@ -236,6 +217,9 @@ public class DepositView extends ActivatableView<VBox, Void> {
addressTextField.setAmountAsCoin(formatter.parseToCoin(t)); addressTextField.setAmountAsCoin(formatter.parseToCoin(t));
updateQRCode(); updateQRCode();
}); });
if (tableView.getSelectionModel().getSelectedItem() == null && !sortedList.isEmpty())
tableView.getSelectionModel().select(0);
} }
@Override @Override
@ -309,6 +293,24 @@ public class DepositView extends ActivatableView<VBox, Void> {
.forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter))); .forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter)));
} }
private Coin getAmountAsCoin() {
Coin senderAmount = formatter.parseToCoin(amountTextField.getText());
if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) {
senderAmount = Coin.ZERO;
/* new Popup()
.warning("The amount is lower than the transaction fee and the min. possible tx value (dust).")
.show();*/
}
return senderAmount;
}
@NotNull
private String getBitcoinURI() {
return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(),
getAmountAsCoin(),
paymentLabel,
null);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// ColumnCellFactories // ColumnCellFactories

View file

@ -228,7 +228,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
calculateVolume(); calculateVolume();
calculateTotalToPay(); calculateTotalToPay();
updateBalance();
} }
void onTabSelected(boolean isSelected) { void onTabSelected(boolean isSelected) {
@ -394,11 +393,13 @@ class CreateOfferDataModel extends ActivatableDataModel {
} }
void calculateTotalToPay() { void calculateTotalToPay() {
if (securityDepositAsCoin != null) { if (direction != null && amountAsCoin.get() != null) {
Coin feeAndSecDeposit = offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin); Coin feeAndSecDeposit = offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin);
Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get()); Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amountAsCoin.get());
Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDepositAndAmount; Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDepositAndAmount;
totalToPayAsCoin.set(required); totalToPayAsCoin.set(required);
log.debug("totalToPayAsCoin " + totalToPayAsCoin.get().toFriendlyString());
updateBalance();
} }
} }
@ -407,11 +408,12 @@ class CreateOfferDataModel extends ActivatableDataModel {
if (useSavingsWallet) { if (useSavingsWallet) {
Coin savingWalletBalance = walletService.getSavingWalletBalance(); Coin savingWalletBalance = walletService.getSavingWalletBalance();
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
if (totalToPayAsCoin.get() != null) {
if (totalToPayAsCoin.get() != null && totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
balance.set(totalToPayAsCoin.get()); balance.set(totalToPayAsCoin.get());
else else
balance.set(totalAvailableBalance); balance.set(totalAvailableBalance);
}
} else { } else {
balance.set(tradeWalletBalance); balance.set(tradeWalletBalance);
} }
@ -422,6 +424,8 @@ class CreateOfferDataModel extends ActivatableDataModel {
missingCoin.set(Coin.ZERO); missingCoin.set(Coin.ZERO);
} }
log.debug("missingCoin " + missingCoin.get().toFriendlyString());
isWalletFunded.set(isBalanceSufficient(balance.get())); isWalletFunded.set(isBalanceSufficient(balance.get()));
if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) { if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) {
walletFundedNotification = new Notification() walletFundedNotification = new Notification()

View file

@ -176,7 +176,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
calculateVolume(); calculateVolume();
calculateTotalToPay(); calculateTotalToPay();
updateBalance();
balanceListener = new BalanceListener(addressEntry.getAddress()) { balanceListener = new BalanceListener(addressEntry.getAddress()) {
@Override @Override
@ -325,11 +324,21 @@ class TakeOfferDataModel extends ActivatableDataModel {
} }
} }
void setAmount(Coin amount) {
amountAsCoin.set(amount);
calculateTotalToPay();
}
void calculateTotalToPay() { void calculateTotalToPay() {
if (getDirection() == Offer.Direction.SELL) if (offer != null && amountAsCoin.get() != null) {
totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin)); if (getDirection() == Offer.Direction.SELL)
else totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin));
totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get())); else
totalToPayAsCoin.set(takerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get()));
updateBalance();
log.debug("totalToPayAsCoin " + totalToPayAsCoin.get().toFriendlyString());
}
} }
void updateBalance() { void updateBalance() {
@ -337,11 +346,12 @@ class TakeOfferDataModel extends ActivatableDataModel {
if (useSavingsWallet) { if (useSavingsWallet) {
Coin savingWalletBalance = walletService.getSavingWalletBalance(); Coin savingWalletBalance = walletService.getSavingWalletBalance();
totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance);
if (totalToPayAsCoin.get() != null) {
if (totalToPayAsCoin.get() != null && totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0)
balance.set(totalToPayAsCoin.get()); balance.set(totalToPayAsCoin.get());
else else
balance.set(totalAvailableBalance); balance.set(totalAvailableBalance);
}
} else { } else {
balance.set(tradeWalletBalance); balance.set(tradeWalletBalance);
} }
@ -350,17 +360,18 @@ class TakeOfferDataModel extends ActivatableDataModel {
if (missingCoin.get().isNegative()) if (missingCoin.get().isNegative())
missingCoin.set(Coin.ZERO); missingCoin.set(Coin.ZERO);
} }
log.debug("missingCoin " + missingCoin.get().toFriendlyString());
isWalletFunded.set(isBalanceSufficient(balance.get())); isWalletFunded.set(isBalanceSufficient(balance.get()));
if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) { if (totalToPayAsCoin.get() != null && isWalletFunded.get() && walletFundedNotification == null) {
walletFundedNotification = new Notification() walletFundedNotification = new Notification()
.headLine("Trading wallet update") .headLine("Trading wallet update")
.notification("Your trading wallet is sufficiently funded.\n" + .notification("Your trading wallet is sufficiently funded.\n" +
"Amount: " + formatter.formatCoinWithCode(totalToPayAsCoin.get())) "Amount: " + formatter.formatCoinWithCode(totalToPayAsCoin.get()))
.autoClose(); .autoClose();
walletFundedNotification.show(); walletFundedNotification.show();
} }
} }
private boolean isBalanceSufficient(Coin balance) { private boolean isBalanceSufficient(Coin balance) {

View file

@ -520,7 +520,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
} }
private void setAmountToModel() { private void setAmountToModel() {
dataModel.amountAsCoin.set(formatter.parseToCoinWith4Decimals(amount.get())); dataModel.setAmount(formatter.parseToCoinWith4Decimals(amount.get()));
} }

View file

@ -228,7 +228,7 @@ public class SellerStep3View extends TradeStepView {
if (model.p2PService.isBootstrapped()) { if (model.p2PService.isBootstrapped()) {
Preferences preferences = model.dataModel.preferences; Preferences preferences = model.dataModel.preferences;
String key = "confirmPaymentReceived"; String key = "confirmPaymentReceived";
if (preferences.showAgain(key)) { if (!BitsquareApp.DEV_MODE && preferences.showAgain(key)) {
new Popup() new Popup()
.headLine("Confirm that you have received the payment") .headLine("Confirm that you have received the payment")
.confirmation("Have you received the " + CurrencyUtil.getNameByCode(model.dataModel.getCurrencyCode()) + .confirmation("Have you received the " + CurrencyUtil.getNameByCode(model.dataModel.getCurrencyCode()) +