Skip to content

Commit

Permalink
Merge pull request #5142 from chimp1984/add-coin-input-control
Browse files Browse the repository at this point in the history
Add coin input control
  • Loading branch information
ripcurlx authored Feb 19, 2021
2 parents cc773b8 + bc6a53d commit 314e6ce
Show file tree
Hide file tree
Showing 9 changed files with 508 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@
import java.util.List;
import java.util.Set;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;

/**
* Used from org.bitcoinj.wallet.DefaultCoinSelector but added selectOutput method and changed static methods to
* instance methods.
Expand All @@ -49,6 +52,12 @@ public abstract class BisqDefaultCoinSelector implements CoinSelector {

protected final boolean permitForeignPendingTx;

// TransactionOutputs to be used as candidates in the select method.
// We reset the value to null just after we have applied it inside the select method.
@Nullable
@Setter
protected Set<TransactionOutput> utxoCandidates;

public CoinSelection select(Coin target, Set<TransactionOutput> candidates) {
return select(target, new ArrayList<>(candidates));
}
Expand All @@ -65,7 +74,16 @@ public BisqDefaultCoinSelector() {
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
ArrayList<TransactionOutput> selected = new ArrayList<>();
// Sort the inputs by age*value so we get the highest "coin days" spent.
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);

ArrayList<TransactionOutput> sortedOutputs;
if (utxoCandidates != null) {
sortedOutputs = new ArrayList<>(utxoCandidates);
// We reuse the selectors. Reset the transactionOutputCandidates field
utxoCandidates = null;
} else {
sortedOutputs = new ArrayList<>(candidates);
}

// If we spend all we don't need to sort
if (!target.equals(NetworkParameters.MAX_MONEY))
sortOutputs(sortedOutputs);
Expand Down Expand Up @@ -120,6 +138,9 @@ protected boolean isTxSpendable(Transaction tx) {

abstract boolean isTxOutputSpendable(TransactionOutput output);

// TODO Why it uses coin age and not try to minimize number of inputs as the highest priority?
// Asked Oscar and he also don't knows why coin age is used. Should be changed so that min. number of inputs is
// target.
protected void sortOutputs(ArrayList<TransactionOutput> outputs) {
Collections.sort(outputs, (a, b) -> {
int depth1 = a.getParentTransactionDepthInBlocks();
Expand Down
42 changes: 40 additions & 2 deletions core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.bitcoinj.core.TransactionConfidence.ConfidenceType.BUILDING;
Expand Down Expand Up @@ -135,6 +137,8 @@ public BsqWalletService(WalletsSetup walletsSetup,
this.unconfirmedBsqChangeOutputListService = unconfirmedBsqChangeOutputListService;
this.daoKillSwitch = daoKillSwitch;

nonBsqCoinSelector.setPreferences(preferences);

walletsSetup.addSetupCompletedHandler(() -> {
wallet = walletsSetup.getBsqWallet();
if (wallet != null) {
Expand Down Expand Up @@ -313,6 +317,16 @@ public void removeWalletTransactionsChangeListener(WalletTransactionsChangeListe
walletTransactionsChangeListeners.remove(listener);
}

public List<TransactionOutput> getSpendableBsqTransactionOutputs() {
return new ArrayList<>(bsqCoinSelector.select(NetworkParameters.MAX_MONEY,
wallet.calculateAllSpendCandidates()).gathered);
}

public List<TransactionOutput> getSpendableNonBsqTransactionOutputs() {
return new ArrayList<>(nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY,
wallet.calculateAllSpendCandidates()).gathered);
}


///////////////////////////////////////////////////////////////////////////////////////////
// BSQ TransactionOutputs and Transactions
Expand Down Expand Up @@ -511,7 +525,19 @@ public void commitTx(Transaction tx, TxType txType) {
///////////////////////////////////////////////////////////////////////////////////////////

public Transaction getPreparedSendBsqTx(String receiverAddress, Coin receiverAmount)
throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException {
throws AddressFormatException, InsufficientBsqException, WalletException,
TransactionVerificationException, BsqChangeBelowDustException {
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false);
}

public Transaction getPreparedSendBsqTx(String receiverAddress,
Coin receiverAmount,
@Nullable Set<TransactionOutput> utxoCandidates)
throws AddressFormatException, InsufficientBsqException, WalletException,
TransactionVerificationException, BsqChangeBelowDustException {
if (utxoCandidates != null) {
bsqCoinSelector.setUtxoCandidates(utxoCandidates);
}
return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false);
}

Expand All @@ -520,7 +546,19 @@ public Transaction getPreparedSendBsqTx(String receiverAddress, Coin receiverAmo
///////////////////////////////////////////////////////////////////////////////////////////

public Transaction getPreparedSendBtcTx(String receiverAddress, Coin receiverAmount)
throws AddressFormatException, InsufficientBsqException, WalletException, TransactionVerificationException, BsqChangeBelowDustException {
throws AddressFormatException, InsufficientBsqException, WalletException,
TransactionVerificationException, BsqChangeBelowDustException {
return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true);
}

public Transaction getPreparedSendBtcTx(String receiverAddress,
Coin receiverAmount,
@Nullable Set<TransactionOutput> utxoCandidates)
throws AddressFormatException, InsufficientBsqException, WalletException,
TransactionVerificationException, BsqChangeBelowDustException {
if (utxoCandidates != null) {
nonBsqCoinSelector.setUtxoCandidates(utxoCandidates);
}
return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@

import bisq.core.dao.state.DaoStateService;
import bisq.core.dao.state.model.blockchain.TxOutputKey;
import bisq.core.user.Preferences;

import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionOutput;

import javax.inject.Inject;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
Expand All @@ -35,6 +37,8 @@
@Slf4j
public class NonBsqCoinSelector extends BisqDefaultCoinSelector {
private DaoStateService daoStateService;
@Setter
private Preferences preferences;

@Inject
public NonBsqCoinSelector(DaoStateService daoStateService) {
Expand All @@ -60,9 +64,9 @@ protected boolean isTxOutputSpendable(TransactionOutput output) {
return !daoStateService.existsTxOutput(key) || daoStateService.isRejectedIssuanceOutput(key);
}

// BTC utxo in the BSQ wallet are usually from rejected comp request so we don't expect dust attack utxos here.
// Prevent usage of dust attack utxos
@Override
protected boolean isDustAttackUtxo(TransactionOutput output) {
return false;
return output.getValue().value < preferences.getIgnoreDustThreshold();
}
}
5 changes: 5 additions & 0 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ shared.offerType=Offer type
shared.details=Details
shared.address=Address
shared.balanceWithCur=Balance in {0}
shared.utxo=Unspent transaction output
shared.txId=Transaction ID
shared.confirmations=Confirmations
shared.revert=Revert Tx
Expand Down Expand Up @@ -2270,6 +2271,7 @@ dao.wallet.send.receiverAddress=Receiver's BSQ address
dao.wallet.send.receiverBtcAddress=Receiver's BTC address
dao.wallet.send.setDestinationAddress=Fill in your destination address
dao.wallet.send.send=Send BSQ funds
dao.wallet.send.inputControl=Select inputs
dao.wallet.send.sendBtc=Send BTC funds
dao.wallet.send.sendFunds.headline=Confirm withdrawal request
dao.wallet.send.sendFunds.details=Sending: {0}\nTo receiving address: {1}.\nRequired mining fee is: {2} ({3} satoshis/vbyte)\nTransaction vsize: {4} vKb\n\nThe recipient will receive: {5}\n\nAre you sure you want to withdraw that amount?
Expand Down Expand Up @@ -2487,6 +2489,9 @@ dao.factsAndFigures.transactions.irregularTx=No. of all irregular transactions
# Windows
####################################################################

inputControlWindow.headline=Select inputs for transaction
inputControlWindow.balanceLabel=Available balance

contractWindow.title=Dispute details
contractWindow.dates=Offer date / Trade date
contractWindow.btcAddresses=Bitcoin address BTC buyer / BTC seller
Expand Down
20 changes: 16 additions & 4 deletions desktop/src/main/java/bisq/desktop/components/InputTextField.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public InputTextField() {

validationResult.addListener((ov, oldValue, newValue) -> {
if (newValue != null) {
resetValidation();
jfxValidationWrapper.resetValidation();
if (!newValue.isValid) {
if (!newValue.errorMessageEquals(oldValue)) { // avoid blinking
validate(); // ensure that the new error message replaces the old one
Expand All @@ -92,9 +92,7 @@ public InputTextField() {
});

textProperty().addListener((o, oldValue, newValue) -> {
if (validator != null) {
this.validationResult.set(validator.validate(getText()));
}
refreshValidation();
});

focusedProperty().addListener((o, oldValue, newValue) -> {
Expand All @@ -108,6 +106,7 @@ public InputTextField() {
});
}


public InputTextField(double inputLineExtension) {
this();
this.inputLineExtension = inputLineExtension;
Expand All @@ -119,6 +118,19 @@ public InputTextField(double inputLineExtension) {

public void resetValidation() {
jfxValidationWrapper.resetValidation();

String input = getText();
if (input.isEmpty()) {
validationResult.set(new InputValidator.ValidationResult(true));
} else {
validationResult.set(validator.validate(input));
}
}

public void refreshValidation() {
if (validator != null) {
this.validationResult.set(validator.validate(getText()));
}
}

///////////////////////////////////////////////////////////////////////////////////////////
Expand Down
Loading

0 comments on commit 314e6ce

Please sign in to comment.