mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 06:55:08 +01:00
Merge branch 'Development' into UI-improvements
This commit is contained in:
commit
14b0292460
106 changed files with 1788 additions and 931 deletions
11
README.md
11
README.md
|
@ -33,12 +33,13 @@ Staying in Touch
|
|||
|
||||
Contact the team and keep up to date using any of the following:
|
||||
|
||||
- The [Bisq Website](https://bisq.network)
|
||||
- The [Bisq website](https://bisq.network)
|
||||
- GitHub [Issues](https://github.com/bisq-network/exchange/issues)
|
||||
- The [Bisq Forum]( https://forum.bisq.network)
|
||||
- The [#bitsquare](https://webchat.freenode.net/?channels=bitsquare) IRC channel on Freenode ([logs](https://botbot.me/freenode/bitsquare))
|
||||
- Our [mailing list](https://groups.google.com/forum/#!forum/bitsquare)
|
||||
- [@Bitsquare_](https://twitter.com/bitsquare_) on Twitter
|
||||
- The [Bisq forum]( https://bisq.community)
|
||||
- The [#bisq](https://webchat.freenode.net/?channels=bisq) IRC channel on Freenode
|
||||
- Our [contributor mailing list](https://lists.bisq.network/listinfo/bisq-contrib)
|
||||
- [@bisq_network](https://twitter.com/bisq_network) on Twitter
|
||||
- The [Bisq newsletter](https://eepurl.com/5uQf9)
|
||||
|
||||
|
||||
License
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.2</version>
|
||||
<version>0.6.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ public class Version {
|
|||
// VERSION = 0.5.0 introduces proto buffer for the P2P network and local DB and is a not backward compatible update
|
||||
// Therefore all sub versions start again with 1
|
||||
// We use semantic versioning with major, minor and patch
|
||||
public static final String VERSION = "0.6.2";
|
||||
public static final String VERSION = "0.6.4";
|
||||
|
||||
public static int getMajorVersion(String version) {
|
||||
return getSubVersion(version, 0);
|
||||
|
|
|
@ -99,12 +99,14 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("GBYTE", "Byte"));
|
||||
result.add(new CryptoCurrency("CAGE", "Cagecoin"));
|
||||
result.add(new CryptoCurrency("XCP", "Counterparty"));
|
||||
result.add(new CryptoCurrency("CREA", "Creativecoin"));
|
||||
result.add(new CryptoCurrency("XCN", "Cryptonite"));
|
||||
result.add(new CryptoCurrency("DNET", "DarkNet"));
|
||||
if (!baseCurrencyCode.equals("DASH"))
|
||||
result.add(new CryptoCurrency("DASH", "Dash"));
|
||||
result.add(new CryptoCurrency("DCT", "DECENT"));
|
||||
result.add(new CryptoCurrency("DCR", "Decred"));
|
||||
result.add(new CryptoCurrency("ONION", "DeepOnion"));
|
||||
if (!baseCurrencyCode.equals("DOGE"))
|
||||
result.add(new CryptoCurrency("DOGE", "Dogecoin"));
|
||||
result.add(new CryptoCurrency("DMC", "DynamicCoin"));
|
||||
|
@ -112,6 +114,7 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("ESP", "Espers"));
|
||||
result.add(new CryptoCurrency("ETH", "Ether"));
|
||||
result.add(new CryptoCurrency("ETC", "Ether Classic"));
|
||||
result.add(new CryptoCurrency("XIN", "Infinity Economics"));
|
||||
result.add(new CryptoCurrency("IOP", "Internet Of People"));
|
||||
result.add(new CryptoCurrency("INXT", "Internext", true));
|
||||
result.add(new CryptoCurrency("GRC", "Gridcoin"));
|
||||
|
@ -146,6 +149,7 @@ public class CurrencyUtil {
|
|||
result.add(new CryptoCurrency("UNO", "Unobtanium"));
|
||||
result.add(new CryptoCurrency("CRED", "Verify", true));
|
||||
result.add(new CryptoCurrency("WAC", "WACoins"));
|
||||
result.add(new CryptoCurrency("WILD", "WILD Token", true));
|
||||
result.add(new CryptoCurrency("XZC", "Zcoin"));
|
||||
result.add(new CryptoCurrency("ZEC", "Zcash"));
|
||||
result.add(new CryptoCurrency("ZEN", "ZenCash"));
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.bisq.common.storage;
|
|||
|
||||
import com.google.common.io.Files;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -77,6 +78,23 @@ public class FileUtil {
|
|||
deleteFileIfExists(file);
|
||||
}
|
||||
|
||||
public static void deleteDirectory(File file, File exclude) throws IOException {
|
||||
boolean excludeFileFound = false;
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null)
|
||||
for (File f : files) {
|
||||
if (!excludeFileFound)
|
||||
excludeFileFound = f.equals(exclude);
|
||||
if (!f.equals(exclude))
|
||||
deleteDirectory(f);
|
||||
}
|
||||
}
|
||||
// Finally delete main file/dir if exclude file was not found in directory
|
||||
if (!excludeFileFound && !file.equals(exclude))
|
||||
deleteFileIfExists(file);
|
||||
}
|
||||
|
||||
public static void deleteFileIfExists(File file) throws IOException {
|
||||
if (file.exists() && !file.delete())
|
||||
throw new FileNotFoundException("Failed to delete file: " + file);
|
||||
|
@ -110,4 +128,8 @@ public class FileUtil {
|
|||
throw new IOException("Failed to rename " + oldFile + " to " + newFile);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyDirectory(File source, File destination) throws IOException {
|
||||
FileUtils.copyDirectory(source, destination);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,6 +223,7 @@ mainView.balance.reserved=Reserved in offers
|
|||
mainView.balance.locked=Locked in trades
|
||||
|
||||
mainView.footer.usingTor=(using Tor)
|
||||
mainView.footer.localhostBitcoinNode=(localhost)
|
||||
mainView.footer.btcInfo=Bitcoin network peers: {0} / {1} {2}
|
||||
mainView.footer.btcInfo.initializing=Initializing
|
||||
mainView.footer.btcInfo.synchronizedWith=synchronized with
|
||||
|
@ -246,6 +247,7 @@ mainView.walletServiceErrorMsg.timeout=Connecting to the bitcoin network failed
|
|||
mainView.walletServiceErrorMsg.connectionError=Connection to the bitcoin network failed because of an error: {0}
|
||||
|
||||
mainView.networkWarning.allConnectionsLost=You lost the connection to all {0} network peers.\nMaybe you lost your internet connection or your computer was in standby mode.
|
||||
mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Bitcoin node.\nPlease restart the Bisq application to connect to other Bitcoin nodes or restart the localhost Bitcoin node.
|
||||
mainView.version.update=(Update available)
|
||||
|
||||
|
||||
|
@ -914,8 +916,10 @@ dispute case. The XMR sender is responsible to be able to verify the XMR transfe
|
|||
arbitrator in case of a dispute.\n\n\
|
||||
There is no payment ID required, just the normal public address.\n\n\
|
||||
If you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=When using {0} you can only use the transparent addresses (starting with t) not \
|
||||
account.altcoin.popup.ZEC.msg=When using {0} you can only use the transparent addresses (starting with t) not \
|
||||
the z-addresses (private), because the arbitrator would not be able to verify the transaction with z-addresses.
|
||||
account.altcoin.popup.XZC.msg=When using {0} you can only use the transparent (traceable) addresses not \
|
||||
the untraceable addresses, because the arbitrator would not be able to verify the transaction with untraceable addresses at a block explorer.
|
||||
account.altcoin.popup.bch=Bitcoin Cash and Bitcoin Clashic suffer from replay protection. If you use those coins be sure you take sufficient precautions and understand all implications.\
|
||||
You can suffer losses by sending one coin and unintentionally send the same coins on the other block chain.\
|
||||
Because those "airdrop coins" share the same history with the Bitcoin blockchain there are also security risks and a considerable risk for losing privacy.\n\n\
|
||||
|
@ -1311,6 +1315,9 @@ popup.warning.noTradingAccountSetup.msg=You need to setup a national currency or
|
|||
popup.warning.noArbitratorSelected.headline=You don't have an arbitrator selected.
|
||||
popup.warning.noArbitratorSelected.msg=You need to setup at least one arbitrator to be able to trade.\nDo you want to do this now?
|
||||
popup.warning.notFullyConnected=You need to wait until you are fully connected to the network.\nThat might take up to about 2 minutes at startup.
|
||||
popup.warning.notSufficientConnectionsToBtcNetwork=You need to wait until you have at least {0} connections to the Bitcoin network.
|
||||
popup.warning.downloadNotComplete=You need to wait until the download of missing Bitcoin blocks is complete.
|
||||
|
||||
popup.warning.removeOffer=Are you sure you want to remove that offer?\nThe maker fee of {0} will be lost if you remove that offer.
|
||||
popup.warning.tooLargePercentageValue=You cannot set a percentage of 100% or larger.
|
||||
popup.warning.examplePercentageValue=Please enter a percentage number like \"5.4\" for 5.4%
|
||||
|
|
|
@ -823,7 +823,7 @@ account.altcoin.yourAltcoinAccounts=Ihre Altcoin-Konten:
|
|||
account.altcoin.popup.wallet.msg=Bitte stellen Sie sicher, dass Sie die Anforderungen für die Nutzung von {0}-Wallets befolgen, die auf der {1}-Website beschrieben werden.\nDie Verwendung von Wallets zentraler Börsen, bei denen Sie Ihre Schlüssel nicht selber verwalten, oder das Nutzen inkompatibler Wallet-Software können zum Verlust der gehandelten Gelder führen!\nDer Vermittler ist kein {2}-Spezialist und kann Ihnen in einem solchen Fall nicht helfen.
|
||||
account.altcoin.popup.wallet.confirm=Ich verstehe und bestätige, dass ich weiß, welche Wallet ich benutzen muss.
|
||||
account.altcoin.popup.xmr.msg=Wenn Sie XMR auf Bisq handeln wollen, stellen Sie bitte sicher, dass Sie die folgenden Bedingungen verstehen und erfüllen:\n\nUm XMR zu senden, brauchen Sie entweder die offizielle Monero-GUI-Wallet oder die vereinfachte Monero-Wallet mit aktiver stor-tx-info-Option (Standard in neuen Versionen).\nStellen Sie bitte sicher, dass Sie auf den Tx-Schlüssel zugreifen können (nutzen Sie das get_tx_key-Kommando in simplewallet), da dies im Fall eines Konflikts für den Vermittler nötig ist, um mit dem XMR-checktx-Werkzeug (http://xmr.llcoins.net/checktx.html) den XMR-Transfer zu überprüfen.\nIn normalen Block-Explorern ist der Transfer nicht überprüfbar.\n\nIm Falle eines Konflikts müssen Sie dem Vermittler folgende Daten übergeben:\n- Den privaten Tx-Schlüssel\n- Den Transaktions-Hash\n- Die öffentliche Adresse des Empfängers\n\nSollten Sie die Daten nicht übergeben können oder eine inkompatible Wallet verwendet haben, werden Sie den Konflikt verlieren. Der XMR-Sender ist in der Verantwortung den Transfer der XMR gegenüber dem Vermittler im Falle eines Konflikts zu beweisen.\n\nEs wird keine Zahlungs-ID benötigt, nur eine normale öffentliche Adresse.\n\nFalls Sie sich über diesen Prozess im unklaren sind, besuchen Sie das Monero-Forum (https://forum.getmonero.org) um weiter Informationen zu erhalten.
|
||||
account.altcoin.popup.transparentTx.msg=Wenn Sie {0} verwenden, können Sie nur die transparenten Adressen verwenden (diese beginnen mit t), nicht die z-Adressen (privat), da der Vermittler die Transaktionen mit z-Adressen nicht überprüfen könnte.
|
||||
account.altcoin.popup.ZEC.msg=Wenn Sie {0} verwenden, können Sie nur die transparenten Adressen verwenden (diese beginnen mit t), nicht die z-Adressen (privat), da der Vermittler die Transaktionen mit z-Adressen nicht überprüfen könnte.
|
||||
|
||||
account.fiat.yourFiatAccounts=Ihre nationalen\nWährungskonten:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Οι altcoin λογαριασμοί σου:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Κατανοώ και επιβεβαιώνω πως γνωρίζω ποιο πορτοφόλι πρέπει να χρησιμοποιήσω.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=Όταν χρησιμοποιείς {0} μπορείς να χρησιμοποιήσεις μονάχα τη διαφανή διεύθυνση (ξεκινά με t), και όχι την z διεύθυνση (ιδιωτική), καθώς ο διαμεσολαβητής δεν θα μπορεί να επαληθεύσει τη συναλλαγή μέσω της z διεύθυνσης.
|
||||
account.altcoin.popup.ZEC.msg=Όταν χρησιμοποιείς {0} μπορείς να χρησιμοποιήσεις μονάχα τη διαφανή διεύθυνση (ξεκινά με t), και όχι την z διεύθυνση (ιδιωτική), καθώς ο διαμεσολαβητής δεν θα μπορεί να επαληθεύσει τη συναλλαγή μέσω της z διεύθυνσης.
|
||||
|
||||
account.fiat.yourFiatAccounts=Οι λογαριασμοί σου\nεθνικών νομισμάτων:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Sus cuentas de altcoin:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Entiendo y confirmo que sé qué monedero tengo que utilizar.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=Cuando use {0} sólo puede usar direcciones transparentes (que empiecen por t) y no las direcciones-z (privadas), porque el árbitro no sería capaz de verificar la transacción con direcciones-z.
|
||||
account.altcoin.popup.ZEC.msg=Cuando use {0} sólo puede usar direcciones transparentes (que empiecen por t) y no las direcciones-z (privadas), porque el árbitro no sería capaz de verificar la transacción con direcciones-z.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency\naccounts:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Te alt-érme fiókjaid:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Megértem és alátámasztom, hogy tudom, melyik pénztárcát kell használnom.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=A {0} használatakor csak átlátható címeket használhat (t-vel kezdődve) nem pedig a z-címeket (privát), mivel hogy a bíró nem tudja ellenőrizni a z-címekkel kezdödő tranzakciókat.
|
||||
account.altcoin.popup.ZEC.msg=A {0} használatakor csak átlátható címeket használhat (t-vel kezdődve) nem pedig a z-címeket (privát), mivel hogy a bíró nem tudja ellenőrizni a z-címekkel kezdödő tranzakciókat.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency\naccounts:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Suas contas de altcoins:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Eu entendo e confirmo que eu sei qual carteira que preciso usar.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=Quando usar {0} você pode apenas utilizar endereços transparentes (começando com t) não os endereços z (privados), pois o árbitro não conseguiria verificar a transação com endereços z.
|
||||
account.altcoin.popup.ZEC.msg=Quando usar {0} você pode apenas utilizar endereços transparentes (começando com t) não os endereços z (privados), pois o árbitro não conseguiria verificar a transação com endereços z.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency\naccounts:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Conturile tale de altcoin:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Înțeleg și confirm că știu ce portofel trebuie să folosesc.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=Atunci când folosești {0}, poți utiliza doar adresele transparente (începând cu t) nu adresele z (private), deoarece arbitrul nu va putea verifica tranzacția cu adrese z.
|
||||
account.altcoin.popup.ZEC.msg=Atunci când folosești {0}, poți utiliza doar adresele transparente (începând cu t) nu adresele z (private), deoarece arbitrul nu va putea verifica tranzacția cu adrese z.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency\naccounts:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Ваши альткоин-счета:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Я понимаю и подтверждаю какой кошелёк должен использовать.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=При использовании {0} вы можете использовать только прозрачные адреса (начиная с t) а не z-адреса (частные), потому что арбитр не сможет проверить транзакцию с z-адресами.
|
||||
account.altcoin.popup.ZEC.msg=При использовании {0} вы можете использовать только прозрачные адреса (начиная с t) а не z-адреса (частные), потому что арбитр не сможет проверить транзакцию с z-адресами.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency\naccounts:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=Vaši altkoin nalozi:
|
|||
account.altcoin.popup.wallet.msg=Please be sure that you follow the requirements for the usage of {0} wallets as described on the {1} web page.\nUsing wallets from centralized exchanges where you don''t have your keys under your control or using a not compatible wallet software can lead to loss of the traded funds!\nThe arbitrator is not a {2} specialist and cannot help in such cases.
|
||||
account.altcoin.popup.wallet.confirm=Razumem i potvrđujem da znam koji novčanik trebam da koristim.
|
||||
account.altcoin.popup.xmr.msg=If you want to trade XMR on Bisq please be sure you understand and fulfill the following requirements:\n\nFor sending XMR you need to use either the official Monero GUI wallet or the Monero simple wallet with the store-tx-info flag enabled (default in new versions).\nPlease be sure that you can access the tx key (use the get_tx_key command in simplewallet) as that would be required in case of a dispute to enable the arbitrator to verify the XMR transfer with the XMR checktx tool (http://xmr.llcoins.net/checktx.html).\nAt normal block explorers the transfer is not verifiable.\n\nYou need to provide the arbitrator the following data in case of a dispute:\n- The tx private key\n- The transaction hash\n- The recipient's public address\n\nIf you cannot provide the above data or if you used an incompatible wallet it would result in losing the dispute case. The XMR sender is responsible to be able to verify the XMR transfer to the arbitrator in case of a dispute.\n\nThere is no payment ID required, just the normal public address.\n\nIf you are not sure about that process visit the Monero forum (https://forum.getmonero.org) to find more information.
|
||||
account.altcoin.popup.transparentTx.msg=Tokom korišćenja {0} možete koristiti samo transparentne adrese (počinju sa t) a ne z-adrese (privatne), jer arbitar ne bi bio u stanju da proveri transakciju sa z-adresom.
|
||||
account.altcoin.popup.ZEC.msg=Tokom korišćenja {0} možete koristiti samo transparentne adrese (počinju sa t) a ne z-adrese (privatne), jer arbitar ne bi bio u stanju da proveri transakciju sa z-adresom.
|
||||
|
||||
account.fiat.yourFiatAccounts=Your national currency\naccounts:
|
||||
|
||||
|
|
|
@ -803,7 +803,7 @@ account.altcoin.yourAltcoinAccounts=您的数字货币账户:
|
|||
account.altcoin.popup.wallet.msg=请确保您按照{1}网页上所述使用{0}钱包的要求\n使用集中式交易所的钱包,您无法控制钥匙或使用不兼容的钱包软件可能会导致交易资金的流失!\n仲裁员不是{2}专家,在这种情况下不能帮助。
|
||||
account.altcoin.popup.wallet.confirm=我了解并确定我知道我需要哪种钱包。
|
||||
account.altcoin.popup.xmr.msg=如果您想在Bisq上交易XMR,请确保您了解并符合以下要求:\n\n对于发送XMR,您需要使用官方的Monero GUI钱包或启用了store-tx-info标志的Monero简单钱包(默认为新版本)。\n请确保您可以访问tx key(使用simplewallet中的get_tx_key命令),因为如果发生纠纷,仲裁员可以使用XMR checktx工具验证XMR传输(http://xmr.llcoins.net/checktx.html).\n在正常的块浏览器中,划转是不可验证的\n\n您需要向仲裁员提供以下数据,以备发生纠纷:\n- tx私钥\n- 交易哈希\n- 收件人的公开地址\n\n如果您无法提供上述数据,或者使用不兼容的钱包,将导致失去纠纷案件。如果发生纠纷,XMR发件人有责任验证XMR向仲裁员的转移\n\n没有需要付款ID,只是正常的公共地址\n\n如果您不确定该进程,请访问Monero论坛( https://forum.getmonero.org )以查找更多信息。
|
||||
account.altcoin.popup.transparentTx.msg=当使用{0}时,您只能使用透明地址(以t开头)而不是z开头地址(私有),因为仲裁者无法使用z地址验证事务。
|
||||
account.altcoin.popup.ZEC.msg=当使用{0}时,您只能使用透明地址(以t开头)而不是z开头地址(私有),因为仲裁者无法使用z地址验证事务。
|
||||
|
||||
account.fiat.yourFiatAccounts=您的法定货币\n账户:
|
||||
|
||||
|
|
|
@ -857,7 +857,7 @@ Si vous ne pouvez pas fournir les données ci-dessus ou si vous avez utilisé un
|
|||
Cas de contestation. L'expéditeur XMR est responsable de pouvoir vérifier le transfert XMR par l'Arbitre en cas de différend. \n \n\
|
||||
Il n'y a pas d'identification de paiement requise, juste l'adresse publique normale. \n \n\
|
||||
Si vous n'êtes pas sûr de ce processus, visitez le forum Monero (https://forum.getmonero.org) pour lire plus d'informations.
|
||||
account.altcoin.popup.transparentTx.msg=Lorsque vous utilisez {0}, vous ne pouvez utiliser que les adresses transparentes (en commençant par t) non\
|
||||
account.altcoin.popup.ZEC.msg=Lorsque vous utilisez {0}, vous ne pouvez utiliser que les adresses transparentes (en commençant par t) non\
|
||||
Les adresses z (privées), car l'arbitre ne pourrait pas vérifier la transaction avec les adresses z.
|
||||
account.fiat.yourFiatAccounts=Votre devise nationale \ncomptes:
|
||||
account.backup.title=Sauvegarder le portefeuille
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.2</version>
|
||||
<version>0.6.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.2</version>
|
||||
<version>0.6.4</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>core</artifactId>
|
||||
|
|
|
@ -119,7 +119,7 @@ public final class Alert implements ProtectedStoragePayload {
|
|||
|
||||
@Override
|
||||
public long getTTL() {
|
||||
return TimeUnit.DAYS.toMillis(30);
|
||||
return TimeUnit.DAYS.toMillis(60);
|
||||
}
|
||||
|
||||
public void setSigAndPubKey(String signatureAsBase64, PublicKey ownerPubKey) {
|
||||
|
|
|
@ -343,7 +343,7 @@ public class BisqEnvironment extends StandardEnvironment {
|
|||
else
|
||||
properties.remove(key);
|
||||
|
||||
log.info("properties=" + properties);
|
||||
log.debug("properties=" + properties);
|
||||
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
|
||||
properties.store(fileOutputStream, null);
|
||||
|
|
|
@ -37,6 +37,7 @@ import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
|||
import io.bisq.core.btc.exceptions.WalletException;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.TradeWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.offer.OpenOffer;
|
||||
import io.bisq.core.offer.OpenOfferManager;
|
||||
import io.bisq.core.trade.Contract;
|
||||
|
@ -65,6 +66,7 @@ public class DisputeManager implements PersistedDataHost {
|
|||
|
||||
private final TradeWalletService tradeWalletService;
|
||||
private final BtcWalletService walletService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final TradeManager tradeManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
|
@ -88,6 +90,7 @@ public class DisputeManager implements PersistedDataHost {
|
|||
public DisputeManager(P2PService p2PService,
|
||||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService walletService,
|
||||
WalletsSetup walletsSetup,
|
||||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
|
@ -97,6 +100,7 @@ public class DisputeManager implements PersistedDataHost {
|
|||
this.p2PService = p2PService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.walletService = walletService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.tradeManager = tradeManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
|
@ -112,13 +116,11 @@ public class DisputeManager implements PersistedDataHost {
|
|||
// We get first the message handler called then the onBootstrapped
|
||||
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
|
||||
decryptedDirectMessageWithPubKeys.add(decryptedMessageWithPubKey);
|
||||
if (p2PService.isBootstrapped())
|
||||
applyMessages();
|
||||
tryApplyMessages();
|
||||
});
|
||||
p2PService.addDecryptedMailboxListener((decryptedMessageWithPubKey, senderAddress) -> {
|
||||
decryptedMailboxMessageWithPubKeys.add(decryptedMessageWithPubKey);
|
||||
if (p2PService.isBootstrapped())
|
||||
applyMessages();
|
||||
tryApplyMessages();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,15 +137,24 @@ public class DisputeManager implements PersistedDataHost {
|
|||
}
|
||||
|
||||
public void onAllServicesInitialized() {
|
||||
if (p2PService.isBootstrapped())
|
||||
applyMessages();
|
||||
else
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
@Override
|
||||
public void onBootstrapComplete() {
|
||||
applyMessages();
|
||||
}
|
||||
});
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
@Override
|
||||
public void onBootstrapComplete() {
|
||||
tryApplyMessages();
|
||||
}
|
||||
});
|
||||
|
||||
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.isDownloadComplete())
|
||||
tryApplyMessages();
|
||||
});
|
||||
|
||||
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (walletsSetup.hasSufficientPeersForBroadcast())
|
||||
tryApplyMessages();
|
||||
});
|
||||
|
||||
tryApplyMessages();
|
||||
|
||||
cleanupDisputes();
|
||||
}
|
||||
|
@ -173,6 +184,17 @@ public class DisputeManager implements PersistedDataHost {
|
|||
});
|
||||
}
|
||||
|
||||
private void tryApplyMessages() {
|
||||
if (isReadyForTxBroadcast())
|
||||
applyMessages();
|
||||
}
|
||||
|
||||
private boolean isReadyForTxBroadcast() {
|
||||
return p2PService.isBootstrapped() &&
|
||||
walletsSetup.isDownloadComplete() &&
|
||||
walletsSetup.hasSufficientPeersForBroadcast();
|
||||
}
|
||||
|
||||
private void applyMessages() {
|
||||
decryptedDirectMessageWithPubKeys.forEach(decryptedMessageWithPubKey -> {
|
||||
NetworkEnvelope networkEnvelope = decryptedMessageWithPubKey.getNetworkEnvelope();
|
||||
|
@ -676,7 +698,7 @@ public class DisputeManager implements PersistedDataHost {
|
|||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error(t.getMessage());
|
||||
}
|
||||
});
|
||||
}, 15);
|
||||
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
|
||||
e.printStackTrace();
|
||||
log.error("Error at traderSignAndFinalizeDisputedPayoutTx " + e.getMessage());
|
||||
|
|
|
@ -68,7 +68,7 @@ public class BitcoinNodes {
|
|||
new BtcNode("bcwat.ch", "z33nukt7ngik3cpe.onion", "5.189.166.193", BtcNode.DEFAULT_PORT, "@sgeisler"),
|
||||
|
||||
// others
|
||||
new BtcNode("btc.jochen-hoenicke.de", null, "88.198.39.205", BtcNode.DEFAULT_PORT, "@jhoenicke"), // requested onion
|
||||
new BtcNode("btc.jochen-hoenicke.de", "sslnjjhnmwllysv4.onion", "88.198.39.205", BtcNode.DEFAULT_PORT, "@jhoenicke"),
|
||||
new BtcNode("bitcoin4-fullnode.csg.uzh.ch", null, "192.41.136.217", BtcNode.DEFAULT_PORT, "@tbocek") // requested onion
|
||||
) :
|
||||
new ArrayList<>();
|
||||
|
|
99
core/src/main/java/io/bisq/core/btc/wallet/Broadcaster.java
Normal file
99
core/src/main/java/io/bisq/core/btc/wallet/Broadcaster.java
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.core.btc.wallet;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.PeerGroup;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Slf4j
|
||||
public class Broadcaster {
|
||||
private static final int DEFAULT_BROADCAST_TIMEOUT = 8;
|
||||
private static Set<String> broadcastTimerSet = new HashSet<>();
|
||||
|
||||
public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction tx, FutureCallback<Transaction> callback) {
|
||||
broadcastTx(wallet, peerGroup, tx, callback, DEFAULT_BROADCAST_TIMEOUT);
|
||||
}
|
||||
|
||||
public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction tx, FutureCallback<Transaction> callback, int timeoutInSec) {
|
||||
final Timer timeoutTimer;
|
||||
if (!broadcastTimerSet.contains(tx.getHashAsString())) {
|
||||
timeoutTimer = UserThread.runAfter(() -> {
|
||||
log.warn("Broadcast of tx {} not completed after {} sec. We optimistically assume that the tx broadcast succeeded and " +
|
||||
"call onSuccess on the callback handler.", tx.getHashAsString(), timeoutInSec);
|
||||
|
||||
wallet.maybeCommitTx(tx);
|
||||
|
||||
callback.onSuccess(tx);
|
||||
|
||||
broadcastTimerSet.remove(tx.getHashAsString());
|
||||
}, timeoutInSec);
|
||||
broadcastTimerSet.add(tx.getHashAsString());
|
||||
} else {
|
||||
timeoutTimer = null;
|
||||
}
|
||||
|
||||
Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction result) {
|
||||
// At regtest we get called immediately back but we want to make sure that the handler is not called
|
||||
// before the caller is finished.
|
||||
UserThread.execute(() -> {
|
||||
if (broadcastTimerSet.contains(tx.getHashAsString())) {
|
||||
// If the timeout has not been called we call the callback.onSuccess
|
||||
stopAndRemoveTimer(timeoutTimer, tx);
|
||||
|
||||
if (result != null)
|
||||
wallet.maybeCommitTx(result);
|
||||
|
||||
callback.onSuccess(tx);
|
||||
} else {
|
||||
// Timeout was triggered, nothing to do anymore.
|
||||
log.info("onSuccess for tx {} was already called from timeout handler. ", tx.getHashAsString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
UserThread.execute(() -> {
|
||||
stopAndRemoveTimer(timeoutTimer, tx);
|
||||
|
||||
callback.onFailure(t);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void stopAndRemoveTimer(@Nullable Timer timer, Transaction tx) {
|
||||
if (timer != null)
|
||||
timer.stop();
|
||||
|
||||
broadcastTimerSet.remove(tx.getHashAsString());
|
||||
}
|
||||
}
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package io.bisq.core.btc.wallet;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
|
@ -261,16 +259,6 @@ public class BsqWalletService extends WalletService {
|
|||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void broadcastTx(Transaction tx, FutureCallback<Transaction> callback) {
|
||||
Futures.addCallback(walletsSetup.getPeerGroup().broadcastTransaction(tx).future(), callback);
|
||||
printTx("BSQ broadcast Tx", tx);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Send BSQ with BTC fee
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -19,8 +19,6 @@ package io.bisq.core.btc.wallet;
|
|||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import io.bisq.common.app.Log;
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
|
@ -160,45 +158,53 @@ public class TradeWalletService {
|
|||
log.debug("fundingAddress " + fundingAddress.toString());
|
||||
log.debug("reservedForTradeAddress " + reservedForTradeAddress.toString());
|
||||
log.debug("changeAddress " + changeAddress.toString());
|
||||
log.debug("reservedFundsForOffer " + reservedFundsForOffer.toPlainString());
|
||||
log.info("reservedFundsForOffer " + reservedFundsForOffer.toPlainString());
|
||||
log.debug("useSavingsWallet " + useSavingsWallet);
|
||||
log.debug("tradingFee " + tradingFee.toPlainString());
|
||||
log.debug("txFee " + txFee.toPlainString());
|
||||
log.info("tradingFee " + tradingFee.toPlainString());
|
||||
log.info("txFee " + txFee.toPlainString());
|
||||
log.debug("feeReceiverAddresses " + feeReceiverAddresses);
|
||||
|
||||
Transaction tradingFeeTx = new Transaction(params);
|
||||
tradingFeeTx.addOutput(tradingFee, Address.fromBase58(params, feeReceiverAddresses));
|
||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
||||
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
||||
SendRequest sendRequest = null;
|
||||
try {
|
||||
tradingFeeTx.addOutput(tradingFee, Address.fromBase58(params, feeReceiverAddresses));
|
||||
// the reserved amount we need for the trade we send to our trade reservedForTradeAddress
|
||||
tradingFeeTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
|
||||
|
||||
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
|
||||
// wait for 1 confirmation)
|
||||
// In case of double spend we will detect later in the trade process and use a ban score to penalize bad behaviour (not impl. yet)
|
||||
SendRequest sendRequest = SendRequest.forTx(tradingFeeTx);
|
||||
sendRequest.shuffleOutputs = false;
|
||||
sendRequest.aesKey = aesKey;
|
||||
if (useSavingsWallet)
|
||||
sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE));
|
||||
else
|
||||
sendRequest.coinSelector = new BtcCoinSelector(fundingAddress);
|
||||
// We use a fixed fee
|
||||
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
|
||||
// wait for 1 confirmation)
|
||||
// In case of double spend we will detect later in the trade process and use a ban score to penalize bad behaviour (not impl. yet)
|
||||
sendRequest = SendRequest.forTx(tradingFeeTx);
|
||||
sendRequest.shuffleOutputs = false;
|
||||
sendRequest.aesKey = aesKey;
|
||||
if (useSavingsWallet)
|
||||
sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE));
|
||||
else
|
||||
sendRequest.coinSelector = new BtcCoinSelector(fundingAddress);
|
||||
// We use a fixed fee
|
||||
|
||||
sendRequest.fee = txFee;
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
sendRequest.fee = txFee;
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
|
||||
// Change is optional in case of overpay or use of funds from savings wallet
|
||||
sendRequest.changeAddress = changeAddress;
|
||||
// Change is optional in case of overpay or use of funds from savings wallet
|
||||
sendRequest.changeAddress = changeAddress;
|
||||
|
||||
checkNotNull(wallet, "Wallet must not be null");
|
||||
wallet.completeTx(sendRequest);
|
||||
WalletService.printTx("tradingFeeTx", tradingFeeTx);
|
||||
checkNotNull(wallet, "Wallet must not be null");
|
||||
wallet.completeTx(sendRequest);
|
||||
WalletService.printTx("tradingFeeTx", tradingFeeTx);
|
||||
|
||||
checkNotNull(walletConfig, "walletConfig must not be null");
|
||||
ListenableFuture<Transaction> broadcastComplete = walletConfig.peerGroup().broadcastTransaction(tradingFeeTx).future();
|
||||
Futures.addCallback(broadcastComplete, callback);
|
||||
broadcastTx(tradingFeeTx, callback);
|
||||
|
||||
return tradingFeeTx;
|
||||
return tradingFeeTx;
|
||||
} catch (Throwable t) {
|
||||
if (wallet != null && sendRequest != null && sendRequest.coinSelector != null)
|
||||
log.warn("Balance = {}; CoinSelector = {}",
|
||||
wallet.getBalance(sendRequest.coinSelector),
|
||||
sendRequest.coinSelector);
|
||||
|
||||
log.warn("createBtcTradingFeeTx failed: tradingFeeTx={}, txOutputs={}", tradingFeeTx.toString(), tradingFeeTx.getOutputs());
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
public Transaction estimateBtcTradingFeeTxSize(Address fundingAddress,
|
||||
|
@ -227,6 +233,7 @@ public class TradeWalletService {
|
|||
sendRequest.ensureMinRequiredFee = false;
|
||||
sendRequest.changeAddress = changeAddress;
|
||||
checkNotNull(wallet, "Wallet must not be null");
|
||||
log.info("estimateBtcTradingFeeTxSize");
|
||||
wallet.completeTx(sendRequest);
|
||||
return tradingFeeTx;
|
||||
}
|
||||
|
@ -651,10 +658,7 @@ public class TradeWalletService {
|
|||
WalletService.verifyTransaction(depositTx);
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
|
||||
// Broadcast depositTx
|
||||
checkNotNull(walletConfig);
|
||||
ListenableFuture<Transaction> broadcastComplete = walletConfig.peerGroup().broadcastTransaction(depositTx).future();
|
||||
Futures.addCallback(broadcastComplete, callback);
|
||||
broadcastTx(depositTx, callback);
|
||||
|
||||
return depositTx;
|
||||
}
|
||||
|
@ -1035,29 +1039,31 @@ public class TradeWalletService {
|
|||
WalletService.verifyTransaction(payoutTx);
|
||||
WalletService.checkWalletConsistency(wallet);
|
||||
|
||||
if (walletConfig != null) {
|
||||
ListenableFuture<Transaction> future = walletConfig.peerGroup().broadcastTransaction(payoutTx).future();
|
||||
Futures.addCallback(future, callback);
|
||||
}
|
||||
broadcastTx(payoutTx, callback, 20);
|
||||
|
||||
return payoutTx;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
// Broadcast tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @param tx
|
||||
* @param callback
|
||||
*/
|
||||
public void broadcastTx(Transaction tx, FutureCallback<Transaction> callback) {
|
||||
checkNotNull(walletConfig);
|
||||
ListenableFuture<Transaction> future = walletConfig.peerGroup().broadcastTransaction(tx).future();
|
||||
Futures.addCallback(future, callback);
|
||||
Broadcaster.broadcastTx(wallet, walletConfig.peerGroup(), tx, callback);
|
||||
}
|
||||
|
||||
public void broadcastTx(Transaction tx, FutureCallback<Transaction> callback, int timeoutInSec) {
|
||||
checkNotNull(walletConfig);
|
||||
Broadcaster.broadcastTx(wallet, walletConfig.peerGroup(), tx, callback, timeoutInSec);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @param transaction The transaction to be added to the wallet
|
||||
* @return The transaction we added to the wallet, which is different as the one we passed as argument!
|
||||
|
@ -1200,9 +1206,10 @@ public class TradeWalletService {
|
|||
}
|
||||
|
||||
private void addAvailableInputsAndChangeOutputs(Transaction transaction, Address address, Address changeAddress, Coin txFee) throws WalletException {
|
||||
SendRequest sendRequest = null;
|
||||
try {
|
||||
// Lets let the framework do the work to find the right inputs
|
||||
SendRequest sendRequest = SendRequest.forTx(transaction);
|
||||
sendRequest = SendRequest.forTx(transaction);
|
||||
sendRequest.shuffleOutputs = false;
|
||||
sendRequest.aesKey = aesKey;
|
||||
// We use a fixed fee
|
||||
|
@ -1217,10 +1224,11 @@ public class TradeWalletService {
|
|||
// We don't commit that tx to the wallet as it will be changed later and it's not signed yet.
|
||||
// So it will not change the wallet balance.
|
||||
checkNotNull(wallet, "wallet must not be null");
|
||||
// TODO we got here exceptions with missing funds. Not reproducable but leave log for better debugging.
|
||||
log.info("print tx before wallet.completeTx: " + sendRequest.tx.toString());
|
||||
wallet.completeTx(sendRequest);
|
||||
} catch (Throwable t) {
|
||||
if (sendRequest != null && sendRequest.tx != null)
|
||||
log.warn("addAvailableInputsAndChangeOutputs: sendRequest.tx={}, sendRequest.tx.getOutputs()={}", sendRequest.tx, sendRequest.tx.getOutputs());
|
||||
|
||||
throw new WalletException(t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
|||
import io.bisq.common.app.Version;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.btc.ProxySocketFactory;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.core.listeners.DownloadProgressTracker;
|
||||
|
@ -104,6 +106,9 @@ public class WalletConfig extends AbstractIdleService {
|
|||
private boolean autoStop = true;
|
||||
private InputStream checkpoints;
|
||||
private boolean blockingStartup = true;
|
||||
@Getter
|
||||
@Setter
|
||||
private int minBroadcastConnections;
|
||||
|
||||
@Nullable
|
||||
private PeerDiscovery discovery;
|
||||
|
@ -215,18 +220,6 @@ public class WalletConfig extends AbstractIdleService {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will only connect to localhost. Cannot be called after startup.
|
||||
*/
|
||||
public WalletConfig connectToLocalHost() {
|
||||
try {
|
||||
return setPeerNodes(new PeerAddress(InetAddress.getLocalHost(), params.getPort()));
|
||||
} catch (UnknownHostException e) {
|
||||
// Borked machine with no loopback adapter configured properly.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, the wallet will save itself to disk automatically whenever it changes.
|
||||
*/
|
||||
|
@ -414,6 +407,10 @@ public class WalletConfig extends AbstractIdleService {
|
|||
vChain = new BlockChain(params, vStore);
|
||||
vPeerGroup = createPeerGroup();
|
||||
|
||||
vPeerGroup.setBroadcastToAllPeers(true);
|
||||
if (minBroadcastConnections > 0)
|
||||
vPeerGroup.setMinBroadcastConnections(minBroadcastConnections);
|
||||
|
||||
vPeerGroup.setUserAgent(userAgent, Version.VERSION);
|
||||
|
||||
// Set up peer addresses or discovery first, so if wallet extensions try to broadcast a transaction
|
||||
|
@ -467,6 +464,17 @@ public class WalletConfig extends AbstractIdleService {
|
|||
}
|
||||
}
|
||||
|
||||
void setPeerNodesForLocalHost() {
|
||||
try {
|
||||
setPeerNodes(new PeerAddress(InetAddress.getLocalHost(), params.getPort()));
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
// Borked machine with no loopback adapter configured properly.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Wallet createOrLoadWallet(File walletFile, boolean shouldReplayWallet,
|
||||
BisqKeyChainGroup keyChainGroup, boolean isBsqWallet, DeterministicSeed restoreFromSeed)
|
||||
throws Exception {
|
||||
|
|
|
@ -29,6 +29,7 @@ import io.bisq.core.btc.listeners.BalanceListener;
|
|||
import io.bisq.core.btc.listeners.TxConfidenceListener;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
import io.bisq.core.user.Preferences;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
|
@ -43,8 +44,6 @@ import org.bitcoinj.wallet.*;
|
|||
import org.bitcoinj.wallet.listeners.AbstractWalletEventListener;
|
||||
import org.bitcoinj.wallet.listeners.WalletEventListener;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -61,9 +60,8 @@ import static com.google.common.base.Preconditions.checkState;
|
|||
/**
|
||||
* Abstract base class for BTC and BSQ wallet. Provides all non-trade specific functionality.
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class WalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(WalletService.class);
|
||||
|
||||
protected final WalletsSetup walletsSetup;
|
||||
protected final Preferences preferences;
|
||||
protected final FeeService feeService;
|
||||
|
@ -288,6 +286,19 @@ public abstract class WalletService {
|
|||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Broadcast tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void broadcastTx(Transaction tx, FutureCallback<Transaction> callback) {
|
||||
Broadcaster.broadcastTx(wallet, walletsSetup.getPeerGroup(), tx, callback);
|
||||
}
|
||||
|
||||
public void broadcastTx(Transaction tx, FutureCallback<Transaction> callback, int timeoutInSec) {
|
||||
Broadcaster.broadcastTx(wallet, walletsSetup.getPeerGroup(), tx, callback, timeoutInSec);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// TransactionConfidence
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -199,7 +199,14 @@ public class WalletsSetup {
|
|||
}
|
||||
};
|
||||
|
||||
configPeerNodes(socks5Proxy);
|
||||
if (params == RegTestParams.get()) {
|
||||
configPeerNodesForRegTest();
|
||||
} else if (bisqEnvironment.isBitcoinLocalhostNodeRunning()) {
|
||||
configPeerNodesForLocalHostBitcoinNode();
|
||||
} else {
|
||||
configPeerNodes(socks5Proxy);
|
||||
}
|
||||
|
||||
walletConfig.setDownloadListener(downloadListener)
|
||||
.setBlockingStartup(false);
|
||||
|
||||
|
@ -271,110 +278,98 @@ public class WalletsSetup {
|
|||
return mode;
|
||||
}
|
||||
|
||||
private void configPeerNodesForRegTest() {
|
||||
walletConfig.setMinBroadcastConnections(1);
|
||||
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
|
||||
try {
|
||||
walletConfig.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else if (regTestHost == RegTestHost.LOCALHOST) {
|
||||
walletConfig.setPeerNodesForLocalHost();
|
||||
}
|
||||
}
|
||||
|
||||
private void configPeerNodesForLocalHostBitcoinNode() {
|
||||
walletConfig.setMinBroadcastConnections(1);
|
||||
walletConfig.setPeerNodesForLocalHost();
|
||||
}
|
||||
|
||||
private void configPeerNodes(Socks5Proxy socks5Proxy) {
|
||||
if (params == RegTestParams.get()) {
|
||||
if (regTestHost == RegTestHost.REG_TEST_SERVER) {
|
||||
try {
|
||||
walletConfig.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
List<BitcoinNodes.BtcNode> btcNodeList = new ArrayList<>();
|
||||
walletConfig.setMinBroadcastConnections((int) Math.floor(DEFAULT_CONNECTIONS * 0.8));
|
||||
switch (BitcoinNodes.BitcoinNodesOption.values()[preferences.getBitcoinNodesOptionOrdinal()]) {
|
||||
case CUSTOM:
|
||||
String bitcoinNodesString = preferences.getBitcoinNodes();
|
||||
if (bitcoinNodesString != null) {
|
||||
btcNodeList = Splitter.on(",")
|
||||
.splitToList(StringUtils.deleteWhitespace(bitcoinNodesString))
|
||||
.stream()
|
||||
.filter(e -> !e.isEmpty())
|
||||
.map(BitcoinNodes.BtcNode::fromFullAddress)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} else if (regTestHost == RegTestHost.LOCALHOST) {
|
||||
walletConfig.connectToLocalHost();
|
||||
}
|
||||
} else {
|
||||
List<BitcoinNodes.BtcNode> btcNodeList = new ArrayList<>();
|
||||
switch (BitcoinNodes.BitcoinNodesOption.values()[preferences.getBitcoinNodesOptionOrdinal()]) {
|
||||
case CUSTOM:
|
||||
String bitcoinNodesString = preferences.getBitcoinNodes();
|
||||
if (bitcoinNodesString != null) {
|
||||
btcNodeList = Splitter.on(",")
|
||||
.splitToList(StringUtils.deleteWhitespace(bitcoinNodesString))
|
||||
.stream()
|
||||
.filter(e -> !e.isEmpty())
|
||||
.map(BitcoinNodes.BtcNode::fromFullAddress)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
break;
|
||||
case PUBLIC:
|
||||
// we keep the empty list
|
||||
break;
|
||||
default:
|
||||
case PROVIDED:
|
||||
btcNodeList = bitcoinNodes.getProvidedBtcNodes();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PUBLIC:
|
||||
// we keep the empty list
|
||||
break;
|
||||
default:
|
||||
case PROVIDED:
|
||||
btcNodeList = bitcoinNodes.getProvidedBtcNodes();
|
||||
|
||||
final boolean useTorForBitcoinJ = socks5Proxy != null;
|
||||
List<PeerAddress> peerAddressList = new ArrayList<>();
|
||||
// We require only 4 nodes instead of 7 (for 9 max connections) because our provided nodes
|
||||
// are more reliable than random public nodes.
|
||||
walletConfig.setMinBroadcastConnections(4);
|
||||
break;
|
||||
}
|
||||
|
||||
// We connect to onion nodes only in case we use Tor for BitcoinJ (default) to avoid privacy leaks at
|
||||
// exit nodes with bloom filters.
|
||||
if (useTorForBitcoinJ) {
|
||||
btcNodeList.stream()
|
||||
.filter(BitcoinNodes.BtcNode::hasOnionAddress)
|
||||
.forEach(btcNode -> {
|
||||
// no DNS lookup for onion addresses
|
||||
log.info("We add a onion node. btcNode={}", btcNode);
|
||||
final String onionAddress = checkNotNull(btcNode.getOnionAddress());
|
||||
try {
|
||||
// OnionCat.onionHostToInetAddress converts onion to ipv6 representation
|
||||
// inetAddress is not used but required for wallet persistence. Throws nullPointer if not set.
|
||||
final InetAddress inetAddress = OnionCat.onionHostToInetAddress(onionAddress);
|
||||
final PeerAddress peerAddress = new PeerAddress(onionAddress, btcNode.getPort());
|
||||
peerAddress.setAddr(inetAddress);
|
||||
peerAddressList.add(peerAddress);
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("OnionCat.onionHostToInetAddress() failed with btcNode={}, error={}", btcNode.toString(), e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
if (useAllProvidedNodes) {
|
||||
// We also use the clear net nodes (used for monitor)
|
||||
btcNodeList.stream()
|
||||
.filter(BitcoinNodes.BtcNode::hasClearNetAddress)
|
||||
.forEach(btcNode -> {
|
||||
try {
|
||||
// We use DnsLookupTor to not leak with DNS lookup
|
||||
// Blocking call. takes about 600 ms ;-(
|
||||
InetSocketAddress address = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, btcNode.getHostNameOrAddress()), btcNode.getPort());
|
||||
log.info("We add a clear net node (tor is used) with InetAddress={}, btcNode={}", address.getAddress(), btcNode);
|
||||
peerAddressList.add(new PeerAddress(address.getAddress(), address.getPort()));
|
||||
} catch (Exception e) {
|
||||
if (btcNode.getAddress() != null) {
|
||||
log.warn("Dns lookup failed. We try with provided IP address. BtcNode: {}", btcNode);
|
||||
try {
|
||||
InetSocketAddress address = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, btcNode.getAddress()), btcNode.getPort());
|
||||
log.info("We add a clear net node (tor is used) with InetAddress={}, BtcNode={}", address.getAddress(), btcNode);
|
||||
peerAddressList.add(new PeerAddress(address.getAddress(), address.getPort()));
|
||||
} catch (Exception e2) {
|
||||
log.warn("Dns lookup failed for BtcNode: {}", btcNode);
|
||||
}
|
||||
} else {
|
||||
log.warn("Dns lookup failed. No IP address is provided. BtcNode: {}", btcNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
final boolean useTorForBitcoinJ = socks5Proxy != null;
|
||||
List<PeerAddress> peerAddressList = new ArrayList<>();
|
||||
|
||||
// We connect to onion nodes only in case we use Tor for BitcoinJ (default) to avoid privacy leaks at
|
||||
// exit nodes with bloom filters.
|
||||
if (useTorForBitcoinJ) {
|
||||
btcNodeList.stream()
|
||||
.filter(BitcoinNodes.BtcNode::hasOnionAddress)
|
||||
.forEach(btcNode -> {
|
||||
// no DNS lookup for onion addresses
|
||||
log.info("We add a onion node. btcNode={}", btcNode);
|
||||
final String onionAddress = checkNotNull(btcNode.getOnionAddress());
|
||||
try {
|
||||
// OnionCat.onionHostToInetAddress converts onion to ipv6 representation
|
||||
// inetAddress is not used but required for wallet persistence. Throws nullPointer if not set.
|
||||
final InetAddress inetAddress = OnionCat.onionHostToInetAddress(onionAddress);
|
||||
final PeerAddress peerAddress = new PeerAddress(onionAddress, btcNode.getPort());
|
||||
peerAddress.setAddr(inetAddress);
|
||||
peerAddressList.add(peerAddress);
|
||||
} catch (UnknownHostException e) {
|
||||
log.error("OnionCat.onionHostToInetAddress() failed with btcNode={}, error={}", btcNode.toString(), e.toString());
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
if (useAllProvidedNodes) {
|
||||
// We also use the clear net nodes (used for monitor)
|
||||
btcNodeList.stream()
|
||||
.filter(BitcoinNodes.BtcNode::hasClearNetAddress)
|
||||
.forEach(btcNode -> {
|
||||
try {
|
||||
InetSocketAddress address = new InetSocketAddress(btcNode.getHostNameOrAddress(), btcNode.getPort());
|
||||
log.info("We add a clear net node (no tor is used) with host={}, btcNode.getPort()={}", btcNode.getHostNameOrAddress(), btcNode.getPort());
|
||||
// We use DnsLookupTor to not leak with DNS lookup
|
||||
// Blocking call. takes about 600 ms ;-(
|
||||
InetSocketAddress address = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, btcNode.getHostNameOrAddress()), btcNode.getPort());
|
||||
log.info("We add a clear net node (tor is used) with InetAddress={}, btcNode={}", address.getAddress(), btcNode);
|
||||
peerAddressList.add(new PeerAddress(address.getAddress(), address.getPort()));
|
||||
} catch (Throwable t) {
|
||||
} catch (Exception e) {
|
||||
if (btcNode.getAddress() != null) {
|
||||
log.warn("Dns lookup failed. We try with provided IP address. BtcNode: {}", btcNode);
|
||||
try {
|
||||
InetSocketAddress address = new InetSocketAddress(btcNode.getAddress(), btcNode.getPort());
|
||||
log.info("We add a clear net node (no tor is used) with host={}, btcNode.getPort()={}", btcNode.getHostNameOrAddress(), btcNode.getPort());
|
||||
InetSocketAddress address = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, btcNode.getAddress()), btcNode.getPort());
|
||||
log.info("We add a clear net node (tor is used) with InetAddress={}, BtcNode={}", address.getAddress(), btcNode);
|
||||
peerAddressList.add(new PeerAddress(address.getAddress(), address.getPort()));
|
||||
} catch (Throwable t2) {
|
||||
log.warn("Failed to create InetSocketAddress from btcNode {}", btcNode);
|
||||
} catch (Exception e2) {
|
||||
log.warn("Dns lookup failed for BtcNode: {}", btcNode);
|
||||
}
|
||||
} else {
|
||||
log.warn("Dns lookup failed. No IP address is provided. BtcNode: {}", btcNode);
|
||||
|
@ -382,21 +377,44 @@ public class WalletsSetup {
|
|||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
btcNodeList.stream()
|
||||
.filter(BitcoinNodes.BtcNode::hasClearNetAddress)
|
||||
.forEach(btcNode -> {
|
||||
try {
|
||||
InetSocketAddress address = new InetSocketAddress(btcNode.getHostNameOrAddress(), btcNode.getPort());
|
||||
log.info("We add a clear net node (no tor is used) with host={}, btcNode.getPort()={}", btcNode.getHostNameOrAddress(), btcNode.getPort());
|
||||
peerAddressList.add(new PeerAddress(address.getAddress(), address.getPort()));
|
||||
} catch (Throwable t) {
|
||||
if (btcNode.getAddress() != null) {
|
||||
log.warn("Dns lookup failed. We try with provided IP address. BtcNode: {}", btcNode);
|
||||
try {
|
||||
InetSocketAddress address = new InetSocketAddress(btcNode.getAddress(), btcNode.getPort());
|
||||
log.info("We add a clear net node (no tor is used) with host={}, btcNode.getPort()={}", btcNode.getHostNameOrAddress(), btcNode.getPort());
|
||||
peerAddressList.add(new PeerAddress(address.getAddress(), address.getPort()));
|
||||
} catch (Throwable t2) {
|
||||
log.warn("Failed to create InetSocketAddress from btcNode {}", btcNode);
|
||||
}
|
||||
} else {
|
||||
log.warn("Dns lookup failed. No IP address is provided. BtcNode: {}", btcNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!peerAddressList.isEmpty()) {
|
||||
final PeerAddress[] peerAddresses = peerAddressList.toArray(new PeerAddress[peerAddressList.size()]);
|
||||
log.info("You connect with peerAddresses: " + peerAddressList.toString());
|
||||
walletConfig.setPeerNodes(peerAddresses);
|
||||
} else if (useTorForBitcoinJ) {
|
||||
if (params == MainNetParams.get())
|
||||
log.warn("You use the public Bitcoin network and are exposed to privacy issues caused by the broken bloom filters." +
|
||||
"See https://bisq.network/blog/privacy-in-bitsquare/ for more info. It is recommended to use the provided nodes.");
|
||||
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
|
||||
walletConfig.setDiscovery(new Socks5MultiDiscovery(socks5Proxy, params, socks5DiscoverMode));
|
||||
} else {
|
||||
log.warn("You don't use gtor and use the public Bitcoin network and are exposed to privacy issues caused by the broken bloom filters." +
|
||||
"See https://bisq.network/blog/privacy-in-bitsquare/ for more info. It is recommended to use Tor and the provided nodes.");
|
||||
}
|
||||
if (!peerAddressList.isEmpty()) {
|
||||
final PeerAddress[] peerAddresses = peerAddressList.toArray(new PeerAddress[peerAddressList.size()]);
|
||||
log.info("You connect with peerAddresses: " + peerAddressList.toString());
|
||||
walletConfig.setPeerNodes(peerAddresses);
|
||||
} else if (useTorForBitcoinJ) {
|
||||
if (params == MainNetParams.get())
|
||||
log.warn("You use the public Bitcoin network and are exposed to privacy issues caused by the broken bloom filters." +
|
||||
"See https://bisq.network/blog/privacy-in-bitsquare/ for more info. It is recommended to use the provided nodes.");
|
||||
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
|
||||
walletConfig.setDiscovery(new Socks5MultiDiscovery(socks5Proxy, params, socks5DiscoverMode));
|
||||
} else {
|
||||
log.warn("You don't use gtor and use the public Bitcoin network and are exposed to privacy issues caused by the broken bloom filters." +
|
||||
"See https://bisq.network/blog/privacy-in-bitsquare/ for more info. It is recommended to use Tor and the provided nodes.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,6 +512,14 @@ public class WalletsSetup {
|
|||
return downloadListener.percentageProperty();
|
||||
}
|
||||
|
||||
public boolean isDownloadComplete() {
|
||||
return downloadPercentageProperty().get() == 1d;
|
||||
}
|
||||
|
||||
public boolean isBitcoinLocalhostNodeRunning() {
|
||||
return bisqEnvironment.isBitcoinLocalhostNodeRunning();
|
||||
}
|
||||
|
||||
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {
|
||||
return ImmutableList.copyOf(addressEntryList.getList()).stream()
|
||||
.filter(addressEntry -> addressEntry.getContext() == context)
|
||||
|
@ -507,6 +533,14 @@ public class WalletsSetup {
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public boolean hasSufficientPeersForBroadcast() {
|
||||
return numPeers.get() >= getMinBroadcastConnections();
|
||||
}
|
||||
|
||||
public int getMinBroadcastConnections() {
|
||||
return walletConfig.getMinBroadcastConnections();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Inner classes
|
||||
|
|
|
@ -86,6 +86,7 @@ public class FilterManager {
|
|||
DevEnv.DEV_PRIVILEGE_PUB_KEY :
|
||||
"022ac7b7766b0aedff82962522c2c14fb8d1961dabef6e5cfd10edc679456a32f1";
|
||||
private ECKey filterSigningKey;
|
||||
private boolean providersRepositoryFiltered;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -185,7 +186,8 @@ public class FilterManager {
|
|||
bisqEnvironment.saveBannedSeedNodes(null);
|
||||
bisqEnvironment.saveBannedPriceRelayNodes(null);
|
||||
providersRepository.applyBannedNodes(null);
|
||||
providersRepository.selectNewRandomBaseUrl();
|
||||
if (providersRepositoryFiltered)
|
||||
providersRepository.selectNewRandomBaseUrl();
|
||||
filterProperty.set(null);
|
||||
}
|
||||
|
||||
|
@ -202,6 +204,7 @@ public class FilterManager {
|
|||
bisqEnvironment.saveBannedPriceRelayNodes(priceRelayNodes);
|
||||
providersRepository.applyBannedNodes(priceRelayNodes);
|
||||
providersRepository.selectNewRandomBaseUrl();
|
||||
providersRepositoryFiltered = true;
|
||||
|
||||
filterProperty.set(filter);
|
||||
listeners.stream().forEach(e -> e.onFilterAdded(filter));
|
||||
|
|
|
@ -72,8 +72,6 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
|||
|
||||
final TradeWalletService tradeWalletService = model.getTradeWalletService();
|
||||
|
||||
// We dont use a timeout here as we need to get the tradeFee tx callback called to be sure the addressEntry is funded
|
||||
|
||||
if (offer.isCurrencyForMakerFeeBtc()) {
|
||||
tradeFeeTx = tradeWalletService.createBtcTradingFeeTx(
|
||||
fundingAddress,
|
||||
|
@ -131,8 +129,6 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
|
|||
// if it gets committed 2 times
|
||||
tradeWalletService.commitTx(tradeWalletService.getClonedTransaction(signedTx));
|
||||
|
||||
// We dont use a timeout here as we need to get the tradeFee tx callback called to be sure the addressEntry is funded
|
||||
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
|
|
|
@ -90,7 +90,6 @@ public class ProvidersRepository {
|
|||
else
|
||||
log.warn("We received banned provider nodes: bannedNodes={}, selected baseUrl={}, providerList={}",
|
||||
bannedNodes, baseUrl, providerList);
|
||||
|
||||
}
|
||||
|
||||
public void selectNewRandomBaseUrl() {
|
||||
|
|
|
@ -56,7 +56,7 @@ public class FeeService {
|
|||
|
||||
// DEFAULT_TX_FEE used in FeeRequestService for non-BTC currencies and for BTC only if we cannot access fee service
|
||||
// fees are per byte
|
||||
public static final long BTC_DEFAULT_TX_FEE = 100; // fees are between 20-400 sat/byte so we try to stay in average
|
||||
public static final long BTC_DEFAULT_TX_FEE = 200; // fees are between 20-600 sat/byte. We try to stay on the safe side.
|
||||
public static final long LTC_DEFAULT_TX_FEE = LTC_REFERENCE_DEFAULT_MIN_TX_FEE.value / 200;
|
||||
public static final long DOGE_DEFAULT_TX_FEE = DOGE_REFERENCE_DEFAULT_MIN_TX_FEE.value / 200; // 200 bytes tx -> 200*5_000_000L=1_000_000_000 (1 DOGE)
|
||||
public static final long DASH_DEFAULT_TX_FEE = DASH_REFERENCE_DEFAULT_MIN_TX_FEE.value / 200; // 200 bytes tx -> 200*50=10000
|
||||
|
|
|
@ -63,7 +63,7 @@ public class PriceFeedService {
|
|||
private long epochInSecondAtLastRequest;
|
||||
private Map<String, Long> timeStampMap = new HashMap<>();
|
||||
private int retryCounter = 0;
|
||||
private int retryDelay = 1;
|
||||
private long requestTs;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -97,25 +97,34 @@ public class PriceFeedService {
|
|||
}
|
||||
}
|
||||
|
||||
public void initialRequestPriceFeed() {
|
||||
request(false);
|
||||
}
|
||||
|
||||
public void requestPriceFeed(Consumer<Double> resultHandler, FaultHandler faultHandler) {
|
||||
this.priceConsumer = resultHandler;
|
||||
this.faultHandler = faultHandler;
|
||||
|
||||
request();
|
||||
request(true);
|
||||
}
|
||||
|
||||
public String getProviderNodeAddress() {
|
||||
return httpClient.getBaseUrl();
|
||||
}
|
||||
|
||||
private void request() {
|
||||
private void request(boolean repeatRequests) {
|
||||
requestTs = System.currentTimeMillis();
|
||||
requestAllPrices(priceProvider, () -> {
|
||||
applyPriceToConsumer();
|
||||
// after first response we know the providers timestamp and want to request quickly after next expected update
|
||||
long delay = Math.max(40, Math.min(90, PERIOD_SEC - (Instant.now().getEpochSecond() - epochInSecondAtLastRequest) + 2 + new Random().nextInt(5)));
|
||||
UserThread.runAfter(this::request, delay);
|
||||
retryDelay = 1;
|
||||
|
||||
if (repeatRequests) {
|
||||
// After first response we know the providers timestamp and want to request quickly after next expected update
|
||||
// We limit request interval to 40-90 sec.
|
||||
long delay = Math.max(40, Math.min(90, PERIOD_SEC - (Instant.now().getEpochSecond() - epochInSecondAtLastRequest) + 2 + new Random().nextInt(5)));
|
||||
UserThread.runAfter(() -> request(true), delay);
|
||||
}
|
||||
}, (errorMessage, throwable) -> {
|
||||
log.warn("request from priceProvider failed: errorMessage={}", errorMessage);
|
||||
// Try other provider if more then 1 is available
|
||||
if (providersRepository.hasMoreProviders()) {
|
||||
providersRepository.selectNewRandomBaseUrl();
|
||||
|
@ -123,9 +132,8 @@ public class PriceFeedService {
|
|||
}
|
||||
UserThread.runAfter(() -> {
|
||||
retryCounter++;
|
||||
retryDelay *= retryCounter;
|
||||
request();
|
||||
}, retryDelay);
|
||||
request(true);
|
||||
}, retryCounter);
|
||||
|
||||
this.faultHandler.handleFault(errorMessage, throwable);
|
||||
});
|
||||
|
@ -232,6 +240,7 @@ public class PriceFeedService {
|
|||
if (cache.containsKey(currencyCode)) {
|
||||
try {
|
||||
MarketPrice marketPrice = cache.get(currencyCode);
|
||||
log.info("Received new marketPrice={} {} sec. after request", marketPrice, (System.currentTimeMillis() - requestTs) / 1000);
|
||||
if (marketPrice.isRecentExternalPriceAvailable())
|
||||
priceConsumer.accept(marketPrice.getPrice());
|
||||
} catch (Throwable t) {
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
package io.bisq.core.trade.protocol.tasks.buyer_as_taker;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.crypto.Hash;
|
||||
import io.bisq.common.taskrunner.TaskRunner;
|
||||
import io.bisq.core.btc.AddressEntry;
|
||||
|
@ -78,16 +76,6 @@ public class BuyerAsTakerSignAndPublishDepositTx extends TradeTask {
|
|||
checkArgument(Arrays.equals(buyerMultiSigPubKey, buyerMultiSigAddressEntry.getPubKey()),
|
||||
"buyerMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + id);
|
||||
|
||||
|
||||
Timer timeoutTimer = UserThread.runAfter(() -> {
|
||||
log.warn("Broadcast not completed after 5 sec. We go on with the trade protocol.");
|
||||
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
|
||||
log.error("timeoutTimer, offerId={}, RESERVED_FOR_TRADE", id);
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
||||
complete();
|
||||
}, 5);
|
||||
|
||||
Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
|
||||
false,
|
||||
contractHash,
|
||||
|
@ -101,9 +89,7 @@ public class BuyerAsTakerSignAndPublishDepositTx extends TradeTask {
|
|||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
if (!completed) {
|
||||
timeoutTimer.stop();
|
||||
log.trace("takerSignAndPublishTx succeeded " + transaction);
|
||||
trade.setDepositTx(transaction);
|
||||
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
||||
|
@ -116,13 +102,13 @@ public class BuyerAsTakerSignAndPublishDepositTx extends TradeTask {
|
|||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
if (!completed) {
|
||||
timeoutTimer.stop();
|
||||
failed(t);
|
||||
} else {
|
||||
log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
|
||||
}
|
||||
}
|
||||
});
|
||||
// We set the deposit tx in case we get the onFailure called.
|
||||
trade.setDepositTx(depositTx);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
package io.bisq.core.trade.protocol.tasks.seller;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.taskrunner.TaskRunner;
|
||||
import io.bisq.core.trade.Trade;
|
||||
import io.bisq.core.trade.protocol.tasks.TradeTask;
|
||||
|
@ -52,17 +50,11 @@ public class SellerBroadcastPayoutTx extends TradeTask {
|
|||
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
||||
complete();
|
||||
} else {
|
||||
Timer timeoutTimer = UserThread.runAfter(() -> {
|
||||
log.warn("Broadcast not completed after 5 sec. We go on with the trade protocol.");
|
||||
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
||||
complete();
|
||||
}, 5);
|
||||
processModel.getTradeWalletService().broadcastTx(payoutTx,
|
||||
new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
if (!completed) {
|
||||
timeoutTimer.stop();
|
||||
log.debug("BroadcastTx succeeded. Transaction:" + transaction);
|
||||
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
||||
complete();
|
||||
|
@ -74,7 +66,6 @@ public class SellerBroadcastPayoutTx extends TradeTask {
|
|||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
if (!completed) {
|
||||
timeoutTimer.stop();
|
||||
log.error("BroadcastTx failed. Error:" + t.getMessage());
|
||||
failed(t);
|
||||
} else {
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
package io.bisq.core.trade.protocol.tasks.seller_as_taker;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.crypto.Hash;
|
||||
import io.bisq.common.taskrunner.TaskRunner;
|
||||
import io.bisq.core.btc.AddressEntry;
|
||||
|
@ -79,15 +77,6 @@ public class SellerAsTakerSignAndPublishDepositTx extends TradeTask {
|
|||
|
||||
TradingPeer tradingPeer = processModel.getTradingPeer();
|
||||
|
||||
Timer timeoutTimer = UserThread.runAfter(() -> {
|
||||
log.warn("Broadcast not completed after 5 sec. We go on with the trade protocol.");
|
||||
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
|
||||
log.debug("timeoutTimer, offerId={}, RESERVED_FOR_TRADE", trade.getId());
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
||||
complete();
|
||||
}, 5);
|
||||
|
||||
Transaction depositTx = processModel.getTradeWalletService().takerSignsAndPublishesDepositTx(
|
||||
true,
|
||||
contractHash,
|
||||
|
@ -101,9 +90,7 @@ public class SellerAsTakerSignAndPublishDepositTx extends TradeTask {
|
|||
@Override
|
||||
public void onSuccess(Transaction transaction) {
|
||||
if (!completed) {
|
||||
timeoutTimer.stop();
|
||||
log.trace("takerSignAndPublishTx succeeded " + transaction);
|
||||
trade.setDepositTx(transaction);
|
||||
trade.setState(Trade.State.TAKER_PUBLISHED_DEPOSIT_TX);
|
||||
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
|
||||
|
@ -116,13 +103,13 @@ public class SellerAsTakerSignAndPublishDepositTx extends TradeTask {
|
|||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
if (!completed) {
|
||||
timeoutTimer.stop();
|
||||
failed(t);
|
||||
} else {
|
||||
log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
|
||||
}
|
||||
}
|
||||
});
|
||||
// We set the deposit tx in case we get the onFailure called.
|
||||
trade.setDepositTx(depositTx);
|
||||
} catch (Throwable t) {
|
||||
final Contract contract = trade.getContract();
|
||||
|
|
|
@ -125,6 +125,7 @@ public class CreateTakerFeeTx extends TradeTask {
|
|||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
tradeWalletService.commitTx(tradeWalletService.getClonedTransaction(signedTx));
|
||||
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
|
|
|
@ -251,7 +251,12 @@ public class TradeStatisticsManager {
|
|||
coinsWithValidator.add("CAGE");
|
||||
coinsWithValidator.add("CRED");
|
||||
coinsWithValidator.add("XSPEC");
|
||||
|
||||
// v0.6.3
|
||||
coinsWithValidator.add("WILD");
|
||||
coinsWithValidator.add("ONION");
|
||||
// v0.6.4
|
||||
coinsWithValidator.add("CREA");
|
||||
coinsWithValidator.add("XIN");
|
||||
|
||||
Set<String> newlyAdded = new HashSet<>();
|
||||
// v0.6.0
|
||||
|
@ -273,7 +278,12 @@ public class TradeStatisticsManager {
|
|||
newlyAdded.add("CAGE");
|
||||
newlyAdded.add("CRED");
|
||||
newlyAdded.add("XSPEC");
|
||||
|
||||
// v0.6.3
|
||||
newlyAdded.add("WILD");
|
||||
newlyAdded.add("ONION");
|
||||
// v0.6.4
|
||||
newlyAdded.add("CREA");
|
||||
newlyAdded.add("XIN");
|
||||
|
||||
CurrencyUtil.getAllSortedCryptoCurrencies().stream()
|
||||
.forEach(e -> allCryptoCurrencies.add(e.getNameAndCode()));
|
||||
|
|
|
@ -40,7 +40,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
new BlockChainExplorer("OXT", "https://oxt.me/transaction/", "https://oxt.me/address/"),
|
||||
new BlockChainExplorer("Blockcypher", "https://live.blockcypher.com/btc/tx/", "https://live.blockcypher.com/btc/address/"),
|
||||
new BlockChainExplorer("Tradeblock", "https://tradeblock.com/bitcoin/tx/", "https://tradeblock.com/bitcoin/address/"),
|
||||
new BlockChainExplorer("Blockexplorer", "https://blockexplorer.com/tx/", "https://blockexplorer.com/address/"),
|
||||
new BlockChainExplorer("Biteasy", "https://www.biteasy.com/transactions/", "https://www.biteasy.com/addresses/"),
|
||||
new BlockChainExplorer("Blockonomics", "https://www.blockonomics.co/api/tx?txid=", "https://www.blockonomics.co/#/search?q="),
|
||||
new BlockChainExplorer("Chainflyer", "http://chainflyer.bitflyer.jp/Transaction/", "http://chainflyer.bitflyer.jp/Address/"),
|
||||
|
@ -53,7 +52,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
private static final ArrayList<BlockChainExplorer> BTC_TEST_NET_EXPLORERS = new ArrayList<>(Arrays.asList(
|
||||
new BlockChainExplorer("Blockcypher", "https://live.blockcypher.com/btc-testnet/tx/", "https://live.blockcypher.com/btc-testnet/address/"),
|
||||
new BlockChainExplorer("Blocktrail", "https://www.blocktrail.com/tBTC/tx/", "https://www.blocktrail.com/tBTC/address/"),
|
||||
new BlockChainExplorer("Blockexplorer", "https://blockexplorer.com/testnet/tx/", "https://blockexplorer.com/testnet/address/"),
|
||||
new BlockChainExplorer("Biteasy", "https://www.biteasy.com/testnet/transactions/", "https://www.biteasy.com/testnet/addresses/"),
|
||||
new BlockChainExplorer("Smartbit", "https://testnet.smartbit.com.au/tx/", "https://testnet.smartbit.com.au/address/"),
|
||||
new BlockChainExplorer("SoChain. Wow.", "https://chain.so/tx/BTCTEST/", "https://chain.so/address/BTCTEST/")
|
||||
|
@ -608,9 +606,9 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
|
|||
|
||||
private void updateTradeCurrencies(ListChangeListener.Change<? extends TradeCurrency> change) {
|
||||
change.next();
|
||||
if (change.wasAdded() && change.getAddedSize() == 1)
|
||||
if (change.wasAdded() && change.getAddedSize() == 1 && initialReadDone)
|
||||
tradeCurrenciesAsObservable.add(change.getAddedSubList().get(0));
|
||||
else if (change.wasRemoved() && change.getRemovedSize() == 1)
|
||||
else if (change.wasRemoved() && change.getRemovedSize() == 1 && initialReadDone)
|
||||
tradeCurrenciesAsObservable.remove(change.getRemoved().get(0));
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.2</version>
|
||||
<version>0.6.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -88,6 +88,13 @@
|
|||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<!-- For the binary build that bouncycastle exclusion need to be removed. -->
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.bouncycastle:*:*:*</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
|
||||
<!-- broken with Java 8 (MSHADE-174), using ProGuard instead. -->
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<transformers>
|
||||
|
|
|
@ -266,10 +266,8 @@ public class BisqApp extends Application {
|
|||
else
|
||||
new Popup<>().warning(Res.get("popup.warning.walletNotInitialized")).show();
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.G, keyEvent)) {
|
||||
TradeWalletService tradeWalletService = injector.getInstance(TradeWalletService.class);
|
||||
BtcWalletService walletService = injector.getInstance(BtcWalletService.class);
|
||||
if (walletService.isWalletReady())
|
||||
new ManualPayoutTxWindow(tradeWalletService).show();
|
||||
if (injector.getInstance(BtcWalletService.class).isWalletReady())
|
||||
injector.getInstance(ManualPayoutTxWindow.class).show();
|
||||
else
|
||||
new Popup<>().warning(Res.get("popup.warning.walletNotInitialized")).show();
|
||||
} else if (DevEnv.DEV_MODE) {
|
||||
|
|
|
@ -68,7 +68,7 @@ import static javafx.scene.layout.AnchorPane.*;
|
|||
@Slf4j
|
||||
public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||
// If after 30 sec we have not got connected we show "open network settings" button
|
||||
private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 30;
|
||||
private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 90;
|
||||
private Label versionLabel;
|
||||
|
||||
public static StackPane getRootContainer() {
|
||||
|
|
|
@ -182,7 +182,6 @@ public class MainViewModel implements ViewModel {
|
|||
final StringProperty numOpenDisputesAsString = new SimpleStringProperty();
|
||||
final BooleanProperty showOpenDisputesNotification = new SimpleBooleanProperty();
|
||||
private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty();
|
||||
private final String btcNetworkAsString;
|
||||
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
|
@ -250,9 +249,6 @@ public class MainViewModel implements ViewModel {
|
|||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.formatter = formatter;
|
||||
|
||||
btcNetworkAsString = Res.get(BisqEnvironment.getBaseCurrencyNetwork().name()) +
|
||||
(preferences.getUseTorForBitcoinJ() ? (" " + Res.get("mainView.footer.usingTor")) : "");
|
||||
|
||||
TxIdTextField.setPreferences(preferences);
|
||||
|
||||
// TODO
|
||||
|
@ -409,17 +405,8 @@ public class MainViewModel implements ViewModel {
|
|||
initWalletService();
|
||||
|
||||
// We want to get early connected to the price relay so we call it already now
|
||||
long ts = new Date().getTime();
|
||||
final boolean[] logged = {false};
|
||||
priceFeedService.setCurrencyCodeOnInit();
|
||||
priceFeedService.requestPriceFeed(price -> {
|
||||
if (!logged[0]) {
|
||||
log.info("We received data from the price relay after {} ms.",
|
||||
(new Date().getTime() - ts));
|
||||
logged[0] = true;
|
||||
}
|
||||
},
|
||||
(errorMessage, throwable) -> log.error("requestPriceFeed failed:" + errorMessage));
|
||||
priceFeedService.initialRequestPriceFeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -505,7 +492,7 @@ public class MainViewModel implements ViewModel {
|
|||
result = Res.get("mainView.footer.btcInfo",
|
||||
peers,
|
||||
Res.get("mainView.footer.btcInfo.synchronizedWith"),
|
||||
btcNetworkAsString);
|
||||
getBtcNetworkAsString());
|
||||
btcSplashSyncIconId.set("image-connection-synced");
|
||||
|
||||
if (allBasicServicesInitialized)
|
||||
|
@ -514,18 +501,18 @@ public class MainViewModel implements ViewModel {
|
|||
result = Res.get("mainView.footer.btcInfo",
|
||||
peers,
|
||||
Res.get("mainView.footer.btcInfo.synchronizedWith"),
|
||||
btcNetworkAsString + ": " + formatter.formatToPercentWithSymbol(percentage));
|
||||
getBtcNetworkAsString() + ": " + formatter.formatToPercentWithSymbol(percentage));
|
||||
} else {
|
||||
result = Res.get("mainView.footer.btcInfo",
|
||||
peers,
|
||||
Res.get("mainView.footer.btcInfo.connectingTo"),
|
||||
btcNetworkAsString);
|
||||
getBtcNetworkAsString());
|
||||
}
|
||||
} else {
|
||||
result = Res.get("mainView.footer.btcInfo",
|
||||
numBtcPeers,
|
||||
Res.get("mainView.footer.btcInfo.connectionFailed"),
|
||||
btcNetworkAsString);
|
||||
getBtcNetworkAsString());
|
||||
log.error(exception.getMessage());
|
||||
if (exception instanceof TimeoutException) {
|
||||
walletServiceErrorMsg.set(Res.get("mainView.walletServiceErrorMsg.timeout"));
|
||||
|
@ -933,7 +920,10 @@ public class MainViewModel implements ViewModel {
|
|||
checkNumberOfBtcPeersTimer = UserThread.runAfter(() -> {
|
||||
// check again numPeers
|
||||
if (walletsSetup.numPeersProperty().get() == 0) {
|
||||
walletServiceErrorMsg.set(Res.get("mainView.networkWarning.allConnectionsLost", Res.getBaseCurrencyName().toLowerCase()));
|
||||
if (bisqEnvironment.isBitcoinLocalhostNodeRunning())
|
||||
walletServiceErrorMsg.set(Res.get("mainView.networkWarning.localhostBitcoinLost", Res.getBaseCurrencyName().toLowerCase()));
|
||||
else
|
||||
walletServiceErrorMsg.set(Res.get("mainView.networkWarning.allConnectionsLost", Res.getBaseCurrencyName().toLowerCase()));
|
||||
} else {
|
||||
walletServiceErrorMsg.set(null);
|
||||
}
|
||||
|
@ -1279,4 +1269,15 @@ public class MainViewModel implements ViewModel {
|
|||
void openDownloadWindow() {
|
||||
displayAlertIfPresent(user.getDisplayedAlert(), true);
|
||||
}
|
||||
|
||||
public String getBtcNetworkAsString() {
|
||||
String postFix;
|
||||
if (bisqEnvironment.isBitcoinLocalhostNodeRunning())
|
||||
postFix = " " + Res.get("mainView.footer.localhostBitcoinNode");
|
||||
else if (preferences.getUseTorForBitcoinJ())
|
||||
postFix = " " + Res.get("mainView.footer.usingTor");
|
||||
else
|
||||
postFix = "";
|
||||
return Res.get(BisqEnvironment.getBaseCurrencyNetwork().name()) + postFix;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,12 +141,12 @@ public class AltCoinAccountsView extends ActivatableViewAndModel<GridPane, AltCo
|
|||
.show();
|
||||
break;
|
||||
case "ZEC":
|
||||
new Popup<>().information(Res.get("account.altcoin.popup.transparentTx.msg", "ZEC"))
|
||||
new Popup<>().information(Res.get("account.altcoin.popup.ZEC.msg", "ZEC"))
|
||||
.useIUnderstandButton()
|
||||
.show();
|
||||
break;
|
||||
case "XZC":
|
||||
new Popup<>().information(Res.get("account.altcoin.popup.transparentTx.msg", "XZC"))
|
||||
new Popup<>().information(Res.get("account.altcoin.popup.XZC.msg", "XZC"))
|
||||
.useIUnderstandButton()
|
||||
.show();
|
||||
break;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package io.bisq.gui.main.account.content.backup;
|
||||
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.common.storage.FileUtil;
|
||||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.app.AppOptionKeys;
|
||||
|
@ -36,7 +37,6 @@ import javafx.scene.control.TextField;
|
|||
import javafx.scene.layout.GridPane;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
@ -146,10 +146,9 @@ public class BackupView extends ActivatableView<GridPane, Void> {
|
|||
String backupDirectory = preferences.getBackupDirectory();
|
||||
if (backupDirectory != null && backupDirectory.length() > 0) {
|
||||
try {
|
||||
String dateString = new SimpleDateFormat("YYYY-MM-dd-HHmmss").format(new Date());
|
||||
String dateString = new SimpleDateFormat("yyyy-MM-dd-HHmmss").format(new Date());
|
||||
String destination = Paths.get(backupDirectory, "bisq_backup_" + dateString).toString();
|
||||
FileUtils.copyDirectory(dataDir,
|
||||
new File(destination));
|
||||
FileUtil.copyDirectory(dataDir, new File(destination));
|
||||
new Popup<>().feedback(Res.get("account.backup.success", destination)).show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
|
|
|
@ -226,7 +226,9 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
|
|||
}
|
||||
|
||||
private void doRestore() {
|
||||
long date = restoreDatePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||
final LocalDate value = restoreDatePicker.getValue();
|
||||
//TODO Is ZoneOffset correct?
|
||||
long date = value != null ? value.atStartOfDay().toEpochSecond(ZoneOffset.UTC) : 0;
|
||||
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
|
||||
walletsManager.restoreSeedWords(
|
||||
seed,
|
||||
|
|
|
@ -27,6 +27,7 @@ import io.bisq.core.btc.exceptions.WalletException;
|
|||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.ChangeBelowDustException;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.dao.DaoConstants;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestPayload;
|
||||
|
@ -37,6 +38,7 @@ import io.bisq.gui.common.view.FxmlView;
|
|||
import io.bisq.gui.main.dao.compensation.CompensationRequestDisplay;
|
||||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.network.p2p.NodeAddress;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -67,9 +69,10 @@ public class CreateCompensationRequestView extends ActivatableView<GridPane, Voi
|
|||
private final PublicKey p2pStorageSignaturePubKey;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final P2PService p2PService;
|
||||
private final FeeService feeService;
|
||||
private final CompensationRequestManager compensationRequestManager;
|
||||
private final P2PService p2PService;
|
||||
private final BSFormatter btcFormatter;
|
||||
|
||||
@Nullable
|
||||
|
@ -80,14 +83,19 @@ public class CreateCompensationRequestView extends ActivatableView<GridPane, Voi
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private CreateCompensationRequestView(BsqWalletService bsqWalletService, BtcWalletService btcWalletService, FeeService feeService,
|
||||
CompensationRequestManager compensationRequestManager, P2PService p2PService,
|
||||
private CreateCompensationRequestView(BsqWalletService bsqWalletService,
|
||||
BtcWalletService btcWalletService,
|
||||
WalletsSetup walletsSetup,
|
||||
P2PService p2PService,
|
||||
FeeService feeService,
|
||||
CompensationRequestManager compensationRequestManager,
|
||||
KeyRing keyRing, BSFormatter btcFormatter) {
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.p2PService = p2PService;
|
||||
this.feeService = feeService;
|
||||
this.compensationRequestManager = compensationRequestManager;
|
||||
this.p2PService = p2PService;
|
||||
this.btcFormatter = btcFormatter;
|
||||
|
||||
p2pStorageSignaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();
|
||||
|
@ -106,99 +114,105 @@ public class CreateCompensationRequestView extends ActivatableView<GridPane, Voi
|
|||
protected void activate() {
|
||||
compensationRequestDisplay.fillWithMock();
|
||||
createButton.setOnAction(event -> {
|
||||
//TODO
|
||||
Date startDate = new Date();
|
||||
Date endDate = new Date();
|
||||
// TODO break up in methods
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
//TODO date is just dummy impl.
|
||||
Date startDate = new Date();
|
||||
Date endDate = new Date();
|
||||
|
||||
// TODO can be null if we are still not full connected
|
||||
nodeAddress = p2PService.getAddress();
|
||||
// TODO can be null if we are still not full connected
|
||||
nodeAddress = p2PService.getAddress();
|
||||
|
||||
// TODO we neet to wait until all services are initiated before calling the code below
|
||||
checkNotNull(nodeAddress, "nodeAddress must not be null");
|
||||
CompensationRequestPayload compensationRequestPayload = new CompensationRequestPayload(UUID.randomUUID().toString(),
|
||||
compensationRequestDisplay.nameTextField.getText(),
|
||||
compensationRequestDisplay.titleTextField.getText(),
|
||||
compensationRequestDisplay.categoryTextField.getText(),
|
||||
compensationRequestDisplay.descriptionTextField.getText(),
|
||||
compensationRequestDisplay.linkTextField.getText(),
|
||||
startDate,
|
||||
endDate,
|
||||
btcFormatter.parseToCoin(compensationRequestDisplay.requestedBTCTextField.getText()),
|
||||
compensationRequestDisplay.btcAddressTextField.getText(),
|
||||
nodeAddress,
|
||||
p2pStorageSignaturePubKey
|
||||
);
|
||||
// TODO we neet to wait until all services are initiated before calling the code below
|
||||
checkNotNull(nodeAddress, "nodeAddress must not be null");
|
||||
CompensationRequestPayload compensationRequestPayload = new CompensationRequestPayload(UUID.randomUUID().toString(),
|
||||
compensationRequestDisplay.nameTextField.getText(),
|
||||
compensationRequestDisplay.titleTextField.getText(),
|
||||
compensationRequestDisplay.categoryTextField.getText(),
|
||||
compensationRequestDisplay.descriptionTextField.getText(),
|
||||
compensationRequestDisplay.linkTextField.getText(),
|
||||
startDate,
|
||||
endDate,
|
||||
btcFormatter.parseToCoin(compensationRequestDisplay.requestedBTCTextField.getText()),
|
||||
compensationRequestDisplay.btcAddressTextField.getText(),
|
||||
nodeAddress,
|
||||
p2pStorageSignaturePubKey
|
||||
);
|
||||
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Coin createCompensationRequestFee = feeService.getCreateCompensationRequestFee();
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedBurnFeeTx(createCompensationRequestFee);
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Coin createCompensationRequestFee = feeService.getCreateCompensationRequestFee();
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedBurnFeeTx(createCompensationRequestFee);
|
||||
|
||||
checkArgument(!preparedSendTx.getInputs().isEmpty(), "preparedSendTx inputs must not be empty");
|
||||
checkArgument(!preparedSendTx.getInputs().isEmpty(), "preparedSendTx inputs must not be empty");
|
||||
|
||||
// We use the key of the first BSQ input for signing the data
|
||||
TransactionOutput connectedOutput = preparedSendTx.getInputs().get(0).getConnectedOutput();
|
||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||
DeterministicKey bsqKeyPair = bsqWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||
checkNotNull(bsqKeyPair, "bsqKeyPair must not be null");
|
||||
// We use the key of the first BSQ input for signing the data
|
||||
TransactionOutput connectedOutput = preparedSendTx.getInputs().get(0).getConnectedOutput();
|
||||
checkNotNull(connectedOutput, "connectedOutput must not be null");
|
||||
DeterministicKey bsqKeyPair = bsqWalletService.findKeyFromPubKeyHash(connectedOutput.getScriptPubKey().getPubKeyHash());
|
||||
checkNotNull(bsqKeyPair, "bsqKeyPair must not be null");
|
||||
|
||||
// We get the JSON of the object excluding signature and feeTxId
|
||||
String payloadAsJson = StringUtils.deleteWhitespace(Utilities.objectToJson(compensationRequestPayload));
|
||||
log.error(payloadAsJson);
|
||||
// Signs a text message using the standard Bitcoin messaging signing format and returns the signature as a base64
|
||||
// encoded string.
|
||||
String signature = bsqKeyPair.signMessage(payloadAsJson);
|
||||
compensationRequestPayload.setSignature(signature);
|
||||
// We get the JSON of the object excluding signature and feeTxId
|
||||
String payloadAsJson = StringUtils.deleteWhitespace(Utilities.objectToJson(compensationRequestPayload));
|
||||
log.error(payloadAsJson);
|
||||
// Signs a text message using the standard Bitcoin messaging signing format and returns the signature as a base64
|
||||
// encoded string.
|
||||
String signature = bsqKeyPair.signMessage(payloadAsJson);
|
||||
compensationRequestPayload.setSignature(signature);
|
||||
|
||||
String dataAndSig = payloadAsJson + signature;
|
||||
byte[] dataAndSigAsBytes = dataAndSig.getBytes();
|
||||
outputStream.write(DaoConstants.OP_RETURN_TYPE_COMPENSATION_REQUEST);
|
||||
outputStream.write(Version.COMPENSATION_REQUEST_VERSION);
|
||||
outputStream.write(Utils.sha256hash160(dataAndSigAsBytes));
|
||||
byte hash[] = outputStream.toByteArray();
|
||||
//TODO should we store the hash in the compensationRequestPayload object?
|
||||
String dataAndSig = payloadAsJson + signature;
|
||||
byte[] dataAndSigAsBytes = dataAndSig.getBytes();
|
||||
outputStream.write(DaoConstants.OP_RETURN_TYPE_COMPENSATION_REQUEST);
|
||||
outputStream.write(Version.COMPENSATION_REQUEST_VERSION);
|
||||
outputStream.write(Utils.sha256hash160(dataAndSigAsBytes));
|
||||
byte hash[] = outputStream.toByteArray();
|
||||
//TODO should we store the hash in the compensationRequestPayload object?
|
||||
|
||||
|
||||
//TODO 1 Btc output (small payment to own compensation receiving address)
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedSendTx, false, hash);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
int txSize = signedTx.bitcoinSerialize().length;
|
||||
new Popup<>().headLine(Res.get("dao.compensation.create.confirm"))
|
||||
.confirmation(Res.get("dao.compensation.create.confirm.info",
|
||||
btcFormatter.formatCoinWithCode(createCompensationRequestFee),
|
||||
btcFormatter.formatCoinWithCode(miningFee),
|
||||
CoinUtil.getFeePerByte(miningFee, txSize),
|
||||
(txSize / 1000d)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
bsqWalletService.commitTx(txWithBtcFee);
|
||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
checkNotNull(transaction, "Transaction must not be null at broadcastTx callback.");
|
||||
compensationRequestPayload.setFeeTxId(transaction.getHashAsString());
|
||||
compensationRequestManager.addToP2PNetwork(compensationRequestPayload);
|
||||
compensationRequestDisplay.clearForm();
|
||||
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
|
||||
}
|
||||
//TODO 1 Btc output (small payment to own compensation receiving address)
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedSendTx, false, hash);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
int txSize = signedTx.bitcoinSerialize().length;
|
||||
new Popup<>().headLine(Res.get("dao.compensation.create.confirm"))
|
||||
.confirmation(Res.get("dao.compensation.create.confirm.info",
|
||||
btcFormatter.formatCoinWithCode(createCompensationRequestFee),
|
||||
btcFormatter.formatCoinWithCode(miningFee),
|
||||
CoinUtil.getFeePerByte(miningFee, txSize),
|
||||
(txSize / 1000d)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
bsqWalletService.commitTx(txWithBtcFee);
|
||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error(t.toString());
|
||||
new Popup<>().warning(t.toString()).show();
|
||||
}
|
||||
});
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} catch (IOException | TransactionVerificationException | WalletException |
|
||||
InsufficientMoneyException | ChangeBelowDustException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
checkNotNull(transaction, "Transaction must not be null at broadcastTx callback.");
|
||||
compensationRequestPayload.setFeeTxId(transaction.getHashAsString());
|
||||
compensationRequestManager.addToP2PNetwork(compensationRequestPayload);
|
||||
compensationRequestDisplay.clearForm();
|
||||
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error(t.toString());
|
||||
new Popup<>().warning(t.toString()).show();
|
||||
}
|
||||
}, 15);
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} catch (IOException | TransactionVerificationException | WalletException |
|
||||
InsufficientMoneyException | ChangeBelowDustException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
}
|
||||
} else {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import io.bisq.core.btc.exceptions.WalletException;
|
|||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.ChangeBelowDustException;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.dao.compensation.CompensationRequest;
|
||||
import io.bisq.core.dao.compensation.CompensationRequestManager;
|
||||
import io.bisq.core.dao.vote.*;
|
||||
|
@ -35,8 +36,9 @@ import io.bisq.gui.common.view.FxmlView;
|
|||
import io.bisq.gui.components.TitledGroupBg;
|
||||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.gui.util.Layout;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
@ -73,6 +75,8 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
|||
private final CompensationRequestManager compensationRequestManager;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final P2PService p2PService;
|
||||
private final FeeService feeService;
|
||||
private final BSFormatter btcFormatter;
|
||||
private final VotingManager voteManager;
|
||||
|
@ -90,12 +94,18 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private VoteView(CompensationRequestManager compensationRequestManager, BsqWalletService bsqWalletService,
|
||||
BtcWalletService btcWalletService, FeeService feeService, BsqFormatter bsqFormatter,
|
||||
private VoteView(CompensationRequestManager compensationRequestManager,
|
||||
BsqWalletService bsqWalletService,
|
||||
BtcWalletService btcWalletService,
|
||||
WalletsSetup walletsSetup,
|
||||
P2PService p2PService,
|
||||
FeeService feeService,
|
||||
BSFormatter btcFormatter, VotingManager voteManager) {
|
||||
this.compensationRequestManager = compensationRequestManager;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.p2PService = p2PService;
|
||||
this.feeService = feeService;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.voteManager = voteManager;
|
||||
|
@ -198,65 +208,71 @@ public class VoteView extends ActivatableView<GridPane, Void> {
|
|||
voteButton.visibleProperty().bind(voteButton.managedProperty());
|
||||
|
||||
voteButton.setOnAction(event -> {
|
||||
log.error(voteItemsList.toString());
|
||||
//TODO
|
||||
if (voteItemsList.isMyVote()) {
|
||||
new Popup<>().warning(Res.get("dao.voting.votedAlready")).show();
|
||||
} else if (!voteItemsList.getAllVoteItemList().stream().filter(VoteItem::isHasVoted).findAny().isPresent() &&
|
||||
!voteItemsList.getAllVoteItemList().stream().filter(e -> e instanceof CompensationRequestVoteItemCollection)
|
||||
.filter(e -> ((CompensationRequestVoteItemCollection) e).hasVotedOnAnyItem()).findAny().isPresent()) {
|
||||
new Popup<>().warning(Res.get("dao.voting.notVotedOnAnyEntry")).show();
|
||||
} else {
|
||||
try {
|
||||
byte[] opReturnData = voteManager.calculateOpReturnData(voteItemsList);
|
||||
// TODO break up in methods
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
log.info(voteItemsList.toString());
|
||||
//TODO
|
||||
if (voteItemsList.isMyVote()) {
|
||||
new Popup<>().warning(Res.get("dao.voting.votedAlready")).show();
|
||||
} else if (!voteItemsList.getAllVoteItemList().stream().filter(VoteItem::isHasVoted).findAny().isPresent() &&
|
||||
!voteItemsList.getAllVoteItemList().stream().filter(e -> e instanceof CompensationRequestVoteItemCollection)
|
||||
.filter(e -> ((CompensationRequestVoteItemCollection) e).hasVotedOnAnyItem()).findAny().isPresent()) {
|
||||
new Popup<>().warning(Res.get("dao.voting.notVotedOnAnyEntry")).show();
|
||||
} else {
|
||||
try {
|
||||
Coin votingTxFee = feeService.getVotingTxFee();
|
||||
Transaction preparedVotingTx = bsqWalletService.getPreparedBurnFeeTx(votingTxFee);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedVotingTx, false, opReturnData);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
int txSize = signedTx.bitcoinSerialize().length;
|
||||
new Popup<>().headLine(Res.get("dao.voting.confirmTx"))
|
||||
.confirmation(Res.get("dao.tx.summary",
|
||||
btcFormatter.formatCoinWithCode(votingTxFee),
|
||||
btcFormatter.formatCoinWithCode(miningFee),
|
||||
CoinUtil.getFeePerByte(miningFee, txSize),
|
||||
(txSize / 1000d)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
bsqWalletService.commitTx(txWithBtcFee);
|
||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
checkNotNull(transaction, "Transaction must not be null at doSend callback.");
|
||||
log.error("tx successful published" + transaction.getHashAsString());
|
||||
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
|
||||
voteItemsList.setIsMyVote(true);
|
||||
byte[] opReturnData = voteManager.calculateOpReturnData(voteItemsList);
|
||||
try {
|
||||
Coin votingTxFee = feeService.getVotingTxFee();
|
||||
Transaction preparedVotingTx = bsqWalletService.getPreparedBurnFeeTx(votingTxFee);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedBsqTx(preparedVotingTx, false, opReturnData);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
int txSize = signedTx.bitcoinSerialize().length;
|
||||
new Popup<>().headLine(Res.get("dao.voting.confirmTx"))
|
||||
.confirmation(Res.get("dao.tx.summary",
|
||||
btcFormatter.formatCoinWithCode(votingTxFee),
|
||||
btcFormatter.formatCoinWithCode(miningFee),
|
||||
CoinUtil.getFeePerByte(miningFee, txSize),
|
||||
(txSize / 1000d)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
bsqWalletService.commitTx(txWithBtcFee);
|
||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
|
||||
|
||||
//TODO send to P2P network
|
||||
}
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
checkNotNull(transaction, "Transaction must not be null at doSend callback.");
|
||||
log.error("tx successful published" + transaction.getHashAsString());
|
||||
new Popup<>().confirmation(Res.get("dao.tx.published.success")).show();
|
||||
voteItemsList.setIsMyVote(true);
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
new Popup<>().warning(t.toString()).show();
|
||||
}
|
||||
});
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} catch (InsufficientMoneyException | WalletException | TransactionVerificationException |
|
||||
ChangeBelowDustException e) {
|
||||
log.error(e.toString());
|
||||
//TODO send to P2P network
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
new Popup<>().warning(t.toString()).show();
|
||||
}
|
||||
}, 15);
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} catch (InsufficientMoneyException | WalletException | TransactionVerificationException |
|
||||
ChangeBelowDustException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
new Popup<>().error(e.toString()).show();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
new Popup<>().error(e.toString()).show();
|
||||
}
|
||||
} else {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import io.bisq.core.btc.Restrictions;
|
|||
import io.bisq.core.btc.wallet.BsqBalanceListener;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.util.CoinUtil;
|
||||
import io.bisq.gui.Navigation;
|
||||
import io.bisq.gui.common.view.ActivatableView;
|
||||
|
@ -35,9 +36,11 @@ import io.bisq.gui.main.funds.deposit.DepositView;
|
|||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.BsqFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.gui.util.Layout;
|
||||
import io.bisq.gui.util.validation.BsqAddressValidator;
|
||||
import io.bisq.gui.util.validation.BsqValidator;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
@ -55,6 +58,8 @@ import static io.bisq.gui.util.FormBuilder.*;
|
|||
public class BsqSendView extends ActivatableView<GridPane, Void> {
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final P2PService p2PService;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
private final BSFormatter btcFormatter;
|
||||
private final Navigation navigation;
|
||||
|
@ -75,12 +80,20 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private BsqSendView(BsqWalletService bsqWalletService, BtcWalletService btcWalletService,
|
||||
BsqFormatter bsqFormatter, BSFormatter btcFormatter, Navigation navigation,
|
||||
BsqBalanceUtil bsqBalanceUtil, BsqValidator bsqValidator,
|
||||
private BsqSendView(BsqWalletService bsqWalletService,
|
||||
BtcWalletService btcWalletService,
|
||||
WalletsSetup walletsSetup,
|
||||
P2PService p2PService,
|
||||
BsqFormatter bsqFormatter,
|
||||
BSFormatter btcFormatter,
|
||||
Navigation navigation,
|
||||
BsqBalanceUtil bsqBalanceUtil,
|
||||
BsqValidator bsqValidator,
|
||||
BsqAddressValidator bsqAddressValidator) {
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.p2PService = p2PService;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.navigation = navigation;
|
||||
|
@ -112,62 +125,68 @@ public class BsqSendView extends ActivatableView<GridPane, Void> {
|
|||
sendButton = addButtonAfterGroup(root, ++gridRow, Res.get("dao.wallet.send.send"));
|
||||
|
||||
sendButton.setOnAction((event) -> {
|
||||
String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString();
|
||||
Coin receiverAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
|
||||
try {
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendTx(receiversAddressString, receiverAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
int txSize = signedTx.bitcoinSerialize().length;
|
||||
new Popup<>().headLine(Res.get("dao.wallet.send.sendFunds.headline"))
|
||||
.confirmation(Res.get("dao.wallet.send.sendFunds.details",
|
||||
bsqFormatter.formatCoinWithCode(receiverAmount),
|
||||
receiversAddressInputTextField.getText(),
|
||||
btcFormatter.formatCoinWithCode(miningFee),
|
||||
CoinUtil.getFeePerByte(miningFee, txSize),
|
||||
txSize / 1000d,
|
||||
bsqFormatter.formatCoinWithCode(receiverAmount)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
bsqWalletService.commitTx(txWithBtcFee);
|
||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
if (transaction != null) {
|
||||
log.debug("Successfully sent tx with id " + transaction.getHashAsString());
|
||||
// TODO break up in methods
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
String receiversAddressString = bsqFormatter.getAddressFromBsqAddress(receiversAddressInputTextField.getText()).toString();
|
||||
Coin receiverAmount = bsqFormatter.parseToCoin(amountInputTextField.getText());
|
||||
try {
|
||||
Transaction preparedSendTx = bsqWalletService.getPreparedSendTx(receiversAddressString, receiverAmount);
|
||||
Transaction txWithBtcFee = btcWalletService.completePreparedSendBsqTx(preparedSendTx, true);
|
||||
Transaction signedTx = bsqWalletService.signTx(txWithBtcFee);
|
||||
Coin miningFee = signedTx.getFee();
|
||||
int txSize = signedTx.bitcoinSerialize().length;
|
||||
new Popup<>().headLine(Res.get("dao.wallet.send.sendFunds.headline"))
|
||||
.confirmation(Res.get("dao.wallet.send.sendFunds.details",
|
||||
bsqFormatter.formatCoinWithCode(receiverAmount),
|
||||
receiversAddressInputTextField.getText(),
|
||||
btcFormatter.formatCoinWithCode(miningFee),
|
||||
CoinUtil.getFeePerByte(miningFee, txSize),
|
||||
txSize / 1000d,
|
||||
bsqFormatter.formatCoinWithCode(receiverAmount)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> {
|
||||
bsqWalletService.commitTx(txWithBtcFee);
|
||||
// We need to create another instance, otherwise the tx would trigger an invalid state exception
|
||||
// if it gets committed 2 times
|
||||
btcWalletService.commitTx(btcWalletService.getClonedTransaction(txWithBtcFee));
|
||||
|
||||
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction transaction) {
|
||||
if (transaction != null) {
|
||||
log.debug("Successfully sent tx with id " + transaction.getHashAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error(t.toString());
|
||||
new Popup<>().warning(t.toString());
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error(t.toString());
|
||||
new Popup<>().warning(t.toString());
|
||||
}
|
||||
}, 15);
|
||||
|
||||
receiversAddressInputTextField.setText("");
|
||||
amountInputTextField.setText("");
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof InsufficientMoneyException) {
|
||||
final Coin missingCoin = ((InsufficientMoneyException) t).missing;
|
||||
final String missing = missingCoin != null ? missingCoin.toFriendlyString() : "null";
|
||||
//noinspection unchecked
|
||||
new Popup<>().warning(Res.get("popup.warning.insufficientBtcFundsForBsqTx", missing))
|
||||
.actionButtonTextWithGoTo("navigation.funds.depositFunds")
|
||||
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
|
||||
receiversAddressInputTextField.setText("");
|
||||
amountInputTextField.setText("");
|
||||
})
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} else {
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
new Popup<>().warning(t.getMessage()).show();
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof InsufficientMoneyException) {
|
||||
final Coin missingCoin = ((InsufficientMoneyException) t).missing;
|
||||
final String missing = missingCoin != null ? missingCoin.toFriendlyString() : "null";
|
||||
//noinspection unchecked
|
||||
new Popup<>().warning(Res.get("popup.warning.insufficientBtcFundsForBsqTx", missing))
|
||||
.actionButtonTextWithGoTo("navigation.funds.depositFunds")
|
||||
.onAction(() -> navigation.navigateTo(MainView.class, FundsView.class, DepositView.class))
|
||||
.show();
|
||||
} else {
|
||||
log.error(t.toString());
|
||||
t.printStackTrace();
|
||||
new Popup<>().warning(t.getMessage()).show();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -319,6 +319,9 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
|
|||
.onAddAlertMessage(privateNotificationManager::sendPrivateNotificationMessageIfKeyIsValid)
|
||||
.show();
|
||||
}
|
||||
} else if (Utilities.isAltOrCtrlPressed(KeyCode.ENTER, event)) {
|
||||
if (selectedDispute != null && messagesInputBox.isVisible() && inputTextArea.isFocused())
|
||||
onTrySendMessage();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -422,6 +425,21 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
|
|||
contractWindow.show(dispute);
|
||||
}
|
||||
|
||||
private void onTrySendMessage() {
|
||||
if (p2PService.isBootstrapped()) {
|
||||
String text = inputTextArea.getText();
|
||||
if (!text.isEmpty()) {
|
||||
if (text.length() < 5_000) {
|
||||
onSendMessage(text, selectedDispute);
|
||||
} else {
|
||||
new Popup<>().information(Res.get("popup.warning.messageTooLong")).show();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSendMessage(String inputText, Dispute dispute) {
|
||||
if (disputeCommunicationMessage != null) {
|
||||
disputeCommunicationMessage.arrivedProperty().removeListener(arrivedPropertyListener);
|
||||
|
@ -624,18 +642,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
|
|||
|
||||
sendButton = new AutoTooltipButton(Res.get("support.send"));
|
||||
sendButton.setDefaultButton(true);
|
||||
sendButton.setOnAction(e -> {
|
||||
if (p2PService.isBootstrapped()) {
|
||||
String text = inputTextArea.getText();
|
||||
if (!text.isEmpty())
|
||||
if (text.length() < 5_000)
|
||||
onSendMessage(text, selectedDispute);
|
||||
else
|
||||
new Popup<>().information(Res.get("popup.warning.messageTooLong")).show();
|
||||
} else {
|
||||
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
|
||||
}
|
||||
});
|
||||
sendButton.setOnAction(e -> onTrySendMessage());
|
||||
inputTextAreaTextSubscription = EasyBind.subscribe(inputTextArea.textProperty(), t -> sendButton.setDisable(t.isEmpty()));
|
||||
|
||||
Button uploadButton = new AutoTooltipButton(Res.get("support.addAttachments"));
|
||||
|
|
|
@ -26,6 +26,7 @@ import io.bisq.common.util.Utilities;
|
|||
import io.bisq.core.arbitration.DisputeManager;
|
||||
import io.bisq.core.btc.wallet.BsqWalletService;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.offer.OpenOffer;
|
||||
import io.bisq.core.offer.OpenOfferManager;
|
||||
import io.bisq.core.trade.Tradable;
|
||||
|
@ -45,6 +46,7 @@ import io.bisq.gui.main.overlays.windows.OfferDetailsWindow;
|
|||
import io.bisq.gui.main.overlays.windows.TradeDetailsWindow;
|
||||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
@ -88,6 +90,8 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final TradeManager tradeManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
|
@ -108,14 +112,24 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private TransactionsView(BtcWalletService btcWalletService, BsqWalletService bsqWalletService,
|
||||
TradeManager tradeManager, OpenOfferManager openOfferManager,
|
||||
ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager,
|
||||
BSFormatter formatter, Preferences preferences, TradeDetailsWindow tradeDetailsWindow,
|
||||
DisputeManager disputeManager, Stage stage,
|
||||
private TransactionsView(BtcWalletService btcWalletService,
|
||||
BsqWalletService bsqWalletService,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
TradeManager tradeManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FailedTradesManager failedTradesManager,
|
||||
BSFormatter formatter,
|
||||
Preferences preferences,
|
||||
TradeDetailsWindow tradeDetailsWindow,
|
||||
DisputeManager disputeManager,
|
||||
Stage stage,
|
||||
OfferDetailsWindow offerDetailsWindow) {
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.tradeManager = tradeManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
|
@ -567,15 +581,19 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
|
|||
}
|
||||
|
||||
private void revertTransaction(String txId, @Nullable Tradable tradable) {
|
||||
try {
|
||||
btcWalletService.doubleSpendTransaction(txId, () -> {
|
||||
if (tradable != null)
|
||||
btcWalletService.swapAnyTradeEntryContextToAvailableEntry(tradable.getId());
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
try {
|
||||
btcWalletService.doubleSpendTransaction(txId, () -> {
|
||||
if (tradable != null)
|
||||
btcWalletService.swapAnyTradeEntryContextToAvailableEntry(tradable.getId());
|
||||
|
||||
new Popup<>().information(Res.get("funds.tx.txSent")).show();
|
||||
}, errorMessage -> new Popup<>().warning(errorMessage).show());
|
||||
} catch (Throwable e) {
|
||||
new Popup<>().warning(e.getMessage()).show();
|
||||
new Popup<>().information(Res.get("funds.tx.txSent")).show();
|
||||
}, errorMessage -> new Popup<>().warning(errorMessage).show());
|
||||
} catch (Throwable e) {
|
||||
new Popup<>().warning(e.getMessage()).show();
|
||||
}
|
||||
} else {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import io.bisq.core.btc.InsufficientFundsException;
|
|||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.core.btc.listeners.BalanceListener;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.trade.Tradable;
|
||||
import io.bisq.core.trade.Trade;
|
||||
import io.bisq.core.trade.TradeManager;
|
||||
|
@ -43,6 +44,7 @@ import io.bisq.gui.main.overlays.windows.WalletPasswordWindow;
|
|||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.gui.util.validation.BtcAddressValidator;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
@ -90,6 +92,8 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
private final TradeManager tradeManager;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final FailedTradesManager failedTradesManager;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final BSFormatter formatter;
|
||||
private final Preferences preferences;
|
||||
private final BtcAddressValidator btcAddressValidator;
|
||||
|
@ -119,13 +123,18 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
TradeManager tradeManager,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FailedTradesManager failedTradesManager,
|
||||
BSFormatter formatter, Preferences preferences,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
BSFormatter formatter,
|
||||
Preferences preferences,
|
||||
BtcAddressValidator btcAddressValidator,
|
||||
WalletPasswordWindow walletPasswordWindow) {
|
||||
this.walletService = walletService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.failedTradesManager = failedTradesManager;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.formatter = formatter;
|
||||
this.preferences = preferences;
|
||||
this.btcAddressValidator = btcAddressValidator;
|
||||
|
@ -253,72 +262,76 @@ public class WithdrawalView extends ActivatableView<VBox, Void> {
|
|||
|
||||
@FXML
|
||||
public void onWithdraw() {
|
||||
try {
|
||||
// We do not know sendersAmount if senderPaysFee is true. We repeat fee calculation after first attempt if senderPaysFee is true.
|
||||
Transaction feeEstimationTransaction = walletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin);
|
||||
if (feeExcluded && feeEstimationTransaction != null) {
|
||||
sendersAmount = amountAsCoin.add(feeEstimationTransaction.getFee());
|
||||
feeEstimationTransaction = walletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, sendersAmount);
|
||||
}
|
||||
checkNotNull(feeEstimationTransaction, "feeEstimationTransaction must not be null");
|
||||
Coin fee = feeEstimationTransaction.getFee();
|
||||
sendersAmount = feeExcluded ? amountAsCoin.add(fee) : amountAsCoin;
|
||||
Coin receiverAmount = feeExcluded ? amountAsCoin : amountAsCoin.subtract(fee);
|
||||
if (areInputsValid()) {
|
||||
int txSize = feeEstimationTransaction.bitcoinSerialize().length;
|
||||
log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txSize, fee.toPlainString());
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
try {
|
||||
// We do not know sendersAmount if senderPaysFee is true. We repeat fee calculation after first attempt if senderPaysFee is true.
|
||||
Transaction feeEstimationTransaction = walletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, amountAsCoin);
|
||||
if (feeExcluded && feeEstimationTransaction != null) {
|
||||
sendersAmount = amountAsCoin.add(feeEstimationTransaction.getFee());
|
||||
feeEstimationTransaction = walletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, sendersAmount);
|
||||
}
|
||||
checkNotNull(feeEstimationTransaction, "feeEstimationTransaction must not be null");
|
||||
Coin fee = feeEstimationTransaction.getFee();
|
||||
sendersAmount = feeExcluded ? amountAsCoin.add(fee) : amountAsCoin;
|
||||
Coin receiverAmount = feeExcluded ? amountAsCoin : amountAsCoin.subtract(fee);
|
||||
if (areInputsValid()) {
|
||||
int txSize = feeEstimationTransaction.bitcoinSerialize().length;
|
||||
log.info("Fee for tx with size {}: {} " + Res.getBaseCurrencyCode() + "", txSize, fee.toPlainString());
|
||||
|
||||
if (receiverAmount.isPositive()) {
|
||||
double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
|
||||
double kb = txSize / 1000d;
|
||||
new Popup<>().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest"))
|
||||
.confirmation(Res.get("shared.sendFundsDetailsWithFee",
|
||||
formatter.formatCoinWithCode(sendersAmount),
|
||||
withdrawFromTextField.getText(),
|
||||
withdrawToTextField.getText(),
|
||||
formatter.formatCoinWithCode(fee),
|
||||
feePerByte,
|
||||
kb,
|
||||
formatter.formatCoinWithCode(receiverAmount)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> doWithdraw(sendersAmount, fee, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
|
||||
if (transaction != null) {
|
||||
log.debug("onWithdraw onSuccess tx ID:" + transaction.getHashAsString());
|
||||
} else {
|
||||
log.error("onWithdraw transaction is null");
|
||||
if (receiverAmount.isPositive()) {
|
||||
double feePerByte = CoinUtil.getFeePerByte(fee, txSize);
|
||||
double kb = txSize / 1000d;
|
||||
new Popup<>().headLine(Res.get("funds.withdrawal.confirmWithdrawalRequest"))
|
||||
.confirmation(Res.get("shared.sendFundsDetailsWithFee",
|
||||
formatter.formatCoinWithCode(sendersAmount),
|
||||
withdrawFromTextField.getText(),
|
||||
withdrawToTextField.getText(),
|
||||
formatter.formatCoinWithCode(fee),
|
||||
feePerByte,
|
||||
kb,
|
||||
formatter.formatCoinWithCode(receiverAmount)))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(() -> doWithdraw(sendersAmount, fee, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@javax.annotation.Nullable Transaction transaction) {
|
||||
if (transaction != null) {
|
||||
log.debug("onWithdraw onSuccess tx ID:" + transaction.getHashAsString());
|
||||
} else {
|
||||
log.error("onWithdraw transaction is null");
|
||||
}
|
||||
|
||||
List<Trade> trades = new ArrayList<>(tradeManager.getTradableList());
|
||||
trades.stream()
|
||||
.filter(Trade::isPayoutPublished)
|
||||
.forEach(trade -> {
|
||||
walletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT)
|
||||
.ifPresent(addressEntry -> {
|
||||
if (walletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
|
||||
tradeManager.addTradeToClosedTrades(trade);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
List<Trade> trades = new ArrayList<>(tradeManager.getTradableList());
|
||||
trades.stream()
|
||||
.filter(Trade::isPayoutPublished)
|
||||
.forEach(trade -> {
|
||||
walletService.getAddressEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT)
|
||||
.ifPresent(addressEntry -> {
|
||||
if (walletService.getBalanceForAddress(addressEntry.getAddress()).isZero())
|
||||
tradeManager.addTradeToClosedTrades(trade);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error("onWithdraw onFailure");
|
||||
}
|
||||
}))
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} else {
|
||||
new Popup<>().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show();
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error("onWithdraw onFailure");
|
||||
}
|
||||
}))
|
||||
.closeButtonText(Res.get("shared.cancel"))
|
||||
.show();
|
||||
} else {
|
||||
new Popup<>().warning(Res.get("portfolio.pending.step5_buyer.amountTooLow")).show();
|
||||
}
|
||||
}
|
||||
} catch (InsufficientFundsException e) {
|
||||
new Popup<>().warning(Res.get("funds.withdrawal.warn.amountExceeds") + "\n\nError message:\n" + e.getMessage()).show();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.toString());
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
}
|
||||
} catch (InsufficientFundsException e) {
|
||||
new Popup<>().warning(Res.get("funds.withdrawal.warn.amountExceeds")+ "\n\nError message:\n" + e.getMessage()).show();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
log.error(e.toString());
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
} else {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -301,7 +301,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void onPlaceOffer() {
|
||||
if (model.isBootstrapped()) {
|
||||
if (model.isReadyForTxBroadcast()) {
|
||||
if (model.dataModel.isMakerFeeValid()) {
|
||||
if (model.hasAcceptedArbitrators()) {
|
||||
Offer offer = model.createAndGetOffer();
|
||||
|
@ -329,7 +329,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
|
|||
showInsufficientBsqFundsForBtcFeePaymentPopup();
|
||||
}
|
||||
} else {
|
||||
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
|
||||
model.showNotReadyForTxBroadcastPopups();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import io.bisq.common.monetary.Volume;
|
|||
import io.bisq.common.util.MathUtils;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.offer.Offer;
|
||||
import io.bisq.core.offer.OfferPayload;
|
||||
import io.bisq.core.payment.PaymentAccount;
|
||||
|
@ -64,6 +65,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
private final BsqValidator bsqValidator;
|
||||
private final SecurityDepositValidator securityDepositValidator;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final PriceFeedService priceFeedService;
|
||||
private final Navigation navigation;
|
||||
private final BSFormatter btcFormatter;
|
||||
|
@ -159,6 +161,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
BsqValidator bsqValidator,
|
||||
SecurityDepositValidator securityDepositValidator,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
PriceFeedService priceFeedService,
|
||||
Navigation navigation,
|
||||
BSFormatter btcFormatter,
|
||||
|
@ -172,6 +175,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
this.bsqValidator = bsqValidator;
|
||||
this.securityDepositValidator = securityDepositValidator;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.navigation = navigation;
|
||||
this.btcFormatter = btcFormatter;
|
||||
|
@ -193,8 +197,8 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
UserThread.runAfter(() -> {
|
||||
switch (BisqEnvironment.getBaseCurrencyNetwork().getCurrencyCode()) {
|
||||
case "BTC":
|
||||
amount.set("0.1");
|
||||
price.set("0.0001");
|
||||
amount.set("0.0001");
|
||||
price.set("14029");
|
||||
break;
|
||||
case "LTC":
|
||||
amount.set("50");
|
||||
|
@ -885,8 +889,12 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
|
|||
return dataModel.hasAcceptedArbitrators();
|
||||
}
|
||||
|
||||
boolean isBootstrapped() {
|
||||
return p2PService.isBootstrapped();
|
||||
boolean isReadyForTxBroadcast() {
|
||||
return GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup);
|
||||
}
|
||||
|
||||
void showNotReadyForTxBroadcastPopups() {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -53,10 +53,30 @@ public class OfferBook {
|
|||
offerBookService.addOfferBookChangedListener(new OfferBookService.OfferBookChangedListener() {
|
||||
@Override
|
||||
public void onAdded(Offer offer) {
|
||||
OfferBookListItem offerBookListItem = new OfferBookListItem(offer);
|
||||
if (!isOfferWithIdInList(offer)) {
|
||||
// We get onAdded called every time a new ProtectedStorageEntry is received.
|
||||
// Mostly it is the same OfferPayload but the ProtectedStorageEntry is different.
|
||||
// We filter here to only add new offers if the same offer (using equals) was not already added.
|
||||
boolean hasSameOffer = offerBookListItems.stream()
|
||||
.filter(item -> item.getOffer().equals(offer))
|
||||
.findAny()
|
||||
.isPresent();
|
||||
if (!hasSameOffer) {
|
||||
OfferBookListItem offerBookListItem = new OfferBookListItem(offer);
|
||||
// We don't use the contains method as the equals method in Offer takes state and errorMessage into account.
|
||||
// If we have an offer with same ID we remove it and add the new offer as it might have a changed state.
|
||||
Optional<OfferBookListItem> candidateWithSameId = offerBookListItems.stream()
|
||||
.filter(item -> item.getOffer().getId().equals(offer.getId()))
|
||||
.findAny();
|
||||
if (candidateWithSameId.isPresent()) {
|
||||
log.warn("We had an old offer in the list with the same Offer ID. Might be that the state or errorMessage was different. " +
|
||||
"old offerBookListItem={}, new offerBookListItem={}", candidateWithSameId.get(), offerBookListItem);
|
||||
offerBookListItems.remove(candidateWithSameId.get());
|
||||
}
|
||||
|
||||
offerBookListItems.add(offerBookListItem);
|
||||
Log.logIfStressTests("OfferPayload added: No. of offers = " + offerBookListItems.size());
|
||||
}else{
|
||||
log.debug("We have the exact same offer already in our list and ignore the onAdded call. ID={}", offer.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,15 +87,13 @@ public class OfferBook {
|
|||
|
||||
// clean up possible references in openOfferManager
|
||||
tradeManager.onOfferRemovedFromRemoteOfferBook(offer);
|
||||
Optional<OfferBookListItem> candidate = offerBookListItems.stream()
|
||||
// We don't use the contains method as the equals method in Offer takes state and errorMessage into account.
|
||||
Optional<OfferBookListItem> candidateToRemove = offerBookListItems.stream()
|
||||
.filter(item -> item.getOffer().getId().equals(offer.getId()))
|
||||
.findAny();
|
||||
if (candidate.isPresent()) {
|
||||
OfferBookListItem item = candidate.get();
|
||||
if (offerBookListItems.contains(item)) {
|
||||
offerBookListItems.remove(item);
|
||||
Log.logIfStressTests("OfferPayload removed: No. of offers = " + offerBookListItems.size());
|
||||
}
|
||||
if (candidateToRemove.isPresent()) {
|
||||
offerBookListItems.remove(candidateToRemove.get());
|
||||
Log.logIfStressTests("OfferPayload removed: No. of offers = " + offerBookListItems.size());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -91,8 +109,6 @@ public class OfferBook {
|
|||
}
|
||||
|
||||
public void fillOfferBookListItems() {
|
||||
log.debug("fillOfferBookListItems");
|
||||
|
||||
try {
|
||||
// setAll causes sometimes an UnsupportedOperationException
|
||||
// Investigate why....
|
||||
|
|
|
@ -26,6 +26,7 @@ import io.bisq.common.handlers.ResultHandler;
|
|||
import io.bisq.common.locale.*;
|
||||
import io.bisq.common.monetary.Price;
|
||||
import io.bisq.common.monetary.Volume;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.filter.FilterManager;
|
||||
import io.bisq.core.offer.Offer;
|
||||
import io.bisq.core.offer.OfferPayload;
|
||||
|
@ -72,6 +73,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
final PriceFeedService priceFeedService;
|
||||
private final ClosedTradableManager closedTradableManager;
|
||||
private final FilterManager filterManager;
|
||||
private final WalletsSetup walletsSetup;
|
||||
final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Navigation navigation;
|
||||
final BSFormatter formatter;
|
||||
|
@ -103,10 +105,18 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Inject
|
||||
public OfferBookViewModel(User user, OpenOfferManager openOfferManager, OfferBook offerBook,
|
||||
Preferences preferences, P2PService p2PService, PriceFeedService priceFeedService,
|
||||
ClosedTradableManager closedTradableManager, FilterManager filterManager,
|
||||
AccountAgeWitnessService accountAgeWitnessService, Navigation navigation, BSFormatter formatter) {
|
||||
public OfferBookViewModel(User user,
|
||||
OpenOfferManager openOfferManager,
|
||||
OfferBook offerBook,
|
||||
Preferences preferences,
|
||||
P2PService p2PService,
|
||||
PriceFeedService priceFeedService,
|
||||
ClosedTradableManager closedTradableManager,
|
||||
FilterManager filterManager,
|
||||
WalletsSetup walletsSetup,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
Navigation navigation,
|
||||
BSFormatter formatter) {
|
||||
super();
|
||||
|
||||
this.openOfferManager = openOfferManager;
|
||||
|
@ -117,6 +127,7 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
this.priceFeedService = priceFeedService;
|
||||
this.closedTradableManager = closedTradableManager;
|
||||
this.filterManager = filterManager;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.navigation = navigation;
|
||||
this.formatter = formatter;
|
||||
|
@ -420,7 +431,11 @@ class OfferBookViewModel extends ActivatableViewModel {
|
|||
filteredItems.setPredicate(offerBookListItem -> {
|
||||
Offer offer = offerBookListItem.getOffer();
|
||||
boolean directionResult = offer.getDirection() != direction;
|
||||
boolean currencyResult = showAllTradeCurrenciesProperty.get() ||
|
||||
boolean isPreferredCurrency = getTradeCurrencies().stream()
|
||||
.filter(c -> c.getCode().equals(offer.getCurrencyCode()))
|
||||
.findAny()
|
||||
.isPresent();
|
||||
boolean currencyResult = (showAllTradeCurrenciesProperty.get() && isPreferredCurrency) ||
|
||||
offer.getCurrencyCode().equals(selectedTradeCurrency.getCode());
|
||||
boolean paymentMethodResult = showAllPaymentMethods ||
|
||||
offer.getPaymentMethod().equals(selectedPaymentMethod);
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.bitcoinj.core.Address;
|
|||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
@ -103,8 +104,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
Coin totalAvailableBalance;
|
||||
private Notification walletFundedNotification;
|
||||
Price tradePrice;
|
||||
private int feeTxSize = 320; // 260 kb is size of typical trade fee tx with 1 input but trade tx (deposit and payout) are larger so we adjust to 320
|
||||
// 260 kb is size of typical trade fee tx with 1 input but trade tx (deposit and payout) are larger so we adjust to 320
|
||||
private int feeTxSize = 320;
|
||||
private int feeTxSizeEstimationRecursionCounter;
|
||||
private boolean freezeFee;
|
||||
private Coin txFeePerByteFromFeeService;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -187,12 +191,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
getBuyerSecurityDeposit() :
|
||||
getSellerSecurityDeposit();
|
||||
|
||||
// We request to get the actual estimated fee
|
||||
requestTxFee();
|
||||
|
||||
|
||||
// Taker pays 2 times the tx fee because the mining fee might be different when maker created the offer
|
||||
// and reserved his funds, so that would not work well with dynamic fees.
|
||||
// Taker pays 3 times the tx fee (taker fee, deposit, payout) because the mining fee might be different when maker created the offer
|
||||
// and reserved his funds. Taker creates at least taker fee and deposit tx at nearly the same moment. Just the payout will
|
||||
// be later and still could lead to issues if the required fee changed a lot in the meantime. using RBF and/or
|
||||
// multiple batch-signed payout tx with different fees might be an option but RBF is not supported yet in BitcoinJ
|
||||
// and batched txs would add more complexity to the trade protocol.
|
||||
|
||||
// A typical trade fee tx has about 260 bytes (if one input). The trade txs has about 336-414 bytes.
|
||||
// We use 320 as a average value.
|
||||
|
@ -204,7 +207,22 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
|
||||
// Set the default values (in rare cases if the fee request was not done yet we get the hard coded default values)
|
||||
// But the "take offer" happens usually after that so we should have already the value from the estimation service.
|
||||
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
|
||||
txFeePerByteFromFeeService = feeService.getTxFeePerByte();
|
||||
txFeeFromFeeService = getTxFeeBySize(feeTxSize);
|
||||
|
||||
// We request to get the actual estimated fee
|
||||
log.info("Start requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService);
|
||||
feeService.requestFees(() -> {
|
||||
if (!freezeFee) {
|
||||
txFeePerByteFromFeeService = feeService.getTxFeePerByte();
|
||||
txFeeFromFeeService = getTxFeeBySize(feeTxSize);
|
||||
calculateTotalToPay();
|
||||
log.info("Completed requestTxFee: txFeeFromFeeService={}", txFeeFromFeeService);
|
||||
} else {
|
||||
log.warn("We received the tx fee respnse after we have shown the funding screen and ignore that " +
|
||||
"to avoid that the total funds to pay changes due cahnged tx fees.");
|
||||
}
|
||||
}, null);
|
||||
|
||||
calculateVolume();
|
||||
calculateTotalToPay();
|
||||
|
@ -246,11 +264,11 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
priceFeedService.setCurrencyCode(offer.getCurrencyCode());
|
||||
}
|
||||
|
||||
void requestTxFee() {
|
||||
feeService.requestFees(() -> {
|
||||
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
|
||||
calculateTotalToPay();
|
||||
}, null);
|
||||
// We don't want that the fee gets updated anymore after we show the funding screen.
|
||||
void onShowPayFundsScreen() {
|
||||
estimateTxSize();
|
||||
freezeFee = true;
|
||||
calculateTotalToPay();
|
||||
}
|
||||
|
||||
void onTabSelected(boolean isSelected) {
|
||||
|
@ -306,57 +324,90 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
}
|
||||
|
||||
// This works only if have already funds in the wallet
|
||||
// TODO: There still are issues if we get funded by very small inputs which are causing higher tx fees and the
|
||||
// changed total required amount is not updated. That will cause a InsufficientMoneyException and the user need to
|
||||
// start over again. To reproduce keep adding 0.002 BTC amounts while in the funding screen.
|
||||
// It would require a listener on changed balance and a new fee estimation with a correct recalculation of the required funds.
|
||||
// Another edge case not handled correctly is: If there are many small inputs and user add a large input later the
|
||||
// fee estimation is based on the large tx with many inputs but the actual tx will get created with the large input, thus
|
||||
// leading to a smaller tx and too high fees. Simply updating the fee estimation would lead to changed required funds
|
||||
// and if funds get higher (if tx get larger) the user would get confused (adding small inputs would increase total required funds).
|
||||
// So that would require more thoughts how to deal with all those cases.
|
||||
public void estimateTxSize() {
|
||||
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
|
||||
Address fundingAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address changeAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
int txSize = 0;
|
||||
if (btcWalletService.getBalance(Wallet.BalanceType.AVAILABLE).isPositive()) {
|
||||
txFeeFromFeeService = getTxFeeBySize(feeTxSize);
|
||||
|
||||
Coin reservedFundsForOffer = getSecurityDeposit();
|
||||
if (!isBuyOffer())
|
||||
reservedFundsForOffer = reservedFundsForOffer.add(amount.get());
|
||||
Address reservedForTradeAddress = btcWalletService.getOrCreateAddressEntry(offer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
|
||||
Address changeAddress = btcWalletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
|
||||
checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null");
|
||||
checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty");
|
||||
String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress();
|
||||
try {
|
||||
log.info("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," +
|
||||
" txFee based on feeTxSize: {}, recommended txFee is {} sat/byte",
|
||||
feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize(
|
||||
fundingAddress,
|
||||
reservedForTradeAddress,
|
||||
changeAddress,
|
||||
reservedFundsForOffer,
|
||||
true,
|
||||
getTakerFee(),
|
||||
txFeeFromFeeService,
|
||||
dummyArbitratorAddress);
|
||||
Coin reservedFundsForOffer = getSecurityDeposit().add(txFeeFromFeeService).add(txFeeFromFeeService);
|
||||
if (isBuyOffer())
|
||||
reservedFundsForOffer = reservedFundsForOffer.add(amount.get());
|
||||
|
||||
final int txSize = tradeFeeTx.bitcoinSerialize().length;
|
||||
// use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop
|
||||
if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) {
|
||||
feeTxSizeEstimationRecursionCounter++;
|
||||
log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " +
|
||||
"adjusted txFee to reach the target tx fee.", txSize, feeTxSize);
|
||||
feeTxSize = txSize;
|
||||
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
|
||||
// lets try again with the adjusted txSize and fee.
|
||||
estimateTxSize();
|
||||
} else {
|
||||
log.info("feeTxSize {} bytes", feeTxSize);
|
||||
log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte",
|
||||
txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
checkNotNull(user.getAcceptedArbitrators(), "user.getAcceptedArbitrators() must not be null");
|
||||
checkArgument(!user.getAcceptedArbitrators().isEmpty(), "user.getAcceptedArbitrators() must not be empty");
|
||||
String dummyArbitratorAddress = user.getAcceptedArbitrators().get(0).getBtcAddress();
|
||||
try {
|
||||
log.debug("We create a dummy tx to see if our estimated size is in the accepted range. feeTxSize={}," +
|
||||
" txFee based on feeTxSize: {}, recommended txFee is {} sat/byte",
|
||||
feeTxSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
Transaction tradeFeeTx = tradeWalletService.estimateBtcTradingFeeTxSize(
|
||||
fundingAddress,
|
||||
reservedForTradeAddress,
|
||||
changeAddress,
|
||||
reservedFundsForOffer,
|
||||
true,
|
||||
getTakerFee(),
|
||||
txFeeFromFeeService,
|
||||
dummyArbitratorAddress);
|
||||
|
||||
txSize = tradeFeeTx.bitcoinSerialize().length;
|
||||
// use feeTxSizeEstimationRecursionCounter to avoid risk for endless loop
|
||||
// We use the tx size for the trade fee tx as target for the fees.
|
||||
// The deposit and payout txs are determined +/- 1 output but the trade fee tx can have either 1 or many inputs
|
||||
// so we need to make sure the trade fee tx gets the correct fee to not get stuck.
|
||||
// We use a 20% tolerance frm out default 320 byte size (typical for deposit and payout) and only if we get a
|
||||
// larger size we increase the fee. Worst case is that we overpay for the other follow up txs, but better than
|
||||
// use a too low fee and get stuck.
|
||||
if (txSize > feeTxSize * 1.2 && feeTxSizeEstimationRecursionCounter < 10) {
|
||||
feeTxSizeEstimationRecursionCounter++;
|
||||
log.info("txSize is {} bytes but feeTxSize used for txFee calculation was {} bytes. We try again with an " +
|
||||
"adjusted txFee to reach the target tx fee.", txSize, feeTxSize);
|
||||
|
||||
feeTxSize = txSize;
|
||||
txFeeFromFeeService = getTxFeeBySize(txSize);
|
||||
|
||||
// lets try again with the adjusted txSize and fee.
|
||||
estimateTxSize();
|
||||
} else {
|
||||
// We are done with estimation iterations
|
||||
if (feeTxSizeEstimationRecursionCounter < 10)
|
||||
log.info("Fee estimation completed:\n" +
|
||||
"txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)",
|
||||
feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
else
|
||||
log.warn("We could not estimate the fee as the feeTxSizeEstimationRecursionCounter exceeded our limit of 10 recursions.\n" +
|
||||
"txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. " +
|
||||
"TxFee is {} ({} sat/byte)",
|
||||
feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
}
|
||||
} catch (InsufficientMoneyException e) {
|
||||
log.info("We cannot complete the fee estimation because there are not enough funds in the wallet.\n" +
|
||||
"This is expected if the user has not sufficient funds yet.\n" +
|
||||
"In that case we use the latest estimated tx size or the default if none has been calculated yet.\n" +
|
||||
"txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)",
|
||||
feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
}
|
||||
} catch (InsufficientMoneyException e) {
|
||||
// If we need to fund from an external wallet we can assume we only have 1 input (260 bytes).
|
||||
log.warn("We cannot do the fee estimation because there are not enough funds in the wallet. This is expected " +
|
||||
"if the user pays from an external wallet. In that case we use an estimated tx size of 260 bytes.");
|
||||
feeTxSize = 260;
|
||||
txFeeFromFeeService = feeService.getTxFee(feeTxSize);
|
||||
log.info("feeTxSize {} bytes", feeTxSize);
|
||||
log.info("txFee based on estimated size: {}, recommended txFee is {} sat/byte",
|
||||
txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
} else {
|
||||
feeTxSize = 320;
|
||||
txFeeFromFeeService = getTxFeeBySize(feeTxSize);
|
||||
log.info("We cannot do the fee estimation because there are no funds in the wallet.\nThis is expected " +
|
||||
"if the user has not funded his wallet yet.\n" +
|
||||
"In that case we use an estimated tx size of 320 bytes.\n" +
|
||||
"txFee based on estimated size of {} bytes. Average tx size = {} bytes. Actual tx size = {} bytes. TxFee is {} ({} sat/byte)",
|
||||
feeTxSize, getAverageSize(feeTxSize), txSize, txFeeFromFeeService.toFriendlyString(), feeService.getTxFeePerByte());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,8 +423,10 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
void fundFromSavingsWallet() {
|
||||
useSavingsWallet = true;
|
||||
updateBalance();
|
||||
if (!isWalletFunded.get())
|
||||
if (!isWalletFunded.get()) {
|
||||
this.useSavingsWallet = false;
|
||||
updateBalance();
|
||||
}
|
||||
}
|
||||
|
||||
void setIsCurrencyForTakerFeeBtc(boolean isCurrencyForTakerFeeBtc) {
|
||||
|
@ -546,6 +599,18 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
btcWalletService.resetAddressEntriesForOpenOffer(offer.getId());
|
||||
}
|
||||
|
||||
// We use the sum of the size of the trade fee and the deposit tx to get an average.
|
||||
// Miners will take the trade fee tx if the total fee of both dependent txs are good enough.
|
||||
// With that we avoid that we overpay in case that the trade fee has many inputs and we would apply that fee for the
|
||||
// other 2 txs as well. We still might overpay a bit for the payout tx.
|
||||
private int getAverageSize(int txSize) {
|
||||
return (txSize + 320) / 2;
|
||||
}
|
||||
|
||||
private Coin getTxFeeBySize(int sizeInBytes) {
|
||||
return txFeePerByteFromFeeService.multiply(getAverageSize(sizeInBytes));
|
||||
}
|
||||
|
||||
/* private void setFeeFromFundingTx(Coin fee) {
|
||||
feeFromFundingTx = fee;
|
||||
isFeeFromFundingTxSufficient.set(feeFromFundingTx.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0);
|
||||
|
@ -567,13 +632,17 @@ class TakeOfferDataModel extends ActivatableDataModel {
|
|||
|
||||
boolean wouldCreateDustForMaker() {
|
||||
//noinspection SimplifiableIfStatement
|
||||
boolean result;
|
||||
if (amount.get() != null && offer != null) {
|
||||
Coin customAmount = offer.getAmount().subtract(amount.get());
|
||||
Coin dustAndFee = getTotalTxFee().add(Restrictions.getMinNonDustOutput());
|
||||
return customAmount.isPositive() && customAmount.isLessThan(dustAndFee);
|
||||
result = customAmount.isPositive() && customAmount.isLessThan(Restrictions.getMinNonDustOutput());
|
||||
|
||||
if (result)
|
||||
log.info("would create dust for maker, customAmount={}, Restrictions.getMinNonDustOutput()={}", customAmount, Restrictions.getMinNonDustOutput());
|
||||
} else {
|
||||
return true;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ReadOnlyObjectProperty<Coin> getAmount() {
|
||||
|
|
|
@ -327,34 +327,38 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
|
|||
|
||||
@SuppressWarnings("PointlessBooleanExpression")
|
||||
private void onTakeOffer() {
|
||||
if (model.dataModel.isTakerFeeValid()) {
|
||||
if (model.hasAcceptedArbitrators()) {
|
||||
if (!DevEnv.DEV_MODE) {
|
||||
offerDetailsWindow.onTakeOffer(() ->
|
||||
model.onTakeOffer(() -> {
|
||||
offerDetailsWindow.hide();
|
||||
offerDetailsWindowDisplayed = false;
|
||||
})
|
||||
).show(model.getOffer(), model.dataModel.getAmount().get(), model.dataModel.tradePrice);
|
||||
offerDetailsWindowDisplayed = true;
|
||||
if (model.isReadyForTxBroadcast()) {
|
||||
if (model.dataModel.isTakerFeeValid()) {
|
||||
if (model.hasAcceptedArbitrators()) {
|
||||
if (!DevEnv.DEV_MODE) {
|
||||
offerDetailsWindow.onTakeOffer(() ->
|
||||
model.onTakeOffer(() -> {
|
||||
offerDetailsWindow.hide();
|
||||
offerDetailsWindowDisplayed = false;
|
||||
})
|
||||
).show(model.getOffer(), model.dataModel.getAmount().get(), model.dataModel.tradePrice);
|
||||
offerDetailsWindowDisplayed = true;
|
||||
} else {
|
||||
balanceSubscription.unsubscribe();
|
||||
model.onTakeOffer(() -> {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
balanceSubscription.unsubscribe();
|
||||
model.onTakeOffer(() -> {
|
||||
});
|
||||
new Popup<>().headLine(Res.get("popup.warning.noArbitratorSelected.headline"))
|
||||
.instruction(Res.get("popup.warning.noArbitratorSelected.msg"))
|
||||
.actionButtonTextWithGoTo("navigation.arbitratorSelection")
|
||||
.onAction(() -> {
|
||||
navigation.setReturnPath(navigation.getCurrentPath());
|
||||
//noinspection unchecked
|
||||
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class,
|
||||
ArbitratorSelectionView.class);
|
||||
}).show();
|
||||
}
|
||||
} else {
|
||||
new Popup<>().headLine(Res.get("popup.warning.noArbitratorSelected.headline"))
|
||||
.instruction(Res.get("popup.warning.noArbitratorSelected.msg"))
|
||||
.actionButtonTextWithGoTo("navigation.arbitratorSelection")
|
||||
.onAction(() -> {
|
||||
navigation.setReturnPath(navigation.getCurrentPath());
|
||||
//noinspection unchecked
|
||||
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class,
|
||||
ArbitratorSelectionView.class);
|
||||
}).show();
|
||||
showInsufficientBsqFundsForBtcFeePaymentPopup();
|
||||
}
|
||||
} else {
|
||||
showInsufficientBsqFundsForBtcFeePaymentPopup();
|
||||
model.showNotReadyForTxBroadcastPopups();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ package io.bisq.gui.main.offer.takeoffer;
|
|||
|
||||
import io.bisq.common.app.DevEnv;
|
||||
import io.bisq.common.locale.Res;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.offer.Offer;
|
||||
import io.bisq.core.offer.OfferPayload;
|
||||
import io.bisq.core.payment.PaymentAccount;
|
||||
|
@ -55,6 +56,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
final TakeOfferDataModel dataModel;
|
||||
private final BtcValidator btcValidator;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final Navigation navigation;
|
||||
private final BSFormatter btcFormatter;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
|
@ -111,6 +113,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
public TakeOfferViewModel(TakeOfferDataModel dataModel,
|
||||
BtcValidator btcValidator,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
Navigation navigation,
|
||||
BSFormatter btcFormatter,
|
||||
BsqFormatter bsqFormatter) {
|
||||
|
@ -119,6 +122,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
|
||||
this.btcValidator = btcValidator;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.navigation = navigation;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
|
@ -216,8 +220,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
}
|
||||
|
||||
public void onShowPayFundsScreen() {
|
||||
dataModel.estimateTxSize();
|
||||
dataModel.requestTxFee();
|
||||
dataModel.onShowPayFundsScreen();
|
||||
showPayFundsScreenDisplayed.set(true);
|
||||
updateSpinnerInfo();
|
||||
}
|
||||
|
@ -618,6 +621,14 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
|||
return dataModel.hasAcceptedArbitrators();
|
||||
}
|
||||
|
||||
boolean isReadyForTxBroadcast() {
|
||||
return GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup);
|
||||
}
|
||||
|
||||
void showNotReadyForTxBroadcastPopups() {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
|
||||
public void resetOfferWarning() {
|
||||
offerWarning.set(null);
|
||||
}
|
||||
|
|
|
@ -22,13 +22,16 @@ import io.bisq.common.locale.Res;
|
|||
import io.bisq.common.util.Tuple2;
|
||||
import io.bisq.core.btc.Restrictions;
|
||||
import io.bisq.core.btc.wallet.WalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.offer.OpenOfferManager;
|
||||
import io.bisq.gui.components.AutoTooltipButton;
|
||||
import io.bisq.gui.components.InputTextField;
|
||||
import io.bisq.gui.main.overlays.Overlay;
|
||||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.BSFormatter;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.gui.util.Transitions;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -52,6 +55,8 @@ import static io.bisq.gui.util.FormBuilder.*;
|
|||
public class EmptyWalletWindow extends Overlay<EmptyWalletWindow> {
|
||||
private static final Logger log = LoggerFactory.getLogger(EmptyWalletWindow.class);
|
||||
private final WalletPasswordWindow walletPasswordWindow;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
private final BSFormatter formatter;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
|
||||
|
@ -67,9 +72,14 @@ public class EmptyWalletWindow extends Overlay<EmptyWalletWindow> {
|
|||
|
||||
@Inject
|
||||
public EmptyWalletWindow(WalletPasswordWindow walletPasswordWindow,
|
||||
OpenOfferManager openOfferManager, BSFormatter formatter) {
|
||||
OpenOfferManager openOfferManager,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
BSFormatter formatter) {
|
||||
this.walletPasswordWindow = walletPasswordWindow;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.formatter = formatter;
|
||||
|
||||
type = Type.Instruction;
|
||||
|
@ -148,14 +158,18 @@ public class EmptyWalletWindow extends Overlay<EmptyWalletWindow> {
|
|||
}
|
||||
|
||||
private void doEmptyWallet(KeyParameter aesKey) {
|
||||
if (!openOfferManager.getObservableList().isEmpty()) {
|
||||
UserThread.runAfter(() ->
|
||||
new Popup<>().warning(Res.get("emptyWalletWindow.openOffers.warn"))
|
||||
.actionButtonText(Res.get("emptyWalletWindow.openOffers.yes"))
|
||||
.onAction(() -> doEmptyWallet2(aesKey))
|
||||
.show(), 300, TimeUnit.MILLISECONDS);
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
if (!openOfferManager.getObservableList().isEmpty()) {
|
||||
UserThread.runAfter(() ->
|
||||
new Popup<>().warning(Res.get("emptyWalletWindow.openOffers.warn"))
|
||||
.actionButtonText(Res.get("emptyWalletWindow.openOffers.yes"))
|
||||
.onAction(() -> doEmptyWallet2(aesKey))
|
||||
.show(), 300, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
doEmptyWallet2(aesKey);
|
||||
}
|
||||
} else {
|
||||
doEmptyWallet2(aesKey);
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,12 @@ import io.bisq.common.UserThread;
|
|||
import io.bisq.core.btc.exceptions.TransactionVerificationException;
|
||||
import io.bisq.core.btc.exceptions.WalletException;
|
||||
import io.bisq.core.btc.wallet.TradeWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.gui.components.InputTextField;
|
||||
import io.bisq.gui.main.overlays.Overlay;
|
||||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
|
@ -35,6 +38,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static io.bisq.gui.util.FormBuilder.addLabelInputTextField;
|
||||
|
||||
|
@ -42,14 +46,19 @@ import static io.bisq.gui.util.FormBuilder.addLabelInputTextField;
|
|||
public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
|
||||
private static final Logger log = LoggerFactory.getLogger(ManualPayoutTxWindow.class);
|
||||
private final TradeWalletService tradeWalletService;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public ManualPayoutTxWindow(TradeWalletService tradeWalletService) {
|
||||
@Inject
|
||||
public ManualPayoutTxWindow(TradeWalletService tradeWalletService, P2PService p2PService, WalletsSetup walletsSetup) {
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
type = Type.Attention;
|
||||
}
|
||||
|
||||
|
@ -156,27 +165,31 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
|
|||
}
|
||||
};
|
||||
onAction(() -> {
|
||||
try {
|
||||
tradeWalletService.emergencySignAndPublishPayoutTx(depositTxHex.getText(),
|
||||
Coin.parseCoin(buyerPayoutAmount.getText()),
|
||||
Coin.parseCoin(sellerPayoutAmount.getText()),
|
||||
Coin.parseCoin(arbitratorPayoutAmount.getText()),
|
||||
Coin.parseCoin(txFee.getText()),
|
||||
buyerAddressString.getText(),
|
||||
sellerAddressString.getText(),
|
||||
arbitratorAddressString.getText(),
|
||||
buyerPrivateKeyAsHex.getText(),
|
||||
sellerPrivateKeyAsHex.getText(),
|
||||
arbitratorPrivateKeyAsHex.getText(),
|
||||
buyerPubKeyAsHex.getText(),
|
||||
sellerPubKeyAsHex.getText(),
|
||||
arbitratorPubKeyAsHex.getText(),
|
||||
P2SHMultiSigOutputScript.getText(),
|
||||
callback);
|
||||
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
UserThread.execute(() -> new Popup<>().warning(e.toString()).show());
|
||||
if (GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup)) {
|
||||
try {
|
||||
tradeWalletService.emergencySignAndPublishPayoutTx(depositTxHex.getText(),
|
||||
Coin.parseCoin(buyerPayoutAmount.getText()),
|
||||
Coin.parseCoin(sellerPayoutAmount.getText()),
|
||||
Coin.parseCoin(arbitratorPayoutAmount.getText()),
|
||||
Coin.parseCoin(txFee.getText()),
|
||||
buyerAddressString.getText(),
|
||||
sellerAddressString.getText(),
|
||||
arbitratorAddressString.getText(),
|
||||
buyerPrivateKeyAsHex.getText(),
|
||||
sellerPrivateKeyAsHex.getText(),
|
||||
arbitratorPrivateKeyAsHex.getText(),
|
||||
buyerPubKeyAsHex.getText(),
|
||||
sellerPubKeyAsHex.getText(),
|
||||
arbitratorPubKeyAsHex.getText(),
|
||||
P2SHMultiSigOutputScript.getText(),
|
||||
callback);
|
||||
} catch (AddressFormatException | WalletException | TransactionVerificationException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
UserThread.execute(() -> new Popup<>().warning(e.toString()).show());
|
||||
}
|
||||
} else {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -342,7 +342,9 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
|||
}
|
||||
|
||||
private void doRestore() {
|
||||
long date = datePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||
final LocalDate value = datePicker.getValue();
|
||||
//TODO Is ZoneOffset correct?
|
||||
long date = value != null ? value.atStartOfDay().toEpochSecond(ZoneOffset.UTC) : 0;
|
||||
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(seedWordsTextArea.getText()), null, "", date);
|
||||
walletsManager.restoreSeedWords(
|
||||
seed,
|
||||
|
|
|
@ -36,7 +36,9 @@ class OpenOffersViewModel extends ActivatableWithDataModel<OpenOffersDataModel>
|
|||
|
||||
|
||||
@Inject
|
||||
public OpenOffersViewModel(OpenOffersDataModel dataModel, P2PService p2PService, BSFormatter formatter) {
|
||||
public OpenOffersViewModel(OpenOffersDataModel dataModel,
|
||||
P2PService p2PService,
|
||||
BSFormatter formatter) {
|
||||
super(dataModel);
|
||||
|
||||
this.p2PService = p2PService;
|
||||
|
|
|
@ -29,6 +29,7 @@ import io.bisq.core.arbitration.Dispute;
|
|||
import io.bisq.core.arbitration.DisputeAlreadyOpenException;
|
||||
import io.bisq.core.arbitration.DisputeManager;
|
||||
import io.bisq.core.btc.wallet.BtcWalletService;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.offer.Offer;
|
||||
import io.bisq.core.offer.OfferPayload;
|
||||
import io.bisq.core.payment.payload.PaymentAccountPayload;
|
||||
|
@ -45,6 +46,7 @@ import io.bisq.gui.main.overlays.notifications.NotificationCenter;
|
|||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.gui.main.overlays.windows.SelectDepositTxWindow;
|
||||
import io.bisq.gui.main.overlays.windows.WalletPasswordWindow;
|
||||
import io.bisq.gui.util.GUIUtil;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
@ -72,6 +74,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
private final KeyRing keyRing;
|
||||
public final DisputeManager disputeManager;
|
||||
private final P2PService p2PService;
|
||||
private final WalletsSetup walletsSetup;
|
||||
public final Navigation navigation;
|
||||
public final WalletPasswordWindow walletPasswordWindow;
|
||||
private final NotificationCenter notificationCenter;
|
||||
|
@ -93,10 +96,15 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public PendingTradesDataModel(TradeManager tradeManager, BtcWalletService btcWalletService,
|
||||
KeyRing keyRing, DisputeManager disputeManager,
|
||||
Preferences preferences, P2PService p2PService,
|
||||
Navigation navigation, WalletPasswordWindow walletPasswordWindow,
|
||||
public PendingTradesDataModel(TradeManager tradeManager,
|
||||
BtcWalletService btcWalletService,
|
||||
KeyRing keyRing,
|
||||
DisputeManager disputeManager,
|
||||
Preferences preferences,
|
||||
P2PService p2PService,
|
||||
WalletsSetup walletsSetup,
|
||||
Navigation navigation,
|
||||
WalletPasswordWindow walletPasswordWindow,
|
||||
NotificationCenter notificationCenter) {
|
||||
this.tradeManager = tradeManager;
|
||||
this.btcWalletService = btcWalletService;
|
||||
|
@ -104,6 +112,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
this.disputeManager = disputeManager;
|
||||
this.preferences = preferences;
|
||||
this.p2PService = p2PService;
|
||||
this.walletsSetup = walletsSetup;
|
||||
this.navigation = navigation;
|
||||
this.walletPasswordWindow = walletPasswordWindow;
|
||||
this.notificationCenter = notificationCenter;
|
||||
|
@ -311,6 +320,14 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
return getOffer() != null ? getOffer().getShortId() : "";
|
||||
}
|
||||
|
||||
public boolean isReadyForTxBroadcast() {
|
||||
return GUIUtil.isReadyForTxBroadcast(p2PService, walletsSetup);
|
||||
}
|
||||
|
||||
public void showNotReadyForTxBroadcastPopups() {
|
||||
GUIUtil.showNotReadyForTxBroadcastPopups(p2PService, walletsSetup);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
|
|
@ -178,7 +178,13 @@ public class BuyerStep4View extends TradeStepView {
|
|||
withdrawAddressTextField.setVisible(true);
|
||||
GridPane.setRowSpan(withdrawTitledGroupBg, 2);
|
||||
withdrawToExternalWalletButton.setDefaultButton(true);
|
||||
withdrawToExternalWalletButton.setOnAction(e -> reviewWithdrawal());
|
||||
withdrawToExternalWalletButton.setOnAction(e -> {
|
||||
if (model.dataModel.isReadyForTxBroadcast())
|
||||
reviewWithdrawal();
|
||||
else
|
||||
model.dataModel.showNotReadyForTxBroadcastPopups();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("PointlessBooleanExpression")
|
||||
|
|
|
@ -248,8 +248,9 @@ public class SellerStep3View extends TradeStepView {
|
|||
|
||||
@SuppressWarnings("PointlessBooleanExpression")
|
||||
private void onPaymentReceived() {
|
||||
log.debug("onPaymentReceived");
|
||||
if (model.p2PService.isBootstrapped()) {
|
||||
// The confirmPaymentReceived call will trigger the trade protocol to do the payout tx. We want to be sure that we
|
||||
// are well connected to the Bitcoin network before triggering the broadcast.
|
||||
if (model.dataModel.isReadyForTxBroadcast()) {
|
||||
//noinspection UnusedAssignment
|
||||
String key = "confirmPaymentReceived";
|
||||
//noinspection ConstantConditions
|
||||
|
@ -278,11 +279,10 @@ public class SellerStep3View extends TradeStepView {
|
|||
confirmPaymentReceived();
|
||||
}
|
||||
} else {
|
||||
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
|
||||
model.dataModel.showNotReadyForTxBroadcastPopups();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("PointlessBooleanExpression")
|
||||
private void showPopup() {
|
||||
PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload();
|
||||
|
@ -352,7 +352,7 @@ public class SellerStep3View extends TradeStepView {
|
|||
return Optional.of(((BankAccountPayload) paymentAccountPayload).getHolderName());
|
||||
else if (paymentAccountPayload instanceof SepaAccountPayload)
|
||||
return Optional.of(((SepaAccountPayload) paymentAccountPayload).getHolderName());
|
||||
else if (paymentAccountPayload instanceof SepaInstantAccountPayload)
|
||||
else if (paymentAccountPayload instanceof SepaInstantAccountPayload)
|
||||
return Optional.of(((SepaInstantAccountPayload) paymentAccountPayload).getHolderName());
|
||||
else
|
||||
return Optional.<String>empty();
|
||||
|
|
|
@ -32,6 +32,7 @@ import io.bisq.common.proto.persistable.PersistenceProtoResolver;
|
|||
import io.bisq.common.storage.Storage;
|
||||
import io.bisq.common.util.Utilities;
|
||||
import io.bisq.core.app.BisqEnvironment;
|
||||
import io.bisq.core.btc.wallet.WalletsSetup;
|
||||
import io.bisq.core.payment.PaymentAccount;
|
||||
import io.bisq.core.payment.PaymentAccountList;
|
||||
import io.bisq.core.provider.fee.FeeService;
|
||||
|
@ -40,6 +41,7 @@ import io.bisq.core.user.Preferences;
|
|||
import io.bisq.core.user.User;
|
||||
import io.bisq.gui.components.indicator.TxConfidenceIndicator;
|
||||
import io.bisq.gui.main.overlays.popups.Popup;
|
||||
import io.bisq.network.p2p.P2PService;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Orientation;
|
||||
|
@ -426,4 +428,19 @@ public class GUIUtil {
|
|||
address), amount, label, null) :
|
||||
"";
|
||||
}
|
||||
|
||||
public static boolean isReadyForTxBroadcast(P2PService p2PService, WalletsSetup walletsSetup) {
|
||||
return p2PService.isBootstrapped() &&
|
||||
walletsSetup.isDownloadComplete() &&
|
||||
walletsSetup.hasSufficientPeersForBroadcast();
|
||||
}
|
||||
|
||||
public static void showNotReadyForTxBroadcastPopups(P2PService p2PService, WalletsSetup walletsSetup) {
|
||||
if (!p2PService.isBootstrapped())
|
||||
new Popup<>().information(Res.get("popup.warning.notFullyConnected")).show();
|
||||
else if (!walletsSetup.hasSufficientPeersForBroadcast())
|
||||
new Popup<>().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", walletsSetup.getMinBroadcastConnections())).show();
|
||||
else if (!walletsSetup.isDownloadComplete())
|
||||
new Popup<>().information(Res.get("popup.warning.downloadNotComplete")).show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ public final class AltCoinAddressValidator extends InputValidator {
|
|||
return regexTestFailed;
|
||||
}
|
||||
case "MDC":
|
||||
if (input.matches("^L[a-zA-Z0-9]{26,33}$"))
|
||||
if (input.matches("^m[a-zA-Z0-9]{26,33}$"))
|
||||
return new ValidationResult(true);
|
||||
else
|
||||
return regexTestFailed;
|
||||
|
@ -367,7 +367,7 @@ public final class AltCoinAddressValidator extends InputValidator {
|
|||
} catch (AddressFormatException e) {
|
||||
return new ValidationResult(false, getErrorMessage(e));
|
||||
}
|
||||
case "CAGE":
|
||||
case "CAGE":
|
||||
if (input.matches("^[D][a-zA-Z0-9]{26,34}$")) {
|
||||
//noinspection ConstantConditions
|
||||
try {
|
||||
|
@ -391,6 +391,32 @@ public final class AltCoinAddressValidator extends InputValidator {
|
|||
} catch (AddressFormatException e) {
|
||||
return new ValidationResult(false, getErrorMessage(e));
|
||||
}
|
||||
case "WILD":
|
||||
// https://github.com/ethereum/web3.js/blob/master/lib/utils/utils.js#L403
|
||||
if (!input.matches("^(0x)?[0-9a-fA-F]{40}$"))
|
||||
return regexTestFailed;
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
case "ONION":
|
||||
try {
|
||||
Address.fromBase58(OnionParams.get(), input);
|
||||
return new ValidationResult(true);
|
||||
} catch (AddressFormatException e) {
|
||||
return new ValidationResult(false, getErrorMessage(e));
|
||||
}
|
||||
case "CREA":
|
||||
try {
|
||||
Address.fromBase58(CreaParams.get(), input);
|
||||
return new ValidationResult(true);
|
||||
} catch (AddressFormatException e) {
|
||||
return new ValidationResult(false, getErrorMessage(e));
|
||||
}
|
||||
case "XIN":
|
||||
if (!input.matches("^XIN-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{5}$"))
|
||||
return regexTestFailed;
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
|
||||
// Add new coins at the end...
|
||||
default:
|
||||
log.debug("Validation for AltCoinAddress not implemented yet. currencyCode: " + currencyCode);
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.gui.util.validation.params;
|
||||
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
|
||||
public class CreaParams extends NetworkParameters {
|
||||
|
||||
private static CreaParams instance;
|
||||
|
||||
public static synchronized CreaParams get() {
|
||||
if (instance == null) {
|
||||
instance = new CreaParams();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// We only use the properties needed for address validation
|
||||
|
||||
public CreaParams() {
|
||||
super();
|
||||
int segwitHeader = 35;
|
||||
addressHeader = 28;
|
||||
p2shHeader = 5;
|
||||
acceptableAddressCodes = new int[]{addressHeader, p2shHeader, segwitHeader};
|
||||
|
||||
}
|
||||
|
||||
// default dummy implementations, not used...
|
||||
@Override
|
||||
public String getPaymentProtocolId() {
|
||||
return PAYMENT_PROTOCOL_ID_MAINNET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkDifficultyTransitions(StoredBlock storedPrev, Block next, BlockStore blockStore) throws VerificationException, BlockStoreException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coin getMaxMoney() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coin getMinNonDustOutput() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MonetaryFormat getMonetaryFormat() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUriScheme() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaxMoney() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitcoinSerializer getSerializer(boolean parseRetain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersionNum(ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bisq.gui.util.validation.params;
|
||||
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.bitcoinj.utils.MonetaryFormat;
|
||||
|
||||
public class OnionParams extends NetworkParameters {
|
||||
|
||||
private static OnionParams instance;
|
||||
|
||||
public static synchronized OnionParams get() {
|
||||
if (instance == null) {
|
||||
instance = new OnionParams();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
// We only use the properties needed for address validation
|
||||
public OnionParams() {
|
||||
super();
|
||||
addressHeader = 31;
|
||||
p2shHeader = 78;
|
||||
acceptableAddressCodes = new int[]{addressHeader, p2shHeader};
|
||||
}
|
||||
|
||||
// default dummy implementations, not used...
|
||||
@Override
|
||||
public String getPaymentProtocolId() {
|
||||
return PAYMENT_PROTOCOL_ID_MAINNET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkDifficultyTransitions(StoredBlock storedPrev, Block next, BlockStore blockStore) throws VerificationException, BlockStoreException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coin getMaxMoney() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Coin getMinNonDustOutput() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MonetaryFormat getMonetaryFormat() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUriScheme() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMaxMoney() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitcoinSerializer getSerializer(boolean parseRetain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersionNum(ProtocolVersion version) {
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -10,24 +10,26 @@
|
|||
<appender-ref ref="CONSOLE_APPENDER"/>
|
||||
</root>
|
||||
|
||||
<!--
|
||||
<logger name="com.neemre.btcdcli4j" level="WARN"/>
|
||||
<logger name="org.berndpruenster" level="WARN"/>
|
||||
|
||||
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
|
||||
<!--
|
||||
<logger name="com.neemre.btcdcli4j" level="WARN"/>
|
||||
|
||||
<logger name="org.bitcoinj.core.AbstractBlockChain" level="WARN"/>
|
||||
<logger name="org.bitcoinj.net.BlockingClient" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.PeerGroup" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.Peer" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.Context" level="WARN"/>
|
||||
<logger name="org.bitcoinj.wallet.WalletFiles" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.listeners.DownloadProgressTracker" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.PeerSocketHandler" level="WARN"/>
|
||||
<logger name="org.bitcoinj.net.NioClientManager" level="WARN"/>
|
||||
<logger name="com.msopentech.thali.toronionproxy.OnionProxyManagerEventHandler" level="INFO"/>
|
||||
|
||||
<!– We get too many errors logged from connection issues–>
|
||||
<logger name="org.bitcoinj.net.BlockingClient" level="OFF"/>
|
||||
-->
|
||||
<logger name="org.bitcoinj.core.AbstractBlockChain" level="WARN"/>
|
||||
<logger name="org.bitcoinj.net.BlockingClient" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.PeerGroup" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.Peer" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.Context" level="WARN"/>
|
||||
<logger name="org.bitcoinj.wallet.WalletFiles" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.listeners.DownloadProgressTracker" level="WARN"/>
|
||||
<logger name="org.bitcoinj.core.PeerSocketHandler" level="WARN"/>
|
||||
<logger name="org.bitcoinj.net.NioClientManager" level="WARN"/>
|
||||
|
||||
<!– We get too many errors logged from connection issues–>
|
||||
<logger name="org.bitcoinj.net.BlockingClient" level="OFF"/>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<logger name="org.bitcoinj.net.ConnectionHandler" level="WARN"/>
|
||||
|
|
|
@ -358,11 +358,11 @@ public class AltCoinAddressValidatorTest {
|
|||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("MDC");
|
||||
|
||||
assertTrue(validator.validate("LiL8HvSJRDgnS5BXATWe5MzWYicCbRvisr").isValid);
|
||||
assertTrue(validator.validate("LX8B2K8eHSxWjfS55sdMoQj2PCPmvRRyLv").isValid);
|
||||
assertTrue(validator.validate("mHUisRLQ4vMXrWrVfGfiEHuD3KZqiUNvzH").isValid);
|
||||
assertTrue(validator.validate("mHbicWaTXNJDbeM6KXCit5JcmvMypwpq8T").isValid);
|
||||
|
||||
assertFalse(validator.validate("mzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhe").isValid);
|
||||
assertFalse(validator.validate("miCVC7QcY917Cz427qTBEUrvBzRapHrupc").isValid);
|
||||
assertFalse(validator.validate("LzBc4XEFSdzCDcTxAgf6EZXgsZWpztRhe").isValid);
|
||||
assertFalse(validator.validate("miCVC7QcY917Cz427qTB").isValid);
|
||||
assertFalse(validator.validate("12KYrjTdVGjFMtaxERSk3gphreJ5US8aUP").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
|
@ -413,7 +413,7 @@ public class AltCoinAddressValidatorTest {
|
|||
}
|
||||
|
||||
// Added 0.6.2
|
||||
@Test
|
||||
@Test
|
||||
public void testCAGE() {
|
||||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("CAGE");
|
||||
|
@ -425,7 +425,7 @@ public class AltCoinAddressValidatorTest {
|
|||
|
||||
assertFalse(validator.validate("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhemqq").isValid);
|
||||
assertFalse(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d1641353982266").isValid);
|
||||
assertFalse(validator.validate("DNkkfdUvkCDiywYE98MTVp9nQJTgeZAiFr").isValid);
|
||||
assertFalse(validator.validate("DNkkfdUvkCDiywYE98MTVp9nQJTgeZAiFr").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
|
||||
|
@ -457,4 +457,72 @@ public class AltCoinAddressValidatorTest {
|
|||
assertFalse(validator.validate("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhek#").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
|
||||
// Added 0.6.3
|
||||
@Test
|
||||
public void testWILD() {
|
||||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("WILD");
|
||||
|
||||
assertTrue(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d164135398226").isValid);
|
||||
assertTrue(validator.validate("2a65Aca4D5fC5B5C859090a6c34d164135398226").isValid);
|
||||
|
||||
assertFalse(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d1641353982266").isValid);
|
||||
assertFalse(validator.validate("0x2a65Aca4D5fC5B5C859090a6c34d16413539822g").isValid);
|
||||
assertFalse(validator.validate("2a65Aca4D5fC5B5C859090a6c34d16413539822g").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testONION() {
|
||||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("ONION");
|
||||
|
||||
assertTrue(validator.validate("DbkkqXwdiWJNcpfw49f2xzTVEbvL1SYWDm").isValid);
|
||||
assertTrue(validator.validate("DetWWdj7VUTDg1yMhjgRfzvwTf4rRSoywK").isValid);
|
||||
assertTrue(validator.validate("Dr5aiywUzZgYHvrQgrCsk6qhxhFzM8VVUM").isValid);
|
||||
|
||||
assertFalse(validator.validate("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhemqq").isValid);
|
||||
assertFalse(validator.validate("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYheO").isValid);
|
||||
assertFalse(validator.validate("1HQQgsvLTgN9xD9hNmAgAreakzVzQUSLSH#").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
|
||||
// Added 0.6.4
|
||||
@Test
|
||||
public void testCREA() {
|
||||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("CREA");
|
||||
|
||||
assertTrue(validator.validate("CGjh99QdHxCE6g9pGUucCJNeUyQPRJr4fE").isValid);
|
||||
assertTrue(validator.validate("FTDYi4GoD3vFYFhEGbuifSZjs6udXVin7B").isValid);
|
||||
assertTrue(validator.validate("361uBxJvmg6f62dMYVM9b7GeR38phkkTyA").isValid);
|
||||
|
||||
assertFalse(validator.validate("C7VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhemqq").isValid);
|
||||
assertFalse(validator.validate("F7VZNX1SN5NtKa8UQFxwQbFeFc3iqRYheO").isValid);
|
||||
assertFalse(validator.validate("17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhek#").isValid);
|
||||
assertFalse(validator.validate("").isValid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXIN() {
|
||||
AltCoinAddressValidator validator = new AltCoinAddressValidator();
|
||||
validator.setCurrencyCode("XIN");
|
||||
|
||||
assertTrue(validator.validate("XIN-FXFA-LR6Y-QZAW-9V4SX").isValid);
|
||||
assertTrue(validator.validate("XIN-JM2U-U4AE-G7WF-3NP9F").isValid);
|
||||
assertTrue(validator.validate("XIN-2223-2222-KB8Y-22222").isValid);
|
||||
|
||||
assertFalse(validator.validate("").isValid);
|
||||
assertFalse(validator.validate("abcde").isValid);
|
||||
assertFalse(validator.validate("XIN-").isValid);
|
||||
|
||||
assertFalse(validator.validate("XIN-FXFA-LR6Y-QZAW-9V4SXA").isValid);
|
||||
assertFalse(validator.validate("NIX-FXFA-LR6Y-QZAW-9V4SX").isValid);
|
||||
|
||||
assertFalse(validator.validate("XIN-FXF-LR6Y-QZAW-9V4SX").isValid);
|
||||
assertFalse(validator.validate("XIN-FXFA-LR6-QZAW-9V4SX").isValid);
|
||||
assertFalse(validator.validate("XIN-FXFA-LR6Y-QZA-9V4SX").isValid);
|
||||
assertFalse(validator.validate("XIN-FXFA-LR6Y-QZAW-9V4S").isValid);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.2</version>
|
||||
<version>0.6.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -88,6 +88,11 @@
|
|||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.bouncycastle:*:*:*</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
<!-- broken with Java 8 (MSHADE-174), using ProGuard instead. -->
|
||||
<minimizeJar>false</minimizeJar>
|
||||
<transformers>
|
||||
|
|
|
@ -34,7 +34,7 @@ import java.security.Security;
|
|||
|
||||
@Slf4j
|
||||
public class Monitor {
|
||||
public static final String VERSION = "1.0.0";
|
||||
public static final String VERSION = "1.0.1";
|
||||
|
||||
private static MonitorEnvironment environment;
|
||||
@Getter
|
||||
|
|
|
@ -96,7 +96,7 @@ public class MonitorMain extends BisqExecutable {
|
|||
// In order to work around a bug in JavaFX 8u25 and below, you must include the following code as the first line of your realMain method:
|
||||
Thread.currentThread().setContextClassLoader(MonitorMain.class.getClassLoader());
|
||||
|
||||
port(8080);
|
||||
port(80);
|
||||
get("/", (req, res) -> {
|
||||
log.info("Incoming request from: " + req.userAgent());
|
||||
return seedNodeMonitor.getMetricsModel().getResultAsHtml();
|
||||
|
|
|
@ -57,6 +57,8 @@ public class MetricsModel {
|
|||
private List<Peer> connectedPeers;
|
||||
private Map<Tuple2<BitcoinNodes.BtcNode, Boolean>, Integer> btcNodeDownTimeMap = new HashMap<>();
|
||||
private Map<Tuple2<BitcoinNodes.BtcNode, Boolean>, Integer> btcNodeUpTimeMap = new HashMap<>();
|
||||
@Getter
|
||||
private Set<NodeAddress> nodesInError = new HashSet<>();
|
||||
|
||||
@Inject
|
||||
public MetricsModel(SeedNodesRepository seedNodesRepository,
|
||||
|
@ -403,4 +405,11 @@ public class MetricsModel {
|
|||
duration = duration.replace(", ", "");
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void addNodesInError(NodeAddress nodeAddress) {
|
||||
nodesInError.add(nodeAddress);
|
||||
}
|
||||
public void removeNodesInError(NodeAddress nodeAddress) {
|
||||
nodesInError.remove(nodeAddress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,8 +52,8 @@ class MonitorRequestHandler implements MessageListener {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private final NetworkNode networkNode;
|
||||
private P2PDataStorage dataStorage;
|
||||
private Metrics metric;
|
||||
private final P2PDataStorage dataStorage;
|
||||
private final Metrics metrics;
|
||||
private final Listener listener;
|
||||
private Timer timeoutTimer;
|
||||
private final int nonce = new Random().nextInt();
|
||||
|
@ -64,10 +64,10 @@ class MonitorRequestHandler implements MessageListener {
|
|||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public MonitorRequestHandler(NetworkNode networkNode, P2PDataStorage dataStorage, Metrics metric, Listener listener) {
|
||||
public MonitorRequestHandler(NetworkNode networkNode, P2PDataStorage dataStorage, Metrics metrics, Listener listener) {
|
||||
this.networkNode = networkNode;
|
||||
this.dataStorage = dataStorage;
|
||||
this.metric = metric;
|
||||
this.metrics = metrics;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
@ -214,12 +214,12 @@ class MonitorRequestHandler implements MessageListener {
|
|||
});
|
||||
sb.append("#################################################################");
|
||||
log.info(sb.toString());
|
||||
metric.getReceivedObjectsList().add(receivedObjects);
|
||||
metrics.getReceivedObjectsList().add(receivedObjects);
|
||||
|
||||
final long duration = new Date().getTime() - requestTs;
|
||||
log.info("Requesting data took {} ms", duration);
|
||||
metric.getRequestDurations().add(duration);
|
||||
metric.getErrorMessages().add(arbitratorReceived[0] ? "" : "No Arbitrator objects received! Seed node need to be restarted!");
|
||||
metrics.getRequestDurations().add(duration);
|
||||
metrics.getErrorMessages().add(arbitratorReceived[0] ? "" : "No Arbitrator objects received! Seed node need to be restarted!");
|
||||
|
||||
cleanup();
|
||||
connection.shutDown(CloseConnectionReason.CLOSE_REQUESTED_BY_PEER, listener::onComplete);
|
||||
|
@ -247,13 +247,11 @@ class MonitorRequestHandler implements MessageListener {
|
|||
|
||||
private void handleFault(String errorMessage, NodeAddress nodeAddress, CloseConnectionReason closeConnectionReason) {
|
||||
cleanup();
|
||||
metric.getErrorMessages().add(errorMessage + " (" + new Date().toString() + ")");
|
||||
|
||||
// In case we would have already a connection we close it
|
||||
networkNode.getAllConnections().stream()
|
||||
.filter(connection -> connection.getPeersNodeAddressOptional().isPresent() && connection.getPeersNodeAddressOptional().get().equals(nodeAddress))
|
||||
.forEach(c -> c.shutDown(closeConnectionReason));
|
||||
// We do not log every error only if it fails several times in a row.
|
||||
|
||||
// We do not close the connection as it might be we have opened a new connection for that peer and
|
||||
// we don't want to close that. We do not know the connection at fault as the fault handler does not contain that,
|
||||
// so we could only search for connections for that nodeAddress but that would close an new connection attempt.
|
||||
listener.onFault(errorMessage, nodeAddress);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
@Slf4j
|
||||
public class MonitorRequestManager implements ConnectionListener {
|
||||
private static final long RETRY_DELAY_SEC = 20;
|
||||
private static final long RETRY_DELAY_SEC = 30;
|
||||
private static final long CLEANUP_TIMER = 60;
|
||||
private static final long REQUEST_PERIOD_MIN = 10;
|
||||
private static final int MAX_RETRIES = 6;
|
||||
private static final int MAX_RETRIES = 5;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -46,7 +46,6 @@ public class MonitorRequestManager implements ConnectionListener {
|
|||
private Map<NodeAddress, Timer> retryTimerMap = new HashMap<>();
|
||||
private Map<NodeAddress, Integer> retryCounterMap = new HashMap<>();
|
||||
private boolean stopped;
|
||||
private Set<NodeAddress> nodesInError = new HashSet<>();
|
||||
private int completedRequestIndex;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -132,9 +131,10 @@ public class MonitorRequestManager implements ConnectionListener {
|
|||
private void requestFromNode(NodeAddress nodeAddress) {
|
||||
if (!stopped) {
|
||||
if (!handlerMap.containsKey(nodeAddress)) {
|
||||
final Metrics metrics = metricsModel.getMetrics(nodeAddress);
|
||||
MonitorRequestHandler requestDataHandler = new MonitorRequestHandler(networkNode,
|
||||
dataStorage,
|
||||
metricsModel.getMetrics(nodeAddress),
|
||||
metrics,
|
||||
new MonitorRequestHandler.Listener() {
|
||||
@Override
|
||||
public void onComplete() {
|
||||
|
@ -151,8 +151,8 @@ public class MonitorRequestManager implements ConnectionListener {
|
|||
if (completedRequestIndex == numNodes)
|
||||
metricsModel.log();
|
||||
|
||||
if (nodesInError.contains(nodeAddress)) {
|
||||
nodesInError.remove(nodeAddress);
|
||||
if (metricsModel.getNodesInError().contains(nodeAddress)) {
|
||||
metricsModel.removeNodesInError(nodeAddress);
|
||||
if (slackApi != null)
|
||||
slackApi.call(new SlackMessage("Fixed: " + nodeAddress.getFullAddress(),
|
||||
"<" + seedNodesRepository.getOperator(nodeAddress) + ">" + " Your seed node is recovered."));
|
||||
|
@ -162,6 +162,8 @@ public class MonitorRequestManager implements ConnectionListener {
|
|||
@Override
|
||||
public void onFault(String errorMessage, NodeAddress nodeAddress) {
|
||||
handlerMap.remove(nodeAddress);
|
||||
stopRetryTimer(nodeAddress);
|
||||
|
||||
int retryCounter;
|
||||
if (retryCounterMap.containsKey(nodeAddress))
|
||||
retryCounter = retryCounterMap.get(nodeAddress);
|
||||
|
@ -169,15 +171,25 @@ public class MonitorRequestManager implements ConnectionListener {
|
|||
retryCounter = 0;
|
||||
|
||||
if (retryCounter < MAX_RETRIES) {
|
||||
log.info("We got an error at peer={}. We will try again after a delay of {} sec. error={} ",
|
||||
nodeAddress, RETRY_DELAY_SEC, errorMessage);
|
||||
final Timer timer = UserThread.runAfter(() -> requestFromNode(nodeAddress), RETRY_DELAY_SEC);
|
||||
retryTimerMap.put(nodeAddress, timer);
|
||||
retryCounterMap.put(nodeAddress, ++retryCounter);
|
||||
} else {
|
||||
log.warn("We got repeated errors at peer={}. error={} ",
|
||||
nodeAddress, errorMessage);
|
||||
|
||||
metricsModel.addNodesInError(nodeAddress);
|
||||
metrics.getErrorMessages().add(errorMessage + " (" + new Date().toString() + ")");
|
||||
|
||||
metricsModel.updateReport();
|
||||
completedRequestIndex++;
|
||||
if (completedRequestIndex == numNodes)
|
||||
metricsModel.log();
|
||||
nodesInError.add(nodeAddress);
|
||||
|
||||
retryCounterMap.remove(nodeAddress);
|
||||
|
||||
if (slackApi != null)
|
||||
slackApi.call(new SlackMessage("Error: " + nodeAddress.getFullAddress(),
|
||||
"<" + seedNodesRepository.getOperator(nodeAddress) + ">" + " Your seed node failed " + RETRY_DELAY_SEC + " times with error message: " + errorMessage));
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>io.bisq</groupId>
|
||||
<version>0.6.2</version>
|
||||
<version>0.6.4</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -20,9 +20,11 @@
|
|||
<dependency>
|
||||
<groupId>com.github.JesusMcCloud.netlayer</groupId>
|
||||
<artifactId>tor.native</artifactId>
|
||||
<!-- older version e7195748 -->
|
||||
<!-- newer version 1c9d80e -> deactivate as there might be some issues TODO check with dev -->
|
||||
<version>e7195748</version>
|
||||
|
||||
<!-- v0.3.2: first integration e7195748 -->
|
||||
<!-- v0.3.3: fixed shutdown exception 1c9d80e -->
|
||||
<!-- v0.3.4: use latest tor version b3497f1d -->
|
||||
<version>b3497f1d</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
|
|||
import io.bisq.common.app.Version;
|
||||
import io.bisq.network.Socks5ProxyProvider;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.protocol.HttpClientContext;
|
||||
|
@ -14,8 +15,6 @@ import org.apache.http.impl.client.CloseableHttpClient;
|
|||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||
import org.apache.http.ssl.SSLContexts;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
@ -31,9 +30,8 @@ import java.util.UUID;
|
|||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
// TODO close connection if failing
|
||||
@Slf4j
|
||||
public class HttpClient {
|
||||
private static final Logger log = LoggerFactory.getLogger(HttpClient.class);
|
||||
|
||||
@Nullable
|
||||
private Socks5ProxyProvider socks5ProxyProvider;
|
||||
@Getter
|
||||
|
@ -142,19 +140,19 @@ public class HttpClient {
|
|||
new PoolingHttpClientConnectionManager(reg) :
|
||||
new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver());
|
||||
try (CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build()) {
|
||||
InetSocketAddress socksaddr = new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort());
|
||||
InetSocketAddress socksAddress = new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort());
|
||||
|
||||
// remove me: Use this to test with system-wide Tor proxy, or change port for another proxy.
|
||||
// InetSocketAddress socksaddr = new InetSocketAddress("127.0.0.1", 9050);
|
||||
// InetSocketAddress socksAddress = new InetSocketAddress("127.0.0.1", 9050);
|
||||
|
||||
HttpClientContext context = HttpClientContext.create();
|
||||
context.setAttribute("socks.address", socksaddr);
|
||||
context.setAttribute("socks.address", socksAddress);
|
||||
|
||||
HttpGet request = new HttpGet(baseUrl + param);
|
||||
if (headerKey != null && headerValue != null)
|
||||
request.setHeader(headerKey, headerValue);
|
||||
|
||||
log.debug("Executing request " + request + " proxy: " + socksaddr);
|
||||
log.debug("Executing request " + request + " proxy: " + socksAddress);
|
||||
try (CloseableHttpResponse response = httpclient.execute(request, context)) {
|
||||
return convertInputStreamToString(response.getEntity().getContent());
|
||||
}
|
||||
|
|
|
@ -359,7 +359,7 @@ public class Connection implements MessageListener {
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void setPeerType(PeerType peerType) {
|
||||
Log.traceCall(peerType.toString());
|
||||
log.debug("setPeerType: peerType={}, nodeAddressOpt={}", peerType.toString(), peersNodeAddressOptional);
|
||||
this.peerType = peerType;
|
||||
}
|
||||
|
||||
|
@ -431,7 +431,7 @@ public class Connection implements MessageListener {
|
|||
}
|
||||
|
||||
public void shutDown(CloseConnectionReason closeConnectionReason, @Nullable Runnable shutDownCompleteHandler) {
|
||||
Log.traceCall(this.toString());
|
||||
log.debug("shutDown: nodeAddressOpt={}, closeConnectionReason={}", this.peersNodeAddressOptional, closeConnectionReason);
|
||||
if (!stopped) {
|
||||
String peersNodeAddress = peersNodeAddressOptional.isPresent() ? peersNodeAddressOptional.get().toString() : "null";
|
||||
log.debug("\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" +
|
||||
|
@ -634,9 +634,11 @@ public class Connection implements MessageListener {
|
|||
closeConnectionReason = CloseConnectionReason.SOCKET_CLOSED;
|
||||
else
|
||||
closeConnectionReason = CloseConnectionReason.RESET;
|
||||
|
||||
log.info("SocketException (expected if connection lost). closeConnectionReason={}; connection={}", closeConnectionReason, connection);
|
||||
} else if (e instanceof SocketTimeoutException || e instanceof TimeoutException) {
|
||||
closeConnectionReason = CloseConnectionReason.SOCKET_TIMEOUT;
|
||||
log.debug("Shut down caused by exception {} on connection={}", e.toString(), connection);
|
||||
log.info("Shut down caused by exception {} on connection={}", e.toString(), connection);
|
||||
} else if (e instanceof EOFException) {
|
||||
closeConnectionReason = CloseConnectionReason.TERMINATED;
|
||||
log.warn("Shut down caused by exception {} on connection={}", e.toString(), connection);
|
||||
|
@ -646,9 +648,9 @@ public class Connection implements MessageListener {
|
|||
} else {
|
||||
// TODO sometimes we get StreamCorruptedException, OptionalDataException, IllegalStateException
|
||||
closeConnectionReason = CloseConnectionReason.UNKNOWN_EXCEPTION;
|
||||
log.warn("Unknown reason for exception at socket {}\n\t" +
|
||||
log.warn("Unknown reason for exception at socket: {}\n\t" +
|
||||
"connection={}\n\t" +
|
||||
"Exception=",
|
||||
"Exception={}",
|
||||
socket.toString(),
|
||||
this,
|
||||
e.toString());
|
||||
|
@ -744,6 +746,7 @@ public class Connection implements MessageListener {
|
|||
try {
|
||||
if (sharedModel.getSocket() != null &&
|
||||
sharedModel.getSocket().isClosed()) {
|
||||
log.warn("Socket is null or closed socket={}", sharedModel.getSocket());
|
||||
stopAndShutDown(CloseConnectionReason.SOCKET_CLOSED);
|
||||
return;
|
||||
}
|
||||
|
@ -765,8 +768,10 @@ public class Connection implements MessageListener {
|
|||
PB.NetworkEnvelope proto = PB.NetworkEnvelope.parseDelimitedFrom(protoInputStream);
|
||||
|
||||
if (proto == null) {
|
||||
if (protoInputStream.read() != -1)
|
||||
log.error("proto is null. Should not happen...");
|
||||
if (protoInputStream.read() == -1)
|
||||
log.info("proto is null because protoInputStream.read()=-1 (EOF). That is expected if client got stopped without proper shutdown.");
|
||||
else
|
||||
log.warn("proto is null. protoInputStream.read()=" + protoInputStream.read());
|
||||
stopAndShutDown(CloseConnectionReason.NO_PROTO_BUFFER_ENV);
|
||||
return;
|
||||
}
|
||||
|
@ -848,7 +853,7 @@ public class Connection implements MessageListener {
|
|||
|
||||
if (networkEnvelope instanceof CloseConnectionMessage) {
|
||||
// If we get a CloseConnectionMessage we shut down
|
||||
log.debug("CloseConnectionMessage received. Reason={}\n\t" +
|
||||
log.info("CloseConnectionMessage received. Reason={}\n\t" +
|
||||
"connection={}", proto.getCloseConnectionMessage().getReason(), connection);
|
||||
if (CloseConnectionReason.PEER_BANNED.name().equals(proto.getCloseConnectionMessage().getReason())) {
|
||||
log.warn("We got shut down because we are banned by the other peer. (InputHandler.run CloseConnectionMessage)");
|
||||
|
@ -880,10 +885,6 @@ public class Connection implements MessageListener {
|
|||
// 4. DirectMessage (implements SendersNodeAddressMessage)
|
||||
if (networkEnvelope instanceof SendersNodeAddressMessage) {
|
||||
NodeAddress senderNodeAddress = ((SendersNodeAddressMessage) networkEnvelope).getSenderNodeAddress();
|
||||
// We must not shut down a banned peer at that moment as it would trigger a connection termination
|
||||
// and we could not send the CloseConnectionMessage.
|
||||
// We shut down a banned peer at the next step at setPeersNodeAddress().
|
||||
|
||||
Optional<NodeAddress> peersNodeAddressOptional = connection.getPeersNodeAddressOptional();
|
||||
if (peersNodeAddressOptional.isPresent()) {
|
||||
// If we have already the peers address we check again if it matches our stored one
|
||||
|
@ -891,6 +892,9 @@ public class Connection implements MessageListener {
|
|||
"senderNodeAddress not matching connections peer address.\n\t" +
|
||||
"message=" + networkEnvelope);
|
||||
} else {
|
||||
// We must not shut down a banned peer at that moment as it would trigger a connection termination
|
||||
// and we could not send the CloseConnectionMessage.
|
||||
// We check for a banned peer inside setPeersNodeAddress() and shut down if banned.
|
||||
connection.setPeersNodeAddress(senderNodeAddress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ import java.net.Socket;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -69,7 +68,8 @@ public class TorNetworkNode extends NetworkNode {
|
|||
|
||||
@Override
|
||||
public void start(@Nullable SetupListener setupListener) {
|
||||
FileUtil.rollingBackup(new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString()), "private_key", 20);
|
||||
final File hiddenservice = new File(Paths.get(torDir.getAbsolutePath(), "hiddenservice").toString());
|
||||
FileUtil.rollingBackup(hiddenservice, "private_key", 20);
|
||||
|
||||
if (setupListener != null)
|
||||
addSetupListener(setupListener);
|
||||
|
@ -83,7 +83,9 @@ public class TorNetworkNode extends NetworkNode {
|
|||
@Override
|
||||
protected Socket createSocket(NodeAddress peerNodeAddress) throws IOException {
|
||||
checkArgument(peerNodeAddress.getHostName().endsWith(".onion"), "PeerAddress is not an onion address");
|
||||
return new TorSocket(peerNodeAddress.getHostName(), peerNodeAddress.getPort(), UUID.randomUUID().toString()); // each socket uses a random Tor stream id
|
||||
// If streamId is null stream isolation gets deactivated.
|
||||
// Hidden services use stream isolation by default so we pass null.
|
||||
return new TorSocket(peerNodeAddress.getHostName(), peerNodeAddress.getPort(), null);
|
||||
}
|
||||
|
||||
// TODO handle failure more cleanly
|
||||
|
@ -210,7 +212,10 @@ public class TorNetworkNode extends NetworkNode {
|
|||
long ts1 = new Date().getTime();
|
||||
log.info("Starting tor");
|
||||
Tor.setDefault(new NativeTor(torDir, bridgeEntries));
|
||||
log.info("Tor started after {} ms. Start publishing hidden service.", (new Date().getTime() - ts1)); // takes usually a few seconds
|
||||
log.info("\n################################################################\n" +
|
||||
"Tor started after {} ms. Start publishing hidden service.\n" +
|
||||
"################################################################",
|
||||
(new Date().getTime() - ts1)); // takes usually a few seconds
|
||||
|
||||
UserThread.execute(() -> setupListeners.stream().forEach(SetupListener::onTorNodeReady));
|
||||
|
||||
|
@ -218,7 +223,10 @@ public class TorNetworkNode extends NetworkNode {
|
|||
hiddenServiceSocket = new HiddenServiceSocket(localPort, "", servicePort);
|
||||
hiddenServiceSocket.addReadyListener(socket -> {
|
||||
try {
|
||||
log.info("Tor hidden service published after {} ms. Socked={}", (new Date().getTime() - ts2), socket); //takes usually 30-40 sec
|
||||
log.info("\n################################################################\n" +
|
||||
"Tor hidden service published after {} ms. Socked={}\n" +
|
||||
"################################################################",
|
||||
(new Date().getTime() - ts2), socket); //takes usually 30-40 sec
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
|
|
@ -38,8 +38,12 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
|
||||
private static final int MAX_REPORTED_PEERS = 1000;
|
||||
private static final int MAX_PERSISTED_PEERS = 500;
|
||||
private static final long MAX_AGE = TimeUnit.DAYS.toMillis(14); // max age for reported peers is 14 days
|
||||
// max age for reported peers is 14 days
|
||||
private static final long MAX_AGE = TimeUnit.DAYS.toMillis(14);
|
||||
// Age of what we consider connected peers still as live peers
|
||||
private static final long MAX_AGE_LIVE_PEERS = TimeUnit.MINUTES.toMillis(30);
|
||||
private static final boolean PRINT_REPORTED_PEERS_DETAILS = true;
|
||||
private Set<Peer> latestLivePeers = new HashSet<>();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -159,12 +163,12 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
// Modify this to change the relationships between connection limits.
|
||||
// maxConnections default 12
|
||||
private void setConnectionLimits(int maxConnections) {
|
||||
this.maxConnections = maxConnections; // 12
|
||||
disconnectFromSeedNode = Math.min(6, maxConnections + 1); // 6
|
||||
minConnections = Math.max(1, maxConnections - 4); // 1-8
|
||||
maxConnectionsPeer = maxConnections + 4; // 16
|
||||
maxConnectionsNonDirect = maxConnections + 8; // 20
|
||||
maxConnectionsAbsolute = maxConnections + 18; // 30 -> seedNode with maxConnections=30 -> 48
|
||||
this.maxConnections = maxConnections; // app node 12; seedNode 30
|
||||
minConnections = Math.max(1, (int) Math.round(maxConnections * 0.7)); // app node 1-8; seedNode 21
|
||||
disconnectFromSeedNode = maxConnections; // app node 12; seedNode 30
|
||||
maxConnectionsPeer = Math.max(4, (int) Math.round(maxConnections * 1.3)); // app node 16; seedNode 39
|
||||
maxConnectionsNonDirect = Math.max(8, (int) Math.round(maxConnections * 1.7)); // app node 20; seedNode 51
|
||||
maxConnectionsAbsolute = Math.max(12, (int) Math.round(maxConnections * 2.5)); // app node 30; seedNode 66
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -198,6 +202,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
|
||||
@Override
|
||||
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
||||
log.info("onDisconnect called: nodeAddress={}, closeConnectionReason={}", connection.getPeersNodeAddressOptional(), closeConnectionReason);
|
||||
Log.logIfStressTests("onDisconnect of peer " +
|
||||
(connection.getPeersNodeAddressOptional().isPresent() ? connection.getPeersNodeAddressOptional().get() : "PeersNode unknown") +
|
||||
" / No. of connections: " + networkNode.getAllConnections().size() +
|
||||
|
@ -214,6 +219,9 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
lostAllConnections = networkNode.getAllConnections().isEmpty();
|
||||
if (lostAllConnections) {
|
||||
stopped = true;
|
||||
log.warn("\n------------------------------------------------------------\n" +
|
||||
"All connections lost\n" +
|
||||
"------------------------------------------------------------");
|
||||
listeners.stream().forEach(Listener::onAllConnectionsLost);
|
||||
}
|
||||
|
||||
|
@ -261,10 +269,10 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
Log.traceCall("maxConnections=" + maxConnections);
|
||||
Set<Connection> allConnections = networkNode.getAllConnections();
|
||||
int size = allConnections.size();
|
||||
log.debug("We have {} connections open. Our limit is {}", size, maxConnections);
|
||||
log.info("We have {} connections open. Our limit is {}", size, maxConnections);
|
||||
|
||||
if (size > maxConnections) {
|
||||
log.debug("We have too many connections open.\n\t" +
|
||||
log.info("We have too many connections open. " +
|
||||
"Lets try first to remove the inbound connections of type PEER.");
|
||||
List<Connection> candidates = allConnections.stream()
|
||||
.filter(e -> e instanceof InboundConnection)
|
||||
|
@ -275,7 +283,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
log.debug("No candidates found. We check if we exceed our " +
|
||||
"maxConnectionsPeer limit of {}", maxConnectionsPeer);
|
||||
if (size > maxConnectionsPeer) {
|
||||
log.debug("Lets try to remove ANY connection of type PEER.");
|
||||
log.info("Lets try to remove ANY connection of type PEER.");
|
||||
candidates = allConnections.stream()
|
||||
.filter(e -> e.getPeerType() == Connection.PeerType.PEER)
|
||||
.collect(Collectors.toList());
|
||||
|
@ -284,7 +292,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
log.debug("No candidates found. We check if we exceed our " +
|
||||
"maxConnectionsNonDirect limit of {}", maxConnectionsNonDirect);
|
||||
if (size > maxConnectionsNonDirect) {
|
||||
log.debug("Lets try to remove any connection which is not of type DIRECT_MSG_PEER or INITIAL_DATA_REQUEST.");
|
||||
log.info("Lets try to remove any connection which is not of type DIRECT_MSG_PEER or INITIAL_DATA_REQUEST.");
|
||||
candidates = allConnections.stream()
|
||||
.filter(e -> e.getPeerType() != Connection.PeerType.DIRECT_MSG_PEER && e.getPeerType() != Connection.PeerType.INITIAL_DATA_REQUEST)
|
||||
.collect(Collectors.toList());
|
||||
|
@ -293,7 +301,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
log.debug("No candidates found. We check if we exceed our " +
|
||||
"maxConnectionsAbsolute limit of {}", maxConnectionsAbsolute);
|
||||
if (size > maxConnectionsAbsolute) {
|
||||
log.debug("Lets try to remove any connection.");
|
||||
log.info("We reached abs. max. connections. Lets try to remove ANY connection.");
|
||||
candidates = allConnections.stream().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -304,8 +312,8 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
|
||||
if (!candidates.isEmpty()) {
|
||||
candidates.sort((o1, o2) -> ((Long) o1.getStatistic().getLastActivityTimestamp()).compareTo(((Long) o2.getStatistic().getLastActivityTimestamp())));
|
||||
log.debug("Candidates.size() for shut down=" + candidates.size());
|
||||
Connection connection = candidates.remove(0);
|
||||
log.info("checkMaxConnections: Num candidates for shut down={}. We close oldest connection: {}", candidates.size(), connection);
|
||||
log.debug("We are going to shut down the oldest connection.\n\tconnection=" + connection.toString());
|
||||
if (!connection.isStopped())
|
||||
connection.shutDown(CloseConnectionReason.TOO_MANY_CONNECTIONS_OPEN, () -> UserThread.runAfter(this::checkMaxConnections, 100, TimeUnit.MILLISECONDS));
|
||||
|
@ -313,7 +321,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
} else {
|
||||
log.warn("No candidates found to remove (That case should not be possible as we use in the " +
|
||||
"last case all connections).\n\t" +
|
||||
"allConnections=", allConnections);
|
||||
"allConnections={}", allConnections);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
@ -395,7 +403,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
return reportedPeers;
|
||||
}
|
||||
|
||||
public void addToReportedPeers(HashSet<Peer> reportedPeersToAdd, Connection connection) {
|
||||
public void addToReportedPeers(Set<Peer> reportedPeersToAdd, Connection connection) {
|
||||
printNewReportedPeers(reportedPeersToAdd);
|
||||
|
||||
// We check if the reported msg is not violating our rules
|
||||
|
@ -419,11 +427,10 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
private void purgeReportedPeersIfExceeds() {
|
||||
Log.traceCall();
|
||||
int size = reportedPeers.size();
|
||||
int limit = MAX_REPORTED_PEERS - maxConnectionsAbsolute;
|
||||
if (size > limit) {
|
||||
log.trace("We have already {} reported peers which exceeds our limit of {}." +
|
||||
"We remove random peers from the reported peers list.", size, limit);
|
||||
int diff = size - limit;
|
||||
if (size > MAX_REPORTED_PEERS) {
|
||||
log.info("We have already {} reported peers which exceeds our limit of {}." +
|
||||
"We remove random peers from the reported peers list.", size, MAX_REPORTED_PEERS);
|
||||
int diff = size - MAX_REPORTED_PEERS;
|
||||
List<Peer> list = new ArrayList<>(reportedPeers);
|
||||
// we dont use sorting by lastActivityDate to keep it more random
|
||||
for (int i = 0; i < diff; i++) {
|
||||
|
@ -448,11 +455,11 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
result.append("\n------------------------------------------------------------\n");
|
||||
log.debug(result.toString());
|
||||
}
|
||||
log.info("Number of reported peers: {}", reportedPeers.size());
|
||||
log.debug("Number of reported peers: {}", reportedPeers.size());
|
||||
}
|
||||
}
|
||||
|
||||
private void printNewReportedPeers(HashSet<Peer> reportedPeers) {
|
||||
private void printNewReportedPeers(Set<Peer> reportedPeers) {
|
||||
//noinspection ConstantConditions
|
||||
if (PRINT_REPORTED_PEERS_DETAILS) {
|
||||
StringBuilder result = new StringBuilder("We received new reportedPeers:");
|
||||
|
@ -570,6 +577,7 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
|
||||
public void handleConnectionFault(NodeAddress nodeAddress, @Nullable Connection connection) {
|
||||
Log.traceCall("nodeAddress=" + nodeAddress);
|
||||
log.debug("handleConnectionFault called: nodeAddress=" + nodeAddress);
|
||||
boolean doRemovePersistedPeer = false;
|
||||
removeReportedPeer(nodeAddress);
|
||||
Optional<Peer> persistedPeerOptional = getPersistedPeerOptional(nodeAddress);
|
||||
|
@ -600,10 +608,22 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
.ifPresent(connection -> connection.shutDown(closeConnectionReason));
|
||||
}
|
||||
|
||||
public HashSet<Peer> getConnectedNonSeedNodeReportedPeers(NodeAddress excludedNodeAddress) {
|
||||
return new HashSet<>(getConnectedNonSeedNodeReportedPeers().stream()
|
||||
// Delivers the live peers from the last 30 min (MAX_AGE_LIVE_PEERS)
|
||||
// We include older peers to avoid risks for network partitioning
|
||||
public Set<Peer> getLivePeers(NodeAddress excludedNodeAddress) {
|
||||
int oldNumLatestLivePeers = latestLivePeers.size();
|
||||
Set<Peer> currentLivePeers = new HashSet<>(getConnectedReportedPeers().stream()
|
||||
.filter(e -> !isSeedNode(e))
|
||||
.filter(e -> !e.getNodeAddress().equals(excludedNodeAddress))
|
||||
.collect(Collectors.toSet()));
|
||||
latestLivePeers.addAll(currentLivePeers);
|
||||
long maxAge = new Date().getTime() - MAX_AGE_LIVE_PEERS;
|
||||
latestLivePeers = latestLivePeers.stream()
|
||||
.filter(peer -> peer.getDate().getTime() > maxAge)
|
||||
.collect(Collectors.toSet());
|
||||
if (oldNumLatestLivePeers != latestLivePeers.size())
|
||||
log.info("Num of latestLivePeers={}, latestLivePeers={}", latestLivePeers.size(), latestLivePeers);
|
||||
return latestLivePeers;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -618,12 +638,6 @@ public class PeerManager implements ConnectionListener, PersistedDataHost {
|
|||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private HashSet<Peer> getConnectedNonSeedNodeReportedPeers() {
|
||||
return new HashSet<>(getConnectedReportedPeers().stream()
|
||||
.filter(e -> !isSeedNode(e))
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
private void stopCheckMaxConnectionsTimer() {
|
||||
if (checkMaxConnectionsTimer != null) {
|
||||
checkMaxConnectionsTimer.stop();
|
||||
|
|
|
@ -80,7 +80,7 @@ public class GetDataRequestHandler {
|
|||
|
||||
if (timeoutTimer == null) {
|
||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||
String errorMessage = "A timeout occurred for getDataResponse:" + getDataResponse +
|
||||
String errorMessage = "A timeout occurred for getDataResponse " +
|
||||
" on connection:" + connection;
|
||||
handleFault(errorMessage, CloseConnectionReason.SEND_MSG_TIMEOUT, connection);
|
||||
},
|
||||
|
|
|
@ -281,7 +281,7 @@ class RequestDataHandler implements MessageListener {
|
|||
log.warn("We have stopped already. We ignore that onDataRequest call.");
|
||||
}
|
||||
} else {
|
||||
log.warn("We got a message from another connection and ignore it. That should never happen.");
|
||||
log.debug("We got the message from another connection and ignore it on that handler. That is expected if we have several requests open.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package io.bisq.network.p2p.peers.getdata;
|
||||
|
||||
import com.google.inject.name.Named;
|
||||
import io.bisq.common.Timer;
|
||||
import io.bisq.common.UserThread;
|
||||
import io.bisq.common.app.Log;
|
||||
import io.bisq.common.proto.network.NetworkEnvelope;
|
||||
import io.bisq.network.NetworkOptionKeys;
|
||||
import io.bisq.network.p2p.NodeAddress;
|
||||
import io.bisq.network.p2p.network.*;
|
||||
import io.bisq.network.p2p.peers.PeerManager;
|
||||
|
@ -16,6 +18,7 @@ import org.jetbrains.annotations.Nullable;
|
|||
|
||||
import javax.inject.Inject;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
@ -24,6 +27,10 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||
public class RequestDataManager implements MessageListener, ConnectionListener, PeerManager.Listener {
|
||||
private static final long RETRY_DELAY_SEC = 10;
|
||||
private static final long CLEANUP_TIMER = 120;
|
||||
// How many seeds we request the PreliminaryGetDataRequest from
|
||||
private static int NUM_SEEDS_FOR_PRELIMINARY_REQUEST = 2;
|
||||
// how many seeds additional to the first responding PreliminaryGetDataRequest seed we request the GetUpdatedDataRequest from
|
||||
private static int NUM_ADDITIONAL_SEEDS_FOR_UPDATE_REQUEST = 1;
|
||||
private boolean isPreliminaryDataRequest = true;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -69,7 +76,8 @@ public class RequestDataManager implements MessageListener, ConnectionListener,
|
|||
public RequestDataManager(NetworkNode networkNode,
|
||||
SeedNodesRepository seedNodesRepository,
|
||||
P2PDataStorage dataStorage,
|
||||
PeerManager peerManager) {
|
||||
PeerManager peerManager,
|
||||
@javax.annotation.Nullable @Named(NetworkOptionKeys.MY_ADDRESS) String myAddress) {
|
||||
this.networkNode = networkNode;
|
||||
this.dataStorage = dataStorage;
|
||||
this.peerManager = peerManager;
|
||||
|
@ -79,6 +87,15 @@ public class RequestDataManager implements MessageListener, ConnectionListener,
|
|||
this.peerManager.addListener(this);
|
||||
|
||||
this.seedNodeAddresses = new HashSet<>(seedNodesRepository.getSeedNodeAddresses());
|
||||
|
||||
// If we are a seed node we use more redundancy at startup to be sure we get all data.
|
||||
// We cannot use networkNode.getNodeAddress() as nodeAddress as that is null at this point, so we use
|
||||
// new NodeAddress(myAddress) for checking if we are a seed node.
|
||||
// seedNodeAddresses do not contain my own address as that gets filtered out
|
||||
if (myAddress != null && !myAddress.isEmpty() && seedNodesRepository.isSeedNode(new NodeAddress(myAddress))) {
|
||||
NUM_SEEDS_FOR_PRELIMINARY_REQUEST = 3;
|
||||
NUM_ADDITIONAL_SEEDS_FOR_UPDATE_REQUEST = 2;
|
||||
}
|
||||
}
|
||||
|
||||
public void shutDown() {
|
||||
|
@ -105,10 +122,15 @@ public class RequestDataManager implements MessageListener, ConnectionListener,
|
|||
ArrayList<NodeAddress> nodeAddresses = new ArrayList<>(seedNodeAddresses);
|
||||
if (!nodeAddresses.isEmpty()) {
|
||||
Collections.shuffle(nodeAddresses);
|
||||
NodeAddress nextCandidate = nodeAddresses.get(0);
|
||||
nodeAddresses.remove(nextCandidate);
|
||||
ArrayList<NodeAddress> finalNodeAddresses = new ArrayList<>(nodeAddresses);
|
||||
final int size = Math.min(NUM_SEEDS_FOR_PRELIMINARY_REQUEST, finalNodeAddresses.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
NodeAddress nodeAddress = finalNodeAddresses.get(i);
|
||||
nodeAddresses.remove(nodeAddress);
|
||||
UserThread.runAfter(() -> requestData(nodeAddress, nodeAddresses), (i * 200 + 1), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
isPreliminaryDataRequest = true;
|
||||
requestData(nextCandidate, nodeAddresses);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -119,13 +141,28 @@ public class RequestDataManager implements MessageListener, ConnectionListener,
|
|||
Log.traceCall();
|
||||
checkArgument(nodeAddressOfPreliminaryDataRequest.isPresent(), "nodeAddressOfPreliminaryDataRequest must be present");
|
||||
dataUpdateRequested = true;
|
||||
List<NodeAddress> remainingNodeAddresses = new ArrayList<>(seedNodeAddresses);
|
||||
if (!remainingNodeAddresses.isEmpty()) {
|
||||
Collections.shuffle(remainingNodeAddresses);
|
||||
isPreliminaryDataRequest = false;
|
||||
List<NodeAddress> nodeAddresses = new ArrayList<>(seedNodeAddresses);
|
||||
if (!nodeAddresses.isEmpty()) {
|
||||
// We use the node we have already connected to to request again
|
||||
NodeAddress candidate = nodeAddressOfPreliminaryDataRequest.get();
|
||||
remainingNodeAddresses.remove(candidate);
|
||||
isPreliminaryDataRequest = false;
|
||||
requestData(candidate, remainingNodeAddresses);
|
||||
nodeAddresses.remove(candidate);
|
||||
requestData(candidate, nodeAddresses);
|
||||
|
||||
// For more redundancy we request as well from other random nodes.
|
||||
Collections.shuffle(nodeAddresses);
|
||||
ArrayList<NodeAddress> finalNodeAddresses = new ArrayList<>(nodeAddresses);
|
||||
int numRequests = 0;
|
||||
for (int i = 0; i < finalNodeAddresses.size() && numRequests < NUM_ADDITIONAL_SEEDS_FOR_UPDATE_REQUEST; i++) {
|
||||
NodeAddress nodeAddress = finalNodeAddresses.get(i);
|
||||
nodeAddresses.remove(nodeAddress);
|
||||
|
||||
// It might be that we have a prelim. request open for the same seed, if so we skip to the next.
|
||||
if (!handlerMap.containsKey(nodeAddress)) {
|
||||
UserThread.runAfter(() -> requestData(nodeAddress, nodeAddresses), (i * 200 + 1), TimeUnit.MILLISECONDS);
|
||||
numRequests++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,7 +304,10 @@ public class RequestDataManager implements MessageListener, ConnectionListener,
|
|||
// 1. We get a response from requestPreliminaryData
|
||||
if (!nodeAddressOfPreliminaryDataRequest.isPresent()) {
|
||||
nodeAddressOfPreliminaryDataRequest = Optional.of(nodeAddress);
|
||||
listener.onPreliminaryDataReceived();
|
||||
// We delay because it can be that we get the HS published before we receive the
|
||||
// preliminary data and the onPreliminaryDataReceived call triggers the
|
||||
// dataUpdateRequested set to true, so we would also call the onUpdatedDataReceived.
|
||||
UserThread.runAfter(listener::onPreliminaryDataReceived, 100 , TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// 2. Later we get a response from requestUpdatesData
|
||||
|
|
|
@ -68,7 +68,7 @@ class GetPeersRequestHandler {
|
|||
checkArgument(connection.getPeersNodeAddressOptional().isPresent(),
|
||||
"The peers address must have been already set at the moment");
|
||||
GetPeersResponse getPeersResponse = new GetPeersResponse(getPeersRequest.getNonce(),
|
||||
peerManager.getConnectedNonSeedNodeReportedPeers(connection.getPeersNodeAddressOptional().get()));
|
||||
peerManager.getLivePeers(connection.getPeersNodeAddressOptional().get()));
|
||||
|
||||
checkArgument(timeoutTimer == null, "onGetPeersRequest must not be called twice.");
|
||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||
|
|
|
@ -81,10 +81,10 @@ class PeerExchangeHandler implements MessageListener {
|
|||
|
||||
private void sendGetPeersRequest(NodeAddress nodeAddress) {
|
||||
Log.traceCall("nodeAddress=" + nodeAddress + " / this=" + this);
|
||||
log.info("sendGetPeersRequest to nodeAddress={}", nodeAddress);
|
||||
log.debug("sendGetPeersRequest to nodeAddress={}", nodeAddress);
|
||||
if (!stopped) {
|
||||
if (networkNode.getNodeAddress() != null) {
|
||||
GetPeersRequest getPeersRequest = new GetPeersRequest(networkNode.getNodeAddress(), nonce, peerManager.getConnectedNonSeedNodeReportedPeers(nodeAddress));
|
||||
GetPeersRequest getPeersRequest = new GetPeersRequest(networkNode.getNodeAddress(), nonce, peerManager.getLivePeers(nodeAddress));
|
||||
if (timeoutTimer == null) {
|
||||
timeoutTimer = UserThread.runAfter(() -> { // setup before sending to avoid race conditions
|
||||
if (!stopped) {
|
||||
|
|
|
@ -104,7 +104,7 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener,
|
|||
|
||||
@Override
|
||||
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
||||
Log.traceCall();
|
||||
log.info("onDisconnect closeConnectionReason={}, nodeAddressOpt={}", closeConnectionReason, connection.getPeersNodeAddressOptional());
|
||||
closeHandler(connection);
|
||||
|
||||
if (retryTimer == null) {
|
||||
|
@ -196,7 +196,7 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener,
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void requestReportedPeers(NodeAddress nodeAddress, List<NodeAddress> remainingNodeAddresses) {
|
||||
Log.traceCall("nodeAddress=" + nodeAddress);
|
||||
log.debug("requestReportedPeers nodeAddress={}; remainingNodeAddresses.size={}" , nodeAddress,remainingNodeAddresses.size());
|
||||
if (!stopped) {
|
||||
if (!handlerMap.containsKey(nodeAddress)) {
|
||||
PeerExchangeHandler peerExchangeHandler = new PeerExchangeHandler(networkNode,
|
||||
|
@ -210,7 +210,7 @@ public class PeerExchangeManager implements MessageListener, ConnectionListener,
|
|||
|
||||
@Override
|
||||
public void onFault(String errorMessage, @Nullable Connection connection) {
|
||||
log.info("PeerExchangeHandshake of outbound connection failed.\n\terrorMessage={}\n\t" +
|
||||
log.debug("PeerExchangeHandshake of outbound connection failed.\n\terrorMessage={}\n\t" +
|
||||
"nodeAddress={}", errorMessage, nodeAddress);
|
||||
|
||||
peerManager.handleConnectionFault(nodeAddress);
|
||||
|
|
|
@ -15,6 +15,7 @@ import javax.annotation.Nullable;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
@ -24,11 +25,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
|||
public final class GetPeersRequest extends NetworkEnvelope implements PeerExchangeMessage, SendersNodeAddressMessage, SupportedCapabilitiesMessage {
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final int nonce;
|
||||
private final HashSet<Peer> reportedPeers;
|
||||
private final Set<Peer> reportedPeers;
|
||||
@Nullable
|
||||
private final List<Integer> supportedCapabilities;
|
||||
|
||||
public GetPeersRequest(NodeAddress senderNodeAddress, int nonce, HashSet<Peer> reportedPeers) {
|
||||
public GetPeersRequest(NodeAddress senderNodeAddress, int nonce, Set<Peer> reportedPeers) {
|
||||
this(senderNodeAddress, nonce, reportedPeers, Capabilities.getSupportedCapabilities(), Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
@ -39,7 +40,7 @@ public final class GetPeersRequest extends NetworkEnvelope implements PeerExchan
|
|||
|
||||
private GetPeersRequest(NodeAddress senderNodeAddress,
|
||||
int nonce,
|
||||
HashSet<Peer> reportedPeers,
|
||||
Set<Peer> reportedPeers,
|
||||
@Nullable List<Integer> supportedCapabilities,
|
||||
int messageVersion) {
|
||||
super(messageVersion);
|
||||
|
|
|
@ -14,17 +14,18 @@ import javax.annotation.Nullable;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Value
|
||||
public final class GetPeersResponse extends NetworkEnvelope implements PeerExchangeMessage, SupportedCapabilitiesMessage {
|
||||
private final int requestNonce;
|
||||
private final HashSet<Peer> reportedPeers;
|
||||
private final Set<Peer> reportedPeers;
|
||||
@Nullable
|
||||
private final List<Integer> supportedCapabilities;
|
||||
|
||||
public GetPeersResponse(int requestNonce, HashSet<Peer> reportedPeers) {
|
||||
public GetPeersResponse(int requestNonce, Set<Peer> reportedPeers) {
|
||||
this(requestNonce, reportedPeers, Capabilities.getSupportedCapabilities(), Version.getP2PMessageVersion());
|
||||
}
|
||||
|
||||
|
@ -34,7 +35,7 @@ public final class GetPeersResponse extends NetworkEnvelope implements PeerExcha
|
|||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private GetPeersResponse(int requestNonce,
|
||||
HashSet<Peer> reportedPeers,
|
||||
Set<Peer> reportedPeers,
|
||||
@Nullable List<Integer> supportedCapabilities,
|
||||
int messageVersion) {
|
||||
super(messageVersion);
|
||||
|
|
|
@ -170,7 +170,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
|
|||
ProtectedStorageEntry protectedStorageEntry = map.get(hashOfPayload);
|
||||
if (!(protectedStorageEntry.getProtectedStoragePayload() instanceof PersistableNetworkPayload)) {
|
||||
toRemoveSet.add(protectedStorageEntry);
|
||||
log.debug("We found an expired data entry. We remove the protectedData:\n\t" + Utilities.toTruncatedString(protectedStorageEntry));
|
||||
log.info("We found an expired data entry. We remove the protectedDataPayload:\n\t" + Utilities.toTruncatedString(protectedStorageEntry.getProtectedStoragePayload(), 100));
|
||||
map.remove(hashOfPayload);
|
||||
}
|
||||
});
|
||||
|
@ -255,8 +255,11 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
|
|||
// TODO investigate what causes the disconnections.
|
||||
// Usually the are: SOCKET_TIMEOUT ,TERMINATED (EOFException)
|
||||
protectedData.backDate();
|
||||
if (protectedData.isExpired())
|
||||
if (protectedData.isExpired()) {
|
||||
log.warn("We found an expired data entry which we have already back dated. " +
|
||||
"We remove the protectedStoragePayload:\n\t" + Utilities.toTruncatedString(protectedData.getProtectedStoragePayload(), 100));
|
||||
doRemoveProtectedExpirableData(protectedData, hashOfPayload);
|
||||
}
|
||||
} else {
|
||||
log.debug("Remove data ignored as we don't have an entry for that data.");
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -6,7 +6,7 @@ mkdir -p gui/deploy
|
|||
set -e
|
||||
|
||||
# Edit version
|
||||
version=0.6.2
|
||||
version=0.6.4
|
||||
|
||||
dir="/media/sf_vm_shared_ubuntu14_32bit"
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ mkdir -p gui/deploy
|
|||
set -e
|
||||
|
||||
# Edit version
|
||||
version=0.6.2
|
||||
version=0.6.4
|
||||
|
||||
dir="/media/sf_vm_shared_ubuntu"
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
# pull base image
|
||||
FROM openjdk:8-jdk
|
||||
ENV version 0.6.2
|
||||
ENV version 0.6.4
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* &&
|
||||
apt-get install -y vim fakeroot
|
||||
|
|
|
@ -5,7 +5,7 @@ mkdir -p gui/deploy
|
|||
|
||||
set -e
|
||||
|
||||
version="0.6.2"
|
||||
version="0.6.4"
|
||||
|
||||
mvn clean package verify -DskipTests -Dmaven.javadoc.skip=true
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
version="0.6.2"
|
||||
version="0.6.4"
|
||||
|
||||
target_dir="/Users/dev/Documents/__bisq/_releases/$version"
|
||||
src_dir="/Users/dev/Documents/intellij/exchange_bisq"
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
:: 32 bit build
|
||||
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
|
||||
|
||||
SET version=0.6.2
|
||||
SET version=0.6.4
|
||||
|
||||
:: Private setup
|
||||
SET outdir=\\VBOXSVR\vm_shared_windows_32bit
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
:: 64 bit build
|
||||
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
|
||||
|
||||
SET version=0.6.2
|
||||
SET version=0.6.4
|
||||
|
||||
:: Private setup
|
||||
SET outdir=\\VBOXSVR\vm_shared_windows
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue