From 8bd03d26895f7b8310c1396958b11882633654b3 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Thu, 3 Dec 2020 13:32:01 -0600 Subject: [PATCH 01/25] Start pricenode providers asynchronously Fixes #4441 --- .../src/main/java/bisq/price/PriceProvider.java | 15 +++++++++------ .../java/bisq/price/mining/FeeRateService.java | 8 ++++++++ .../java/bisq/price/spot/ExchangeRateService.java | 4 ++++ .../providers/MempoolFeeRateProviderTest.java | 3 +++ .../bisq/price/spot/ExchangeRateServiceTest.java | 3 +++ 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/PriceProvider.java b/pricenode/src/main/java/bisq/price/PriceProvider.java index 13b1dc7b0a8..863d740acf2 100644 --- a/pricenode/src/main/java/bisq/price/PriceProvider.java +++ b/pricenode/src/main/java/bisq/price/PriceProvider.java @@ -17,12 +17,15 @@ package bisq.price; +import bisq.common.UserThread; + import org.springframework.context.SmartLifecycle; import java.time.Duration; import java.util.Timer; import java.util.TimerTask; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.slf4j.Logger; @@ -45,17 +48,17 @@ public PriceProvider(Duration refreshInterval) { @Override public final T get() { - if (!isRunning()) - throw new IllegalStateException("call start() before calling get()"); - return cachedResult; } @Override public final void start() { - // we call refresh outside the context of a timer once at startup to ensure that - // any exceptions thrown get propagated and cause the application to halt - refresh(); + // do the initial refresh asynchronously + UserThread.runAfter(() -> { + try { refresh(); } catch (Throwable t) { + log.warn("initial refresh failed", t); + } + }, 1, TimeUnit.MILLISECONDS); timer.scheduleAtFixedRate(new TimerTask() { @Override diff --git a/pricenode/src/main/java/bisq/price/mining/FeeRateService.java b/pricenode/src/main/java/bisq/price/mining/FeeRateService.java index 43116dd24a2..7234e636c8b 100644 --- a/pricenode/src/main/java/bisq/price/mining/FeeRateService.java +++ b/pricenode/src/main/java/bisq/price/mining/FeeRateService.java @@ -27,6 +27,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * High-level mining {@link FeeRate} operations. */ @@ -34,6 +37,7 @@ class FeeRateService { private final List providers; + protected final Logger log = LoggerFactory.getLogger(this.getClass()); /** * Construct a {@link FeeRateService} with a list of all {@link FeeRateProvider} @@ -56,6 +60,10 @@ public Map getFees() { // Process each provider, retrieve and store their fee rate providers.forEach(p -> { FeeRate feeRate = p.get(); + if (feeRate == null) { + log.warn("feeRate is null, provider={} ", p.toString()); + return; + } String currency = feeRate.getCurrency(); if ("BTC".equals(currency)) { sumOfAllFeeRates.getAndAdd(feeRate.getPrice()); diff --git a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java index 6fcf4a6eb2b..e1300b6d5a2 100644 --- a/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java +++ b/pricenode/src/main/java/bisq/price/spot/ExchangeRateService.java @@ -61,6 +61,8 @@ public Map getAllMarketPrices() { Map aggregateExchangeRates = getAggregateExchangeRates(); providers.forEach(p -> { + if (p.get() == null) + return; Set exchangeRates = p.get(); // Specific metadata fields for specific providers are expected by the client, @@ -136,6 +138,8 @@ private Map getAggregateExchangeRates() { private Map> getCurrencyCodeToExchangeRates() { Map> currencyCodeToExchangeRates = new HashMap<>(); for (ExchangeRateProvider p : providers) { + if (p.get() == null) + continue; for (ExchangeRate exchangeRate : p.get()) { String currencyCode = exchangeRate.getCurrency(); if (currencyCodeToExchangeRates.containsKey(currencyCode)) { diff --git a/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java b/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java index e15a3e18a74..f9d3fe43cd9 100644 --- a/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java +++ b/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; +import static java.lang.Thread.sleep; import static org.junit.Assert.assertTrue; /** @@ -66,6 +67,7 @@ protected FeeRate doGet() { // Initialize provider dummyProvider.start(); + try { sleep(1000); } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; @@ -86,6 +88,7 @@ protected FeeRate doGet() { // Initialize provider dummyProvider.start(); + try { sleep(1000); } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; diff --git a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java index 61d2ffbee6a..4b681d68ea6 100644 --- a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java +++ b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import static java.lang.Thread.sleep; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -287,6 +288,7 @@ protected Set doGet() { // Initialize provider dummyProvider.start(); + try { sleep(1000); } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; @@ -322,6 +324,7 @@ protected Set doGet() { // Initialize provider dummyProvider.start(); + try { sleep(1000); } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; From 192a905231dee1e60b719f4ac4fb2d52ee8fb256 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Thu, 3 Dec 2020 14:01:54 -0600 Subject: [PATCH 02/25] codacy --- pricenode/src/main/java/bisq/price/PriceProvider.java | 4 +++- .../mining/providers/MempoolFeeRateProviderTest.java | 8 ++++++-- .../java/bisq/price/spot/ExchangeRateServiceTest.java | 8 ++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pricenode/src/main/java/bisq/price/PriceProvider.java b/pricenode/src/main/java/bisq/price/PriceProvider.java index 863d740acf2..cbeefd9422b 100644 --- a/pricenode/src/main/java/bisq/price/PriceProvider.java +++ b/pricenode/src/main/java/bisq/price/PriceProvider.java @@ -55,7 +55,9 @@ public final T get() { public final void start() { // do the initial refresh asynchronously UserThread.runAfter(() -> { - try { refresh(); } catch (Throwable t) { + try { + refresh(); + } catch (Throwable t) { log.warn("initial refresh failed", t); } }, 1, TimeUnit.MILLISECONDS); diff --git a/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java b/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java index f9d3fe43cd9..153f35687a5 100644 --- a/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java +++ b/pricenode/src/test/java/bisq/price/mining/providers/MempoolFeeRateProviderTest.java @@ -67,7 +67,9 @@ protected FeeRate doGet() { // Initialize provider dummyProvider.start(); - try { sleep(1000); } catch (InterruptedException e) { } + try { + sleep(1000); + } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; @@ -88,7 +90,9 @@ protected FeeRate doGet() { // Initialize provider dummyProvider.start(); - try { sleep(1000); } catch (InterruptedException e) { } + try { + sleep(1000); + } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; diff --git a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java index 4b681d68ea6..fefb3c85bff 100644 --- a/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java +++ b/pricenode/src/test/java/bisq/price/spot/ExchangeRateServiceTest.java @@ -288,7 +288,9 @@ protected Set doGet() { // Initialize provider dummyProvider.start(); - try { sleep(1000); } catch (InterruptedException e) { } + try { + sleep(1000); + } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; @@ -324,7 +326,9 @@ protected Set doGet() { // Initialize provider dummyProvider.start(); - try { sleep(1000); } catch (InterruptedException e) { } + try { + sleep(1000); + } catch (InterruptedException e) { } dummyProvider.stop(); return dummyProvider; From 2b9b9ef8a8e5abd7059ad56862c19cf0d3d59fe0 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 31 Jan 2021 18:40:30 -0500 Subject: [PATCH 03/25] Add refreshValidation method --- .../java/bisq/desktop/components/InputTextField.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/components/InputTextField.java b/desktop/src/main/java/bisq/desktop/components/InputTextField.java index 73130745062..064f7d0f8ba 100644 --- a/desktop/src/main/java/bisq/desktop/components/InputTextField.java +++ b/desktop/src/main/java/bisq/desktop/components/InputTextField.java @@ -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) -> { @@ -108,6 +106,7 @@ public InputTextField() { }); } + public InputTextField(double inputLineExtension) { this(); this.inputLineExtension = inputLineExtension; @@ -121,6 +120,12 @@ public void resetValidation() { jfxValidationWrapper.resetValidation(); } + public void refreshValidation() { + if (validator != null) { + this.validationResult.set(validator.validate(getText())); + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// From 24370146e6329299b6eac1cb4fe5c94939b66956 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 31 Jan 2021 21:36:10 -0500 Subject: [PATCH 04/25] Implement isDustAttackUtxo protection --- .../java/bisq/core/btc/wallet/NonBsqCoinSelector.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java index 12b77dfefa8..6aaa5d75f10 100644 --- a/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java @@ -19,6 +19,7 @@ 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; @@ -26,6 +27,7 @@ import javax.inject.Inject; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** @@ -35,6 +37,8 @@ @Slf4j public class NonBsqCoinSelector extends BisqDefaultCoinSelector { private DaoStateService daoStateService; + @Setter + private Preferences preferences; @Inject public NonBsqCoinSelector(DaoStateService daoStateService) { @@ -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 to use dust attack utxos @Override protected boolean isDustAttackUtxo(TransactionOutput output) { - return false; + return output.getValue().value < preferences.getIgnoreDustThreshold(); } } From 64e2d2b2c45a4070f798af0a0933f54a77bac389 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 31 Jan 2021 21:36:37 -0500 Subject: [PATCH 05/25] Add addButtonCheckBoxWithBox method with top param --- .../main/java/bisq/desktop/util/FormBuilder.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java index 94013d6acab..4cabdebec3b 100644 --- a/desktop/src/main/java/bisq/desktop/util/FormBuilder.java +++ b/desktop/src/main/java/bisq/desktop/util/FormBuilder.java @@ -906,10 +906,10 @@ public static Tuple2 addButtonCheckBox(GridPane gridPane, } public static Tuple3 addButtonCheckBoxWithBox(GridPane gridPane, - int rowIndex, - String buttonTitle, - String checkBoxTitle, - double top) { + int rowIndex, + String buttonTitle, + String checkBoxTitle, + double top) { Button button = new AutoTooltipButton(buttonTitle); CheckBox checkBox = new AutoTooltipCheckBox(checkBoxTitle); @@ -1642,11 +1642,14 @@ public static Tuple3 addLabelBsqAddressTextFie public static BalanceTextField addBalanceTextField(GridPane gridPane, int rowIndex, String title) { + return addBalanceTextField(gridPane, rowIndex, title, 20); + } + public static BalanceTextField addBalanceTextField(GridPane gridPane, int rowIndex, String title, double top) { BalanceTextField balanceTextField = new BalanceTextField(title); GridPane.setRowIndex(balanceTextField, rowIndex); GridPane.setColumnIndex(balanceTextField, 0); - GridPane.setMargin(balanceTextField, new Insets(20, 0, 0, 0)); + GridPane.setMargin(balanceTextField, new Insets(top, 0, 0, 0)); gridPane.getChildren().add(balanceTextField); return balanceTextField; From 06a7ecd382c7933d905e382c725e9a9093f15827 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 31 Jan 2021 21:37:42 -0500 Subject: [PATCH 06/25] Add support for utxo set used in coin selection if set We use that to use only the selected utxos instead of all available. --- .../btc/wallet/BisqDefaultCoinSelector.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java index 57923d0ab32..fa26a7ef60e 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java @@ -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. @@ -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 utxoCandidates; + public CoinSelection select(Coin target, Set candidates) { return select(target, new ArrayList<>(candidates)); } @@ -65,7 +74,16 @@ public BisqDefaultCoinSelector() { public CoinSelection select(Coin target, List candidates) { ArrayList selected = new ArrayList<>(); // Sort the inputs by age*value so we get the highest "coin days" spent. - ArrayList sortedOutputs = new ArrayList<>(candidates); + + ArrayList sortedOutputs; + if (utxoCandidates != null) { + sortedOutputs = new ArrayList<>(utxoCandidates); + // We we reuse the selectors we 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); @@ -120,6 +138,7 @@ 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? protected void sortOutputs(ArrayList outputs) { Collections.sort(outputs, (a, b) -> { int depth1 = a.getParentTransactionDepthInBlocks(); From b345918219a26b3f337767f2a5b22d8bc9d91a37 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 31 Jan 2021 21:40:08 -0500 Subject: [PATCH 07/25] Add getSpendableBsqTransactionOutputs and getSpendableNonBsqTransactionOutputs methods. Add overridden getPreparedSendBsqTx and getPreparedSendBtcTx methods with utxoCandidates param. If utxoCandidates is not null we apply it to our coinSelector. As the coin selector is re-used we re-set it immediately after it was applied (inside coin selector select method). Set preferences in nonBsqCoinSelector --- .../core/btc/wallet/BsqWalletService.java | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java index 55c2f58352c..e8e1afa98cc 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java +++ b/core/src/main/java/bisq/core/btc/wallet/BsqWalletService.java @@ -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; @@ -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) { @@ -313,6 +317,16 @@ public void removeWalletTransactionsChangeListener(WalletTransactionsChangeListe walletTransactionsChangeListeners.remove(listener); } + public List getSpendableBsqTransactionOutputs() { + return new ArrayList<>(bsqCoinSelector.select(NetworkParameters.MAX_MONEY, + wallet.calculateAllSpendCandidates()).gathered); + } + + public List getSpendableNonBsqTransactionOutputs() { + return new ArrayList<>(nonBsqCoinSelector.select(NetworkParameters.MAX_MONEY, + wallet.calculateAllSpendCandidates()).gathered); + } + /////////////////////////////////////////////////////////////////////////////////////////// // BSQ TransactionOutputs and Transactions @@ -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 utxoCandidates) + throws AddressFormatException, InsufficientBsqException, WalletException, + TransactionVerificationException, BsqChangeBelowDustException { + if (utxoCandidates != null) { + bsqCoinSelector.setUtxoCandidates(utxoCandidates); + } return getPreparedSendTx(receiverAddress, receiverAmount, bsqCoinSelector, false); } @@ -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 utxoCandidates) + throws AddressFormatException, InsufficientBsqException, WalletException, + TransactionVerificationException, BsqChangeBelowDustException { + if (utxoCandidates != null) { + nonBsqCoinSelector.setUtxoCandidates(utxoCandidates); + } return getPreparedSendTx(receiverAddress, receiverAmount, nonBsqCoinSelector, true); } From fdad973f4c65cad8e2233ac8f1b73ce38ecaa1fa Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 31 Jan 2021 21:41:08 -0500 Subject: [PATCH 08/25] Add TxInputSelectionWindow for coin input selection. It is both for BSQ and non-BSQ (BTC) used. --- .../resources/i18n/displayStrings.properties | 5 + .../main/dao/wallet/send/BsqSendView.java | 200 ++++++++++---- .../windows/TxInputSelectionWindow.java | 246 ++++++++++++++++++ 3 files changed, 406 insertions(+), 45 deletions(-) create mode 100644 desktop/src/main/java/bisq/desktop/main/overlays/windows/TxInputSelectionWindow.java diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 98513e3b324..8ff969e4fbc 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -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 @@ -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? @@ -2470,6 +2472,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 diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java index 078b967a0e8..7b3a3d035ca 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java @@ -28,7 +28,9 @@ import bisq.desktop.main.funds.deposit.DepositView; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.TxDetailsBsq; +import bisq.desktop.main.overlays.windows.TxInputSelectionWindow; import bisq.desktop.main.overlays.windows.WalletPasswordWindow; +import bisq.desktop.util.FormBuilder; import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.desktop.util.validation.BsqAddressValidator; @@ -47,6 +49,7 @@ import bisq.core.dao.state.model.blockchain.TxType; import bisq.core.locale.Res; import bisq.core.user.DontShowAgainLookup; +import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; import bisq.core.util.coin.BsqFormatter; @@ -58,10 +61,12 @@ import bisq.common.UserThread; import bisq.common.handlers.ResultHandler; +import bisq.common.util.Tuple2; import org.bitcoinj.core.Coin; import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; import javax.inject.Inject; import javax.inject.Named; @@ -71,9 +76,14 @@ import javafx.beans.value.ChangeListener; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; -import static bisq.desktop.util.FormBuilder.addButtonAfterGroup; import static bisq.desktop.util.FormBuilder.addInputTextField; import static bisq.desktop.util.FormBuilder.addTitledGroupBg; @@ -92,15 +102,20 @@ public class BsqSendView extends ActivatableView implements BsqB private final BtcValidator btcValidator; private final BsqAddressValidator bsqAddressValidator; private final BtcAddressValidator btcAddressValidator; + private final Preferences preferences; private final WalletPasswordWindow walletPasswordWindow; private int gridRow = 0; private InputTextField amountInputTextField, btcAmountInputTextField; - private Button sendBsqButton, sendBtcButton; + private Button sendBsqButton, sendBtcButton, bsqInputControlButton, btcInputControlButton; private InputTextField receiversAddressInputTextField, receiversBtcAddressInputTextField; private ChangeListener focusOutListener; private TitledGroupBg btcTitledGroupBg; private ChangeListener inputTextFieldListener; + @Nullable + private Set bsqUtxoCandidates; + @Nullable + private Set btcUtxoCandidates; /////////////////////////////////////////////////////////////////////////////////////////// @@ -121,6 +136,7 @@ private BsqSendView(BsqWalletService bsqWalletService, BtcValidator btcValidator, BsqAddressValidator bsqAddressValidator, BtcAddressValidator btcAddressValidator, + Preferences preferences, WalletPasswordWindow walletPasswordWindow) { this.bsqWalletService = bsqWalletService; this.btcWalletService = btcWalletService; @@ -135,6 +151,7 @@ private BsqSendView(BsqWalletService bsqWalletService, this.btcValidator = btcValidator; this.bsqAddressValidator = bsqAddressValidator; this.btcAddressValidator = btcAddressValidator; + this.preferences = preferences; this.walletPasswordWindow = walletPasswordWindow; } @@ -171,11 +188,16 @@ protected void activate() { bsqWalletService.addBsqBalanceListener(this); + // We reset the input selection at active to have all inputs selected, otherwise the user + // might get confused if he had deselected inputs earlier and cannot spend the full balance. + bsqUtxoCandidates = null; + btcUtxoCandidates = null; + onUpdateBalances(); } private void onUpdateBalances() { - onUpdateBalances(bsqWalletService.getAvailableConfirmedBalance(), + onUpdateBalances(getSpendableBsqBalance(), bsqWalletService.getAvailableNonBsqBalance(), bsqWalletService.getUnverifiedBalance(), bsqWalletService.getUnconfirmedChangeBalance(), @@ -200,6 +222,11 @@ protected void deactivate() { btcAmountInputTextField.textProperty().removeListener(inputTextFieldListener); bsqWalletService.removeBsqBalanceListener(this); + + sendBsqButton.setOnAction(null); + btcInputControlButton.setOnAction(null); + sendBtcButton.setOnAction(null); + bsqInputControlButton.setOnAction(null); } @Override @@ -210,16 +237,24 @@ public void onUpdateBalances(Coin availableConfirmedBalance, Coin lockedForVotingBalance, Coin lockupBondsBalance, Coin unlockingBondsBalance) { + updateBsqValidator(availableConfirmedBalance); + updateBtcValidator(availableNonBsqBalance); + + setSendBtcGroupVisibleState(availableNonBsqBalance.isPositive()); + } + + private void updateBsqValidator(Coin availableConfirmedBalance) { bsqValidator.setAvailableBalance(availableConfirmedBalance); boolean isValid = bsqAddressValidator.validate(receiversAddressInputTextField.getText()).isValid && bsqValidator.validate(amountInputTextField.getText()).isValid; sendBsqButton.setDisable(!isValid); + } - boolean isBtcValid = btcAddressValidator.validate(receiversBtcAddressInputTextField.getText()).isValid && + private void updateBtcValidator(Coin availableConfirmedBalance) { + btcValidator.setMaxValue(availableConfirmedBalance); + boolean isValid = btcAddressValidator.validate(receiversBtcAddressInputTextField.getText()).isValid && btcValidator.validate(btcAmountInputTextField.getText()).isValid; - sendBtcButton.setDisable(!isBtcValid); - - setSendBtcGroupVisibleState(availableNonBsqBalance.isPositive()); + sendBtcButton.setDisable(!isValid); } private void addSendBsqGroup() { @@ -240,9 +275,13 @@ private void addSendBsqGroup() { onUpdateBalances(); }; - sendBsqButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.send")); + Tuple2 tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow, + Res.get("dao.wallet.send.send"), Res.get("dao.wallet.send.inputControl")); + sendBsqButton = tuple.first; + bsqInputControlButton = tuple.second; sendBsqButton.setOnAction((event) -> onSendBsq()); + bsqInputControlButton.setOnAction((event) -> onBsqInputControl()); } private void onSendBsq() { @@ -253,7 +292,8 @@ private void onSendBsq() { String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString(); Coin receiverAmount = ParsingUtils.parseToCoin(amountInputTextField.getText(), bsqFormatter); try { - Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString, receiverAmount); + Transaction preparedSendTx = bsqWalletService.getPreparedSendBsqTx(receiversAddressString, + receiverAmount, bsqUtxoCandidates); Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx); Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); Coin miningFee = signedTx.getFee(); @@ -282,16 +322,49 @@ private void onSendBsq() { } } + private void onBsqInputControl() { + List unspentTransactionOutputs = bsqWalletService.getSpendableBsqTransactionOutputs(); + if (bsqUtxoCandidates == null) { + bsqUtxoCandidates = new HashSet<>(unspentTransactionOutputs); + } else { + // If we had some selection stored we need to update to already spent entries + bsqUtxoCandidates = bsqUtxoCandidates.stream(). + filter(e -> unspentTransactionOutputs.contains(e)). + collect(Collectors.toSet()); + } + TxInputSelectionWindow txInputSelectionWindow = new TxInputSelectionWindow(unspentTransactionOutputs, + bsqUtxoCandidates, + preferences, + bsqFormatter); + txInputSelectionWindow.onAction(() -> setBsqUtxoCandidates(txInputSelectionWindow.getCandidates())) + .show(); + } + + private void setBsqUtxoCandidates(Set candidates) { + this.bsqUtxoCandidates = candidates; + updateBsqValidator(getSpendableBsqBalance()); + amountInputTextField.refreshValidation(); + } + + // We have used input selection it is the sum of our selected inputs, otherwise the availableConfirmedBalance + private Coin getSpendableBsqBalance() { + return bsqUtxoCandidates != null ? + Coin.valueOf(bsqUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) : + bsqWalletService.getAvailableConfirmedBalance(); + } + private void setSendBtcGroupVisibleState(boolean visible) { btcTitledGroupBg.setVisible(visible); receiversBtcAddressInputTextField.setVisible(visible); btcAmountInputTextField.setVisible(visible); sendBtcButton.setVisible(visible); + btcInputControlButton.setVisible(visible); btcTitledGroupBg.setManaged(visible); receiversBtcAddressInputTextField.setManaged(visible); btcAmountInputTextField.setManaged(visible); sendBtcButton.setManaged(visible); + btcInputControlButton.setManaged(visible); } private void addSendBtcGroup() { @@ -306,43 +379,80 @@ private void addSendBtcGroup() { btcAmountInputTextField.setValidator(btcValidator); GridPane.setColumnSpan(btcAmountInputTextField, 3); - sendBtcButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.sendBtc")); - - sendBtcButton.setOnAction((event) -> { - if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { - String receiversAddressString = receiversBtcAddressInputTextField.getText(); - Coin receiverAmount = bsqFormatter.parseToBTC(btcAmountInputTextField.getText()); - try { - Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount); - Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx); - Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); - Coin miningFee = signedTx.getFee(); - - if (miningFee.getValue() >= receiverAmount.getValue()) - GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter); - else { - int txVsize = signedTx.getVsize(); - showPublishTxPopup(receiverAmount, - txWithBtcFee, - TxType.INVALID, - miningFee, - txVsize, receiversBtcAddressInputTextField.getText(), - btcFormatter, - btcFormatter, - () -> { - receiversBtcAddressInputTextField.setText(""); - btcAmountInputTextField.setText(""); - }); - - } - } catch (BsqChangeBelowDustException e) { - String msg = Res.get("popup.warning.btcChangeBelowDustException", btcFormatter.formatCoinWithCode(e.getOutputValue())); - new Popup().warning(msg).show(); - } catch (Throwable t) { - handleError(t); - } + Tuple2 tuple = FormBuilder.add2ButtonsAfterGroup(root, ++gridRow, + Res.get("dao.wallet.send.sendBtc"), Res.get("dao.wallet.send.inputControl")); + sendBtcButton = tuple.first; + btcInputControlButton = tuple.second; + + sendBtcButton.setOnAction((event) -> onSendBtc()); + btcInputControlButton.setOnAction((event) -> onBtcInputControl()); + } + + private void onBtcInputControl() { + List unspentTransactionOutputs = bsqWalletService.getSpendableNonBsqTransactionOutputs(); + if (btcUtxoCandidates == null) { + btcUtxoCandidates = new HashSet<>(unspentTransactionOutputs); + } else { + // If we had some selection stored we need to update to already spent entries + btcUtxoCandidates = btcUtxoCandidates.stream(). + filter(e -> unspentTransactionOutputs.contains(e)). + collect(Collectors.toSet()); + } + TxInputSelectionWindow txInputSelectionWindow = new TxInputSelectionWindow(unspentTransactionOutputs, + btcUtxoCandidates, + preferences, + btcFormatter); + txInputSelectionWindow.onAction(() -> setBtcUtxoCandidates(txInputSelectionWindow.getCandidates())). + show(); + } + + private void setBtcUtxoCandidates(Set candidates) { + this.btcUtxoCandidates = candidates; + updateBtcValidator(getSpendableBtcBalance()); + btcAmountInputTextField.refreshValidation(); + } + + private Coin getSpendableBtcBalance() { + return btcUtxoCandidates != null ? + Coin.valueOf(btcUtxoCandidates.stream().mapToLong(e -> e.getValue().value).sum()) : + bsqWalletService.getAvailableNonBsqBalance(); + } + + private void onSendBtc() { + if (!GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { + return; + } + + String receiversAddressString = receiversBtcAddressInputTextField.getText(); + Coin receiverAmount = bsqFormatter.parseToBTC(btcAmountInputTextField.getText()); + try { + Transaction preparedSendTx = bsqWalletService.getPreparedSendBtcTx(receiversAddressString, receiverAmount, btcUtxoCandidates); + Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx); + Transaction signedTx = bsqWalletService.signTx(txWithBtcFee); + Coin miningFee = signedTx.getFee(); + + if (miningFee.getValue() >= receiverAmount.getValue()) + GUIUtil.showWantToBurnBTCPopup(miningFee, receiverAmount, btcFormatter); + else { + int txVsize = signedTx.getVsize(); + showPublishTxPopup(receiverAmount, + txWithBtcFee, + TxType.INVALID, + miningFee, + txVsize, receiversBtcAddressInputTextField.getText(), + btcFormatter, + btcFormatter, + () -> { + receiversBtcAddressInputTextField.setText(""); + btcAmountInputTextField.setText(""); + }); } - }); + } catch (BsqChangeBelowDustException e) { + String msg = Res.get("popup.warning.btcChangeBelowDustException", btcFormatter.formatCoinWithCode(e.getOutputValue())); + new Popup().warning(msg).show(); + } catch (Throwable t) { + handleError(t); + } } private void handleError(Throwable t) { diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TxInputSelectionWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TxInputSelectionWindow.java new file mode 100644 index 00000000000..abbd34c7dc9 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TxInputSelectionWindow.java @@ -0,0 +1,246 @@ +/* + * This file is part of Bisq. + * + * Bisq 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. + * + * Bisq 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 Bisq. If not, see . + */ + +package bisq.desktop.main.overlays.windows; + +import bisq.desktop.components.AutoTooltipCheckBox; +import bisq.desktop.components.AutoTooltipLabel; +import bisq.desktop.components.AutoTooltipTableColumn; +import bisq.desktop.components.BalanceTextField; +import bisq.desktop.components.ExternalHyperlink; +import bisq.desktop.components.HyperlinkWithIcon; +import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.util.FormBuilder; +import bisq.desktop.util.GUIUtil; +import bisq.desktop.util.Layout; + +import bisq.core.locale.Res; +import bisq.core.user.Preferences; +import bisq.core.util.coin.CoinFormatter; + +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.TransactionOutput; + +import javafx.scene.control.CheckBox; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; + +import javafx.geometry.Insets; + +import javafx.beans.property.ReadOnlyObjectWrapper; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; + +import javafx.util.Callback; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.Setter; + +public class TxInputSelectionWindow extends Overlay { + private static class TransactionOutputItem { + @Getter + private final TransactionOutput transactionOutput; + @Getter + @Setter + private boolean isSelected; + + public TransactionOutputItem(TransactionOutput transactionOutput, boolean isSelected) { + this.transactionOutput = transactionOutput; + this.isSelected = isSelected; + } + } + + private final List spendableTransactionOutputs; + @Getter + private final Set candidates; + private final Preferences preferences; + private final CoinFormatter formatter; + + private BalanceTextField balanceTextField; + private TableView tableView; + + public TxInputSelectionWindow(List spendableTransactionOutputs, + Set candidates, + Preferences preferences, + CoinFormatter formatter) { + this.spendableTransactionOutputs = spendableTransactionOutputs; + this.candidates = candidates; + this.preferences = preferences; + this.formatter = formatter; + type = Type.Attention; + } + + public void show() { + rowIndex = 0; + width = 900; + if (headLine == null) { + headLine = Res.get("inputControlWindow.headline"); + } + createGridPane(); + gridPane.setHgap(15); + addHeadLine(); + addContent(); + addButtons(); + addDontShowAgainCheckBox(); + applyStyles(); + display(); + } + + protected void addContent() { + tableView = new TableView<>(); + tableView.setPlaceholder(new AutoTooltipLabel(Res.get("table.placeholder.noData"))); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + GridPane.setRowIndex(tableView, rowIndex++); + GridPane.setMargin(tableView, new Insets(Layout.GROUP_DISTANCE, 0, 0, 0)); + GridPane.setColumnSpan(tableView, 2); + GridPane.setVgrow(tableView, Priority.ALWAYS); + gridPane.getChildren().add(tableView); + createColumns(); + ObservableList items = FXCollections.observableArrayList(spendableTransactionOutputs.stream() + .map(transactionOutput -> new TransactionOutputItem(transactionOutput, candidates.contains(transactionOutput))) + .collect(Collectors.toList())); + tableView.setItems(new SortedList<>(items)); + GUIUtil.setFitToRowsForTableView(tableView, 26, 28, 0, items.size()); + + balanceTextField = FormBuilder.addBalanceTextField(gridPane, rowIndex++, Res.get("inputControlWindow.balanceLabel"), Layout.FIRST_ROW_DISTANCE); + balanceTextField.setFormatter(formatter); + + updateBalance(); + } + + private void updateBalance() { + balanceTextField.setBalance(Coin.valueOf(candidates.stream() + .mapToLong(transactionOutput -> transactionOutput.getValue().value) + .sum())); + } + + private void onChangeCheckBox(TransactionOutputItem transactionOutputItem) { + if (transactionOutputItem.isSelected()) { + candidates.add(transactionOutputItem.getTransactionOutput()); + } else { + candidates.remove(transactionOutputItem.getTransactionOutput()); + } + + updateBalance(); + } + + private void createColumns() { + TableColumn column; + + column = new AutoTooltipTableColumn<>(Res.get("shared.select")); + column.getStyleClass().add("first-column"); + column.setSortable(false); + column.setMinWidth(60); + column.setMaxWidth(column.getMinWidth()); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(TransactionOutputItem item, boolean empty) { + super.updateItem(item, empty); + final CheckBox checkBox = new AutoTooltipCheckBox(); + if (item != null && !empty) { + checkBox.setSelected(item.isSelected()); + checkBox.setOnAction(e -> { + item.setSelected(checkBox.isSelected()); + onChangeCheckBox(item); + }); + setGraphic(checkBox); + } else { + if (checkBox != null) { + checkBox.setOnAction(null); + } + setGraphic(null); + } + } + }; + } + }); + tableView.getColumns().add(column); + + column = new AutoTooltipTableColumn<>(Res.get("shared.balance")); + column.setMinWidth(100); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + @Override + public void updateItem(TransactionOutputItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + setText(formatter.formatCoinWithCode(item.getTransactionOutput().getValue())); + } else { + setText(""); + } + } + }; + } + }); + tableView.getColumns().add(column); + + column = new AutoTooltipTableColumn<>(Res.get("shared.utxo")); + column.setSortable(false); + column.setMinWidth(550); + column.setCellValueFactory((item) -> new ReadOnlyObjectWrapper<>(item.getValue())); + column.setCellFactory( + new Callback<>() { + @Override + public TableCell call( + TableColumn column) { + return new TableCell<>() { + private HyperlinkWithIcon hyperlinkWithIcon; + + @Override + public void updateItem(TransactionOutputItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + TransactionOutput transactionOutput = item.getTransactionOutput(); + String txId = transactionOutput.getParentTransaction().getTxId().toString(); + hyperlinkWithIcon = new ExternalHyperlink(txId + ":" + transactionOutput.getIndex()); + hyperlinkWithIcon.setOnAction(event -> GUIUtil.openWebPage(preferences.getBsqBlockChainExplorer().txUrl + txId, false)); + hyperlinkWithIcon.setTooltip(new Tooltip(Res.get("tooltip.openBlockchainForTx", txId))); + setGraphic(hyperlinkWithIcon); + } else { + if (hyperlinkWithIcon != null) { + hyperlinkWithIcon.setOnAction(null); + } + setGraphic(null); + } + } + }; + } + }); + tableView.getColumns().add(column); + } +} From 5b2fed0b8afe600cea1dcf8b9d38d80aba5dfd3f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 15 Feb 2021 20:53:38 -0500 Subject: [PATCH 09/25] Fix typos --- .../main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java | 2 +- core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java index fa26a7ef60e..ccb2a105171 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java @@ -78,7 +78,7 @@ public CoinSelection select(Coin target, List candidates) { ArrayList sortedOutputs; if (utxoCandidates != null) { sortedOutputs = new ArrayList<>(utxoCandidates); - // We we reuse the selectors we reset the transactionOutputCandidates field + // We reuse the selectors. Reset the transactionOutputCandidates field utxoCandidates = null; } else { sortedOutputs = new ArrayList<>(candidates); diff --git a/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java index 6aaa5d75f10..8de488ae8ee 100644 --- a/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/NonBsqCoinSelector.java @@ -64,7 +64,7 @@ protected boolean isTxOutputSpendable(TransactionOutput output) { return !daoStateService.existsTxOutput(key) || daoStateService.isRejectedIssuanceOutput(key); } - // Prevent to use dust attack utxos + // Prevent usage of dust attack utxos @Override protected boolean isDustAttackUtxo(TransactionOutput output) { return output.getValue().value < preferences.getIgnoreDustThreshold(); From 097376ef07b99f3e9ca2f3fcc9c8b1dacc09f052 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 15 Feb 2021 20:54:00 -0500 Subject: [PATCH 10/25] Set onAction handlers in activate not in initialize --- .../desktop/main/dao/wallet/send/BsqSendView.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java index 7b3a3d035ca..7450bca5668 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java @@ -176,6 +176,11 @@ protected void activate() { setSendBtcGroupVisibleState(false); bsqBalanceUtil.activate(); + sendBsqButton.setOnAction((event) -> onSendBsq()); + bsqInputControlButton.setOnAction((event) -> onBsqInputControl()); + sendBtcButton.setOnAction((event) -> onSendBtc()); + btcInputControlButton.setOnAction((event) -> onBtcInputControl()); + receiversAddressInputTextField.focusedProperty().addListener(focusOutListener); amountInputTextField.focusedProperty().addListener(focusOutListener); receiversBtcAddressInputTextField.focusedProperty().addListener(focusOutListener); @@ -188,7 +193,7 @@ protected void activate() { bsqWalletService.addBsqBalanceListener(this); - // We reset the input selection at active to have all inputs selected, otherwise the user + // We reset the input selection at activate to have all inputs selected, otherwise the user // might get confused if he had deselected inputs earlier and cannot spend the full balance. bsqUtxoCandidates = null; btcUtxoCandidates = null; @@ -279,9 +284,6 @@ private void addSendBsqGroup() { Res.get("dao.wallet.send.send"), Res.get("dao.wallet.send.inputControl")); sendBsqButton = tuple.first; bsqInputControlButton = tuple.second; - - sendBsqButton.setOnAction((event) -> onSendBsq()); - bsqInputControlButton.setOnAction((event) -> onBsqInputControl()); } private void onSendBsq() { @@ -383,9 +385,6 @@ private void addSendBtcGroup() { Res.get("dao.wallet.send.sendBtc"), Res.get("dao.wallet.send.inputControl")); sendBtcButton = tuple.first; btcInputControlButton = tuple.second; - - sendBtcButton.setOnAction((event) -> onSendBtc()); - btcInputControlButton.setOnAction((event) -> onBtcInputControl()); } private void onBtcInputControl() { @@ -525,4 +524,3 @@ private void sendFunds(Transaction txWithBtcFee, TxType txType, TxBroadcaster.Ca walletsManager.publishAndCommitBsqTx(txWithBtcFee, txType, callback); } } - From 9ae76b621f676368d559e69f816ad1445f7e0b59 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 11:44:12 -0500 Subject: [PATCH 11/25] Add comment --- .../java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java index ccb2a105171..c540cc51126 100644 --- a/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java +++ b/core/src/main/java/bisq/core/btc/wallet/BisqDefaultCoinSelector.java @@ -138,7 +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? + // 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 outputs) { Collections.sort(outputs, (a, b) -> { int depth1 = a.getParentTransactionDepthInBlocks(); From 9ad77e89000bc616d0b1472815e1d369153bafaf Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:12:03 -0500 Subject: [PATCH 12/25] Fixes https://github.com/bisq-network/bisq/issues/5204 Remove SetupListener implementation from MailboxMessageService. Remove requestDataManager Add onNoSeedNodeAvailable method Call onNoSeedNodeAvailable from P2PService --- .../java/bisq/network/p2p/P2PService.java | 1 + .../p2p/mailbox/MailboxMessageService.java | 39 ++++++------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index bd50e248552..b3e259022ad 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -258,6 +258,7 @@ public void onTorNodeReady() { if (!seedNodesAvailable) { isBootstrapped = true; + mailboxMessageService.onNoSeedNodeAvailable(); p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); } } diff --git a/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java b/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java index f1855ca0ed5..1b79ea29c1b 100644 --- a/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java +++ b/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java @@ -26,11 +26,9 @@ import bisq.network.p2p.messaging.DecryptedMailboxListener; import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.NetworkNode; -import bisq.network.p2p.network.SetupListener; import bisq.network.p2p.peers.BroadcastHandler; import bisq.network.p2p.peers.Broadcaster; import bisq.network.p2p.peers.PeerManager; -import bisq.network.p2p.peers.getdata.RequestDataManager; import bisq.network.p2p.storage.HashMapChangedListener; import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.messages.AddDataMessage; @@ -112,14 +110,12 @@ */ @Singleton @Slf4j -public class MailboxMessageService implements SetupListener, HashMapChangedListener, - PersistedDataHost { +public class MailboxMessageService implements HashMapChangedListener, PersistedDataHost { private static final long REPUBLISH_DELAY_SEC = TimeUnit.MINUTES.toSeconds(2); private final NetworkNode networkNode; private final PeerManager peerManager; private final P2PDataStorage p2PDataStorage; - private final RequestDataManager requestDataManager; private final EncryptionService encryptionService; private final IgnoredMailboxService ignoredMailboxService; private final PersistenceManager persistenceManager; @@ -137,7 +133,6 @@ public class MailboxMessageService implements SetupListener, HashMapChangedListe public MailboxMessageService(NetworkNode networkNode, PeerManager peerManager, P2PDataStorage p2PDataStorage, - RequestDataManager requestDataManager, EncryptionService encryptionService, IgnoredMailboxService ignoredMailboxService, PersistenceManager persistenceManager, @@ -147,7 +142,6 @@ public MailboxMessageService(NetworkNode networkNode, this.networkNode = networkNode; this.peerManager = peerManager; this.p2PDataStorage = p2PDataStorage; - this.requestDataManager = requestDataManager; this.encryptionService = encryptionService; this.ignoredMailboxService = ignoredMailboxService; this.persistenceManager = persistenceManager; @@ -155,8 +149,6 @@ public MailboxMessageService(NetworkNode networkNode, this.clock = clock; this.republishMailboxEntries = republishMailboxEntries; - this.networkNode.addSetupListener(this); - this.persistenceManager.initialize(mailboxMessageList, PersistenceManager.Source.PRIVATE_LOW_PRIO); } @@ -236,6 +228,16 @@ public void onUpdatedDataReceived() { } } + public void onNoSeedNodeAvailable() { + if (!isBootstrapped) { + isBootstrapped = true; + // As we do not expect a updated data request response we start here with addHashMapChangedListenerAndApply + addHashMapChangedListenerAndApply(); + maybeRepublishMailBoxMessages(); + } + } + + public void sendEncryptedMailboxMessage(NodeAddress peer, PubKeyRing peersPubKeyRing, MailboxMessage mailboxMessage, @@ -343,25 +345,6 @@ public void addDecryptedMailboxListener(DecryptedMailboxListener listener) { } - /////////////////////////////////////////////////////////////////////////////////////////// - // SetupListener implementation - /////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void onTorNodeReady() { - boolean seedNodesAvailable = requestDataManager.requestPreliminaryData(); - if (!seedNodesAvailable) { - isBootstrapped = true; - // As we do not expect a updated data request response we start here with addHashMapChangedListenerAndApply - addHashMapChangedListenerAndApply(); - maybeRepublishMailBoxMessages(); - } - } - - @Override - public void onHiddenServicePublished() { - } - - /////////////////////////////////////////////////////////////////////////////////////////// // HashMapChangedListener implementation for ProtectedStorageEntry items /////////////////////////////////////////////////////////////////////////////////////////// From 78f59b65533a63ce64c5f26cb184020f6b4cea5d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:13:01 -0500 Subject: [PATCH 13/25] Call onNoSeedNodeAvailable on mailboxMessageService at onNoSeedNodeAvailable --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index b3e259022ad..43be1f63271 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -330,6 +330,7 @@ public void onUpdatedDataReceived() { @Override public void onNoSeedNodeAvailable() { + mailboxMessageService.onNoSeedNodeAvailable(); p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); } From cc203eb5d7a9accfce4c81cc1ce70dd86f074267 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:14:48 -0500 Subject: [PATCH 14/25] Merge onUpdatedDataReceived and onNoSeedNodeAvailable to onBootstrapped --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 6 +++--- .../network/p2p/mailbox/MailboxMessageService.java | 11 +---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index 43be1f63271..e95cc8dff5f 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -258,7 +258,7 @@ public void onTorNodeReady() { if (!seedNodesAvailable) { isBootstrapped = true; - mailboxMessageService.onNoSeedNodeAvailable(); + mailboxMessageService.onBootstrapped(); p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); } } @@ -321,7 +321,7 @@ public void onUpdatedDataReceived() { // We don't use a listener at mailboxMessageService as we require the correct // order of execution. The p2pServiceListeners must be called after // mailboxMessageService.onUpdatedDataReceived. - mailboxMessageService.onUpdatedDataReceived(); + mailboxMessageService.onBootstrapped(); p2pServiceListeners.forEach(P2PServiceListener::onUpdatedDataReceived); p2PDataStorage.onBootstrapComplete(); @@ -330,7 +330,7 @@ public void onUpdatedDataReceived() { @Override public void onNoSeedNodeAvailable() { - mailboxMessageService.onNoSeedNodeAvailable(); + mailboxMessageService.onBootstrapped(); p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); } diff --git a/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java b/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java index 1b79ea29c1b..e505c457388 100644 --- a/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java +++ b/p2p/src/main/java/bisq/network/p2p/mailbox/MailboxMessageService.java @@ -218,7 +218,7 @@ public void readPersisted(Runnable completeHandler) { // We don't listen on requestDataManager directly as we require the correct // order of execution. The p2pService is handling the correct order of execution and we get called // directly from there. - public void onUpdatedDataReceived() { + public void onBootstrapped() { if (!isBootstrapped) { isBootstrapped = true; // Only now we start listening and processing. The p2PDataStorage is our cache for data we have received @@ -228,15 +228,6 @@ public void onUpdatedDataReceived() { } } - public void onNoSeedNodeAvailable() { - if (!isBootstrapped) { - isBootstrapped = true; - // As we do not expect a updated data request response we start here with addHashMapChangedListenerAndApply - addHashMapChangedListenerAndApply(); - maybeRepublishMailBoxMessages(); - } - } - public void sendEncryptedMailboxMessage(NodeAddress peer, PubKeyRing peersPubKeyRing, From 01e32ba3ebf37bbdfc94f06346dbe8894e96d11d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:16:43 -0500 Subject: [PATCH 15/25] Call p2PDataStorage.onBootstrapComplete() before mailboxMessageService.onBootstrapped(); and onUpdatedDataReceived mailboxMessageService depends on p2PDataStorage so we make sure the p2PDataStorage is updated before we update the mailboxMessageService state. --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index e95cc8dff5f..f100240a023 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -318,13 +318,15 @@ public void onPreliminaryDataReceived() { public void onUpdatedDataReceived() { if (!isBootstrapped) { isBootstrapped = true; + + p2PDataStorage.onBootstrapComplete(); + // We don't use a listener at mailboxMessageService as we require the correct // order of execution. The p2pServiceListeners must be called after // mailboxMessageService.onUpdatedDataReceived. mailboxMessageService.onBootstrapped(); p2pServiceListeners.forEach(P2PServiceListener::onUpdatedDataReceived); - p2PDataStorage.onBootstrapComplete(); } } From 69db9a7a84480605b24595b85d32b6b92b674659 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:17:03 -0500 Subject: [PATCH 16/25] Refactor: Rename onBootstrapComplete to onBootstrapped --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 2 +- p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index f100240a023..a035a681311 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -319,7 +319,7 @@ public void onUpdatedDataReceived() { if (!isBootstrapped) { isBootstrapped = true; - p2PDataStorage.onBootstrapComplete(); + p2PDataStorage.onBootstrapped(); // We don't use a listener at mailboxMessageService as we require the correct // order of execution. The p2pServiceListeners must be called after diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index 4607ccc9429..6114f74c16b 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -561,7 +561,7 @@ void removeExpiredEntries() { } } - public void onBootstrapComplete() { + public void onBootstrapped() { removeExpiredEntriesTimer = UserThread.runPeriodically(this::removeExpiredEntries, CHECK_TTL_INTERVAL_SEC); } From 5490c0a92d739f9ebe8c86dcc9aedd33448669d9 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:21:17 -0500 Subject: [PATCH 17/25] Use same behaviour in onNoSeedNodeAvailable as in onUpdatedDataReceived --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index a035a681311..e1ddaee16d3 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -332,8 +332,18 @@ public void onUpdatedDataReceived() { @Override public void onNoSeedNodeAvailable() { - mailboxMessageService.onBootstrapped(); - p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); + if (!isBootstrapped) { + isBootstrapped = true; + + p2PDataStorage.onBootstrapped(); + + // We don't use a listener at mailboxMessageService as we require the correct + // order of execution. The p2pServiceListeners must be called after + // mailboxMessageService.onUpdatedDataReceived. + mailboxMessageService.onBootstrapped(); + + p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); + } } @Override From 017ed50c16d982b87b15a579f9e12510d0ca909d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:27:17 -0500 Subject: [PATCH 18/25] Refactor: Extract method from duplicated code --- .../java/bisq/network/p2p/P2PService.java | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index e1ddaee16d3..9f07f4155e0 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -70,6 +70,7 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -316,22 +317,25 @@ public void onPreliminaryDataReceived() { @Override public void onUpdatedDataReceived() { - if (!isBootstrapped) { - isBootstrapped = true; - - p2PDataStorage.onBootstrapped(); + applyIsBootstrapped(P2PServiceListener::onUpdatedDataReceived); + } - // We don't use a listener at mailboxMessageService as we require the correct - // order of execution. The p2pServiceListeners must be called after - // mailboxMessageService.onUpdatedDataReceived. - mailboxMessageService.onBootstrapped(); + @Override + public void onNoSeedNodeAvailable() { + applyIsBootstrapped(P2PServiceListener::onNoSeedNodeAvailable); + } - p2pServiceListeners.forEach(P2PServiceListener::onUpdatedDataReceived); - } + @Override + public void onNoPeersAvailable() { + p2pServiceListeners.forEach(P2PServiceListener::onNoPeersAvailable); } @Override - public void onNoSeedNodeAvailable() { + public void onDataReceived() { + p2pServiceListeners.forEach(P2PServiceListener::onDataReceived); + } + + private void applyIsBootstrapped(Consumer listenerHandler) { if (!isBootstrapped) { isBootstrapped = true; @@ -342,20 +346,11 @@ public void onNoSeedNodeAvailable() { // mailboxMessageService.onUpdatedDataReceived. mailboxMessageService.onBootstrapped(); - p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); + // Once we have applied the state in the P2P domain we notify our listeners + p2pServiceListeners.forEach(listenerHandler); } } - @Override - public void onNoPeersAvailable() { - p2pServiceListeners.forEach(P2PServiceListener::onNoPeersAvailable); - } - - @Override - public void onDataReceived() { - p2pServiceListeners.forEach(P2PServiceListener::onDataReceived); - } - /////////////////////////////////////////////////////////////////////////////////////////// // ConnectionListener implementation From 4877c1dc6cd78cba381ba3efe3eafa7e510e828a Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:29:27 -0500 Subject: [PATCH 19/25] Call onNoSeedNodeAvailable if return value from requestPreliminaryData is false We did not do all the calls before (like on p2pDataStorage), so that changes behaviour. --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index 9f07f4155e0..b22672217c4 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -258,9 +258,7 @@ public void onTorNodeReady() { p2pServiceListeners.forEach(SetupListener::onTorNodeReady); if (!seedNodesAvailable) { - isBootstrapped = true; - mailboxMessageService.onBootstrapped(); - p2pServiceListeners.forEach(P2PServiceListener::onNoSeedNodeAvailable); + onNoSeedNodeAvailable(); } } From 1825f3308fcbd099a9d5a1ffa5cbfc0df6543c6b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 16:31:17 -0500 Subject: [PATCH 20/25] Remove boolean return value for requestPreliminaryData and call onNoSeedNodeAvailable instead. --- p2p/src/main/java/bisq/network/p2p/P2PService.java | 7 +------ .../bisq/network/p2p/peers/getdata/RequestDataManager.java | 5 ++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index b22672217c4..0bac8ac70bf 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -252,14 +252,9 @@ private void doShutDown() { public void onTorNodeReady() { socks5ProxyProvider.setSocks5ProxyInternal(networkNode); - boolean seedNodesAvailable = requestDataManager.requestPreliminaryData(); - + requestDataManager.requestPreliminaryData(); keepAliveManager.start(); p2pServiceListeners.forEach(SetupListener::onTorNodeReady); - - if (!seedNodesAvailable) { - onNoSeedNodeAvailable(); - } } @Override diff --git a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataManager.java b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataManager.java index 7bf2d8fd361..b390706bc18 100644 --- a/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataManager.java +++ b/p2p/src/main/java/bisq/network/p2p/peers/getdata/RequestDataManager.java @@ -155,7 +155,7 @@ public void setListener(Listener listener) { this.listener = listener; } - public boolean requestPreliminaryData() { + public void requestPreliminaryData() { ArrayList nodeAddresses = new ArrayList<>(seedNodeAddresses); if (!nodeAddresses.isEmpty()) { ArrayList finalNodeAddresses = new ArrayList<>(nodeAddresses); @@ -169,9 +169,8 @@ public boolean requestPreliminaryData() { } isPreliminaryDataRequest = true; - return true; } else { - return false; + checkNotNull(listener).onNoSeedNodeAvailable(); } } From bc6a53d356e68700e48e9b99cbd492c77e600338 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 18 Feb 2021 18:26:43 -0500 Subject: [PATCH 21/25] Improve resetValidation method to reset validation result If text field is empty we apply ValidationResult(true), otherwise we apply the validate result with the text and the given validator. --- .../bisq/desktop/components/InputTextField.java | 9 ++++++++- .../main/dao/wallet/send/BsqSendView.java | 16 ++++++++++++---- .../util/validation/JFXInputValidator.java | 1 + 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/components/InputTextField.java b/desktop/src/main/java/bisq/desktop/components/InputTextField.java index 064f7d0f8ba..536813a1a01 100644 --- a/desktop/src/main/java/bisq/desktop/components/InputTextField.java +++ b/desktop/src/main/java/bisq/desktop/components/InputTextField.java @@ -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 @@ -118,6 +118,13 @@ 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() { diff --git a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java index 7450bca5668..11293867136 100644 --- a/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java +++ b/desktop/src/main/java/bisq/desktop/main/dao/wallet/send/BsqSendView.java @@ -176,6 +176,11 @@ protected void activate() { setSendBtcGroupVisibleState(false); bsqBalanceUtil.activate(); + receiversAddressInputTextField.resetValidation(); + amountInputTextField.resetValidation(); + receiversBtcAddressInputTextField.resetValidation(); + btcAmountInputTextField.resetValidation(); + sendBsqButton.setOnAction((event) -> onSendBsq()); bsqInputControlButton.setOnAction((event) -> onBsqInputControl()); sendBtcButton.setOnAction((event) -> onSendBtc()); @@ -309,12 +314,11 @@ private void onSendBsq() { bsqFormatter, btcFormatter, () -> { - receiversAddressInputTextField.setValidator(null); receiversAddressInputTextField.setText(""); - receiversAddressInputTextField.setValidator(bsqAddressValidator); - amountInputTextField.setValidator(null); amountInputTextField.setText(""); - amountInputTextField.setValidator(bsqValidator); + + receiversAddressInputTextField.resetValidation(); + amountInputTextField.resetValidation(); }); } catch (BsqChangeBelowDustException e) { String msg = Res.get("popup.warning.bsqChangeBelowDustException", bsqFormatter.formatCoinWithCode(e.getOutputValue())); @@ -444,6 +448,10 @@ private void onSendBtc() { () -> { receiversBtcAddressInputTextField.setText(""); btcAmountInputTextField.setText(""); + + receiversBtcAddressInputTextField.resetValidation(); + btcAmountInputTextField.resetValidation(); + }); } } catch (BsqChangeBelowDustException e) { diff --git a/desktop/src/main/java/bisq/desktop/util/validation/JFXInputValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/JFXInputValidator.java index bf10fef8548..135cf58713b 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/JFXInputValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/JFXInputValidator.java @@ -16,6 +16,7 @@ protected void eval() { } public void resetValidation() { + message.set(null); hasErrors.set(false); } From 0e5c7e5de798c72c974aecedc648172eb05e69ae Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Mon, 22 Feb 2021 17:56:30 -0600 Subject: [PATCH 22/25] Fix initialization ordering issue TradeManager must be inited before MailboxMessageService --- .../main/java/bisq/core/app/DomainInitialisation.java | 2 +- p2p/src/main/java/bisq/network/p2p/P2PService.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/app/DomainInitialisation.java b/core/src/main/java/bisq/core/app/DomainInitialisation.java index 8bdc28c989d..a5aa26c236c 100644 --- a/core/src/main/java/bisq/core/app/DomainInitialisation.java +++ b/core/src/main/java/bisq/core/app/DomainInitialisation.java @@ -196,12 +196,12 @@ public void initDomainServices(Consumer rejectedTxErrorMessageHandler, tradeLimits.onAllServicesInitialized(); + tradeManager.onAllServicesInitialized(); arbitrationManager.onAllServicesInitialized(); mediationManager.onAllServicesInitialized(); refundManager.onAllServicesInitialized(); traderChatManager.onAllServicesInitialized(); - tradeManager.onAllServicesInitialized(); closedTradableManager.onAllServicesInitialized(); failedTradesManager.onAllServicesInitialized(); xmrTxProofService.onAllServicesInitialized(); diff --git a/p2p/src/main/java/bisq/network/p2p/P2PService.java b/p2p/src/main/java/bisq/network/p2p/P2PService.java index 0bac8ac70bf..6f363ddc31f 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PService.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PService.java @@ -334,13 +334,12 @@ private void applyIsBootstrapped(Consumer listenerHandler) { p2PDataStorage.onBootstrapped(); - // We don't use a listener at mailboxMessageService as we require the correct - // order of execution. The p2pServiceListeners must be called after - // mailboxMessageService.onUpdatedDataReceived. - mailboxMessageService.onBootstrapped(); - // Once we have applied the state in the P2P domain we notify our listeners p2pServiceListeners.forEach(listenerHandler); + + // We don't use a listener at mailboxMessageService as we require the correct + // order of execution. The p2pServiceListeners must be called before. + mailboxMessageService.onBootstrapped(); } } From b59f022ec5667ebcfcac8981250b457757fc3cb3 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Tue, 23 Feb 2021 10:19:15 -0600 Subject: [PATCH 23/25] Increase min withdrawal tx fee to 15 sats/vB --- .../src/main/java/bisq/common/config/BaseCurrencyNetwork.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/bisq/common/config/BaseCurrencyNetwork.java b/common/src/main/java/bisq/common/config/BaseCurrencyNetwork.java index 419cbc09901..7836855a34d 100644 --- a/common/src/main/java/bisq/common/config/BaseCurrencyNetwork.java +++ b/common/src/main/java/bisq/common/config/BaseCurrencyNetwork.java @@ -73,6 +73,6 @@ public boolean isRegtest() { } public long getDefaultMinFeePerVbyte() { - return 2; + return 15; // 2021-02-22 due to mempool congestion, increased from 2 } } From 6edb31823667ccac98e088714659de7306db375f Mon Sep 17 00:00:00 2001 From: BtcContributor <79100296+BtcContributor@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:41:30 +0100 Subject: [PATCH 24/25] Fix length check text on manual payout tool --- core/src/main/resources/i18n/displayStrings.properties | 1 + .../java/bisq/desktop/util/validation/LengthValidator.java | 4 ++++ .../bisq/desktop/util/validation/LengthValidatorTest.java | 1 - 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 44097300bcc..d9b921ab976 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -3624,6 +3624,7 @@ validation.inputTooSmall=Input has to be larger than {0} validation.inputToBeAtLeast=Input has to be at least {0} validation.amountBelowDust=An amount below the dust limit of {0} satoshi is not allowed. validation.length=Length must be between {0} and {1} +validation.fixedLength=Length must be {0} validation.pattern=Input must be of format: {0} validation.noHexString=The input is not in HEX format. validation.advancedCash.invalidFormat=Must be a valid email or wallet id of format: X000000000000 diff --git a/desktop/src/main/java/bisq/desktop/util/validation/LengthValidator.java b/desktop/src/main/java/bisq/desktop/util/validation/LengthValidator.java index 5d24c74fc22..7e1dc3e1414 100644 --- a/desktop/src/main/java/bisq/desktop/util/validation/LengthValidator.java +++ b/desktop/src/main/java/bisq/desktop/util/validation/LengthValidator.java @@ -21,6 +21,10 @@ public ValidationResult validate(String input) { ValidationResult result = new ValidationResult(true); int length = (input == null) ? 0 : input.length(); + if (this.minLength == this.maxLength) { + if (length != this.minLength) + result = new ValidationResult(false, Res.get("validation.fixedLength", this.minLength)); + } else if (length < this.minLength || length > this.maxLength) result = new ValidationResult(false, Res.get("validation.length", this.minLength, this.maxLength)); diff --git a/desktop/src/test/java/bisq/desktop/util/validation/LengthValidatorTest.java b/desktop/src/test/java/bisq/desktop/util/validation/LengthValidatorTest.java index 3af3973886a..3d3bfd5bfea 100644 --- a/desktop/src/test/java/bisq/desktop/util/validation/LengthValidatorTest.java +++ b/desktop/src/test/java/bisq/desktop/util/validation/LengthValidatorTest.java @@ -69,5 +69,4 @@ public void validate() throws Exception { assertFalse(validator2.validate(null).isValid); // too short assertFalse(validator2.validate("123456789").isValid); // too long } - } From d3b62b1a588a81c62c0728c00d2ce8512a7ae22b Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Tue, 23 Feb 2021 21:12:59 -0600 Subject: [PATCH 25/25] TraderChatManager to handle onAllServicesInitialized Upon receipt of onAllServicesInitialized, TraderChatManager should call tryApplyMessages in order to process any pending mailbox messages stored up while offline. --- .../java/bisq/core/support/traderchat/TraderChatManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java b/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java index 4c0406f429b..a2dc887ad9e 100644 --- a/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java +++ b/core/src/main/java/bisq/core/support/traderchat/TraderChatManager.java @@ -139,6 +139,11 @@ protected AckMessageSourceType getAckMessageSourceType() { // API /////////////////////////////////////////////////////////////////////////////////////////// + public void onAllServicesInitialized() { + super.onAllServicesInitialized(); + tryApplyMessages(); + } + public void onSupportMessage(SupportMessage message) { if (canProcessMessage(message)) { log.info("Received {} with tradeId {} and uid {}",