From ca88bc9b4ff3aa97cb3b509093f58b42ebc33b8c Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Thu, 3 Sep 2020 17:34:34 +0200 Subject: [PATCH 001/108] Bump version number for v1.3.8 --- build.gradle | 2 +- common/src/main/java/bisq/common/app/Version.java | 2 +- desktop/package/linux/Dockerfile | 2 +- desktop/package/linux/package.sh | 2 +- desktop/package/linux/release.sh | 2 +- desktop/package/macosx/Info.plist | 4 ++-- desktop/package/macosx/create_app.sh | 2 +- desktop/package/macosx/finalize.sh | 2 +- desktop/package/macosx/insert_snapshot_version.sh | 2 +- desktop/package/macosx/replace_version_number.sh | 4 ++-- desktop/package/windows/package.bat | 2 +- desktop/package/windows/release.bat | 2 +- relay/src/main/resources/version.txt | 2 +- seednode/src/main/java/bisq/seednode/SeedNodeMain.java | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index c82ff3336ad..932644c115d 100644 --- a/build.gradle +++ b/build.gradle @@ -376,7 +376,7 @@ configure(project(':desktop')) { apply plugin: 'witness' apply from: '../gradle/witness/gradle-witness.gradle' - version = '1.3.7' + version = '1.3.8' mainClassName = 'bisq.desktop.app.BisqAppMain' diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index 926c361e4f4..898228f3def 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -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 = "1.3.7"; + public static final String VERSION = "1.3.8"; public static int getMajorVersion(String version) { return getSubVersion(version, 0); diff --git a/desktop/package/linux/Dockerfile b/desktop/package/linux/Dockerfile index b0c1bd474b6..1838bc7a537 100644 --- a/desktop/package/linux/Dockerfile +++ b/desktop/package/linux/Dockerfile @@ -8,7 +8,7 @@ # pull base image FROM openjdk:8-jdk -ENV version 1.3.7 +ENV version 1.3.8 RUN apt-get update && apt-get install -y --no-install-recommends openjfx && rm -rf /var/lib/apt/lists/* && apt-get install -y vim fakeroot diff --git a/desktop/package/linux/package.sh b/desktop/package/linux/package.sh index 61975412a32..0e395990544 100755 --- a/desktop/package/linux/package.sh +++ b/desktop/package/linux/package.sh @@ -6,7 +6,7 @@ # - Update version below # - Ensure JAVA_HOME below is pointing to OracleJDK 10 directory -version=1.3.7 +version=1.3.8 version_base=$(echo $version | awk -F'[_-]' '{print $1}') if [ ! -f "$JAVA_HOME/bin/javapackager" ]; then if [ -d "/usr/lib/jvm/jdk-10.0.2" ]; then diff --git a/desktop/package/linux/release.sh b/desktop/package/linux/release.sh index 0eefb6a521f..3ba92885a0b 100755 --- a/desktop/package/linux/release.sh +++ b/desktop/package/linux/release.sh @@ -4,7 +4,7 @@ # Prior to running this script: # - Update version below -version=1.3.7 +version=1.3.8 base_dir=$( cd "$(dirname "$0")" ; pwd -P )/../../.. package_dir=$base_dir/desktop/package release_dir=$base_dir/desktop/release/$version diff --git a/desktop/package/macosx/Info.plist b/desktop/package/macosx/Info.plist index 7125f93a83d..bcdbedce14e 100644 --- a/desktop/package/macosx/Info.plist +++ b/desktop/package/macosx/Info.plist @@ -5,10 +5,10 @@ CFBundleVersion - 1.3.7 + 1.3.8 CFBundleShortVersionString - 1.3.7 + 1.3.8 CFBundleExecutable Bisq diff --git a/desktop/package/macosx/create_app.sh b/desktop/package/macosx/create_app.sh index 2938ac7bcd2..57b0cb4bda8 100755 --- a/desktop/package/macosx/create_app.sh +++ b/desktop/package/macosx/create_app.sh @@ -6,7 +6,7 @@ mkdir -p deploy set -e -version="1.3.7" +version="1.3.8" cd .. ./gradlew :desktop:build -x test shadowJar diff --git a/desktop/package/macosx/finalize.sh b/desktop/package/macosx/finalize.sh index 16ed7856ce1..59df06eaa1c 100755 --- a/desktop/package/macosx/finalize.sh +++ b/desktop/package/macosx/finalize.sh @@ -2,7 +2,7 @@ cd ../../ -version="1.3.7" +version="1.3.8" target_dir="releases/$version" diff --git a/desktop/package/macosx/insert_snapshot_version.sh b/desktop/package/macosx/insert_snapshot_version.sh index 9d365421a3e..4e214a5581a 100755 --- a/desktop/package/macosx/insert_snapshot_version.sh +++ b/desktop/package/macosx/insert_snapshot_version.sh @@ -2,7 +2,7 @@ cd $(dirname $0)/../../../ -version=1.3.6 +version=1.3.7 find . -type f \( -name "finalize.sh" \ -o -name "create_app.sh" \ diff --git a/desktop/package/macosx/replace_version_number.sh b/desktop/package/macosx/replace_version_number.sh index 9f21dcae758..33f30a5440d 100755 --- a/desktop/package/macosx/replace_version_number.sh +++ b/desktop/package/macosx/replace_version_number.sh @@ -2,8 +2,8 @@ cd $(dirname $0)/../../../ -oldVersion=1.3.6 -newVersion=1.3.7 +oldVersion=1.3.7 +newVersion=1.3.8 find . -type f \( -name "finalize.sh" \ -o -name "create_app.sh" \ diff --git a/desktop/package/windows/package.bat b/desktop/package/windows/package.bat index 6103e8ebbe6..7f4787f5a97 100644 --- a/desktop/package/windows/package.bat +++ b/desktop/package/windows/package.bat @@ -11,7 +11,7 @@ @echo off -set version=1.3.7 +set version=1.3.8 if not exist "%JAVA_HOME%\bin\javapackager.exe" ( if not exist "%ProgramFiles%\Java\jdk-10.0.2" ( echo Javapackager not found. Update JAVA_HOME variable to point to OracleJDK. diff --git a/desktop/package/windows/release.bat b/desktop/package/windows/release.bat index 6b4b09ed0af..f6b98fbc5f4 100644 --- a/desktop/package/windows/release.bat +++ b/desktop/package/windows/release.bat @@ -6,7 +6,7 @@ @echo off -set version=1.3.7 +set version=1.3.8 set release_dir=%~dp0..\..\..\releases\%version% set package_dir=%~dp0.. diff --git a/relay/src/main/resources/version.txt b/relay/src/main/resources/version.txt index 3336003dccd..e05cb3329c6 100644 --- a/relay/src/main/resources/version.txt +++ b/relay/src/main/resources/version.txt @@ -1 +1 @@ -1.3.7 +1.3.8 diff --git a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java index 3db42fa5858..ffa6ac667ed 100644 --- a/seednode/src/main/java/bisq/seednode/SeedNodeMain.java +++ b/seednode/src/main/java/bisq/seednode/SeedNodeMain.java @@ -33,7 +33,7 @@ @Slf4j public class SeedNodeMain extends ExecutableForAppWithP2p { - private static final String VERSION = "1.3.7"; + private static final String VERSION = "1.3.8"; private SeedNode seedNode; public SeedNodeMain() { From d816967dfe2ebf5e4ce952e83cfd2cfe325a8a6b Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Thu, 3 Sep 2020 17:35:38 +0200 Subject: [PATCH 002/108] Update translations for v1.3.8 --- .../i18n/displayStrings_de.properties | 70 ++++++++++++------ .../i18n/displayStrings_es.properties | 72 +++++++++++++------ .../i18n/displayStrings_fa.properties | 70 ++++++++++++------ .../i18n/displayStrings_fr.properties | 70 ++++++++++++------ .../i18n/displayStrings_ja.properties | 70 ++++++++++++------ .../i18n/displayStrings_pt-br.properties | 70 ++++++++++++------ .../i18n/displayStrings_pt.properties | 70 ++++++++++++------ .../i18n/displayStrings_ru.properties | 70 ++++++++++++------ .../i18n/displayStrings_th.properties | 70 ++++++++++++------ .../i18n/displayStrings_vi.properties | 70 ++++++++++++------ .../i18n/displayStrings_zh-hans.properties | 70 ++++++++++++------ .../i18n/displayStrings_zh-hant.properties | 70 ++++++++++++------ 12 files changed, 601 insertions(+), 241 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties index 6ddcb544d8b..6f0ceac2fca 100644 --- a/core/src/main/resources/i18n/displayStrings_de.properties +++ b/core/src/main/resources/i18n/displayStrings_de.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediation support.tab.arbitration.support=Vermittlung support.tab.legacyArbitration.support=Legacy-Vermittlung support.tab.ArbitratorsSupportTickets={0} Tickets -support.filter=Liste filtern +support.filter=Search disputes support.filter.prompt=Tragen sie Handel ID, Datum, Onion Adresse oder Kontodaten +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Private Benachrichtigung senden +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=Keine offenen Tickets vorhanden support.sendingMessage=Nachricht wird gesendet... support.receiverNotOnline=Empfänger ist nicht online. Nachricht wird in der Mailbox gespeichert. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=Sie haben um Mediation gebeten.\n\n{0}\n\nB support.peerOpenedTicket=Ihr Trading-Partner hat aufgrund technischer Probleme Unterstützung angefordert.\n\n{0}\n\nBisq-Version: {1} support.peerOpenedDispute=Ihr Trading-Partner hat einen Konflikt eröffnet.\n\n{0}\n\nBisq-Version: {1} support.peerOpenedDisputeForMediation=Ihr Trading-Partner hat eine Mediation beantragt.\n\n{0}\n\nBisq-Version: {1} -support.mediatorsDisputeSummary=Systemmeldung:\nKonflikt-Zusammenfassung des Mediators:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Node-Adresse des Mediators: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Verbundene Peers settings.net.onionAddressColumn=Onion-Adresse settings.net.creationDateColumn=Eingerichtet settings.net.connectionTypeColumn=Ein/Aus -settings.net.totalTrafficLabel=Gesamter Verkehr +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Umlaufzeit settings.net.sentBytesColumn=Gesendet settings.net.receivedBytesColumn=Erhalten @@ -989,7 +995,8 @@ settings.net.heightColumn=Höhe settings.net.needRestart=Sie müssen die Anwendung neustarten, um die Änderungen anzuwenden.\nMöchten Sie jetzt neustarten? settings.net.notKnownYet=Noch nicht bekannt... -settings.net.sentReceived=Gesendet: {0}, erhalten: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[IP Adresse:Port | Hostname:Port | Onion-Adresse:Port] (Komma getrennt). Port kann weggelassen werden, wenn Standard genutzt wird (8333). settings.net.seedNode=Seed-Knoten settings.net.directPeer=Peer (direkt) @@ -1011,8 +1018,8 @@ setting.about.support=Bisq unterstützen setting.about.def=Bisq ist keine Firma, sondern ein Gemeinschaftsprojekt, das offen für Mitwirken ist. Wenn Sie an Bisq mitwirken oder das Projekt unterstützen wollen, folgen Sie bitte den unten stehenden Links. setting.about.contribute=Mitwirken setting.about.providers=Datenanbieter -setting.about.apisWithFee=Bisq nutzt für Fiatgeld- und Altcoin-Marktpreise sowie geschätzte Mining-Gebühren die APIs 3tr. -setting.about.apis=Bisq nutzt für Fiatgeld- und Altcoin-Marktpreise die APIs 3tr. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Marktpreise zur Verfügung gestellt von setting.about.feeEstimation.label=Geschätzte Mining-Gebühr bereitgestellt von setting.about.versionDetails=Versionsdetails @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Öffnen Sie die Peer-Info account.tab.arbitratorRegistration=Vermittler-Registrierung account.tab.mediatorRegistration=Mediator-Registrierung account.tab.refundAgentRegistration=Registrierung des Rückerstattungsbeauftragten +account.tab.signing=Signing account.tab.account=Konto account.info.headline=Willkommen in Ihrem Bisq-Konto account.info.msg=Hier können Sie Trading-Konten für nationale Währungen und Altcoins hinzufügen und Backups für Ihre Wallets & Kontodaten erstellen.\n\nEine leere Bitcoin-Wallet wurde erstellt, als Sie das erste Mal Bisq gestartet haben.\n\nWir empfehlen, dass Sie Ihre Bitcoin-Wallet-Seed-Wörter aufschreiben (siehe Tab oben) und sich überlegen ein Passwort hinzuzufügen, bevor Sie einzahlen. Bitcoin-Einzahlungen und Auszahlungen werden unter \"Gelder\" verwaltet.\n\nHinweis zu Privatsphäre & Sicherheit: da Bisq eine dezentralisierte Börse ist, bedeutet dies, dass all Ihre Daten auf ihrem Computer bleiben. Es gibt keine Server und wir haben keinen Zugriff auf Ihre persönlichen Informationen, Ihre Gelder oder selbst Ihre IP-Adresse. Daten wie Bankkontonummern, Altcoin- & Bitcoinadressen, etc werden nur mit Ihrem Trading-Partner geteilt, um Trades abzuschließen, die Sie initiiert haben (im Falle eines Konflikts wird der Vermittler die selben Daten sehen wie Ihr Trading-Partner). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Auszahlungsbetrag des Käufers disputeSummaryWindow.payoutAmount.seller=Auszahlungsbetrag des Verkäufers disputeSummaryWindow.payoutAmount.invert=Verlierer als Veröffentlicher nutzen disputeSummaryWindow.reason=Grund des Konflikts -disputeSummaryWindow.reason.bug=Fehler -disputeSummaryWindow.reason.usability=Nutzbarkeit -disputeSummaryWindow.reason.protocolViolation=Protokollverletzung -disputeSummaryWindow.reason.noReply=Keine Antwort -disputeSummaryWindow.reason.scam=Betrug -disputeSummaryWindow.reason.other=Andere -disputeSummaryWindow.reason.bank=Bank + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Fehler +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Nutzbarkeit +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Protokollverletzung +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Keine Antwort +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Betrug +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Andere +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Bank +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Zusammenfassende Anmerkungen disputeSummaryWindow.addSummaryNotes=Zusammenfassende Anmerkungen hinzufügen disputeSummaryWindow.close.button=Ticket schließen -disputeSummaryWindow.close.msg=Ticket geschlossen am {0}\n\nZusammenfassung:\nAuszahlungsbetrag für BTC-Käufer: {1}\nAuszahlungsbetrag für BTC-Verkäufer: {2}\n\nZusammenfassende Hinweise:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nNächste Schritte:\nTrade öffnen und Empfehlung des Mediators akzeptieren oder ablehnen disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNächste Schritte:\nEs sind keine weiteren Schritte Ihrerseits erforderlich. Wenn der Vermittler sich zu Ihren Gunsten entschieden hat, sehen Sie unter Fonds/Transaktionen eine Transaktion "Rückerstattung aus dem Vermittlungsverfahren" disputeSummaryWindow.close.closePeer=Sie müssen auch das Ticket des Handelspartners schließen! @@ -1985,7 +2013,8 @@ filterWindow.onions=Herausgefilterte Onion-Adressen (durch Kommas getrennt) filterWindow.accounts=Herausgefilterte Handelskonten Daten:\nFormat: Komma getrennte Liste von [Zahlungsmethoden ID | Datenfeld | Wert] filterWindow.bannedCurrencies=Herausgefilterte Währungscodes (durch Kommas getrennt) filterWindow.bannedPaymentMethods=Herausgefilterte Zahlungsmethoden-IDs (durch Kommas getrennt) -filterWindow.bannedSignerPubKeys=Gefilterte Unterzeichner-Pubkeys (Komma getr. Hex der Pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Gefilterte Vermittler (mit Komma getr. Onion-Adressen) filterWindow.mediators=Gefilterte Mediatoren (mit Komma getr. Onion-Adressen) filterWindow.refundAgents=Gefilterte Rückerstattungsagenten (mit Komma getr. Onion-Adressen) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=Sie haben nicht genügend BTC-Gelder, popup.warning.bsqChangeBelowDustException=Diese Transaktion erzeugt eine BSQ-Wechselgeld-Ausgabe, die unter dem Dust-Limit (5.46 BSQ) liegt und vom Bitcoin-Netzwerk abgelehnt werden würde.\n\nSie müssen entweder einen höheren Betrag senden, um die Wechselgeld-Ausgabe zu vermeiden (z.B. indem Sie den Dust-Betrag zu Ihrem Sende-Betrag hinzufügen) oder mehr BSQ-Guthaben zu Ihrer Wallet hinzufügen, damit Sie vermeiden, eine Dust-Ausgabe zu generieren.\n\nDie Dust-Ausgabe ist {0}. popup.warning.btcChangeBelowDustException=Diese Transaktion erzeugt eine Wechselgeld-Ausgabe, die unter dem Dust-Limit (546 Satoshi) liegt und vom Bitcoin-Netzwerk abgelehnt würde.\n\nSie müssen den Dust-Betrag zu Ihrem Sende-Betrag hinzufügen, um zu vermeiden, dass eine Dust-Ausgabe generiert wird.\n\nDie Dust-Ausgabe ist {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=Sie haben nicht genügend BSQ-Gelder, um die Handels-Gebühr in BSQ zu bezahlen.\nSie können die Gebühr in BTC bezahlen, oder müssen Ihre BSQ-Wallet füllen. Sie können BSQ in Bisq kaufen.\n\nFehlende BSQ Gelder: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Ihre BSQ-Wallet hat keine ausreichenden Gelder, um die Handels-Gebühr in BSQ zu bezahlen. popup.warning.messageTooLong=Ihre Nachricht überschreitet die maximal erlaubte Größe. Sende Sie diese in mehreren Teilen oder laden Sie sie in einen Dienst wie https://pastebin.com hoch. popup.warning.lockedUpFunds=Sie haben gesperrtes Guthaben aus einem gescheiterten Trade.\nGesperrtes Guthaben: {0} \nEinzahlungs-Tx-Adresse: {1}\nTrade ID: {2}.\n\nBitte öffnen Sie ein Support-Ticket, indem Sie den Trade im Bildschirm "Offene Trades" auswählen und auf \"alt + o\" oder \"option + o\" drücken. @@ -2437,6 +2466,7 @@ seed.restore.error=Beim Wiederherstellen der Wallets mit den Seed-Wörtern ist e payment.account=Konto payment.account.no=Kontonummer payment.account.name=Kontoname +payment.account.userName=User name payment.account.owner=Vollständiger Name des Kontoinhabers payment.account.fullName=Vollständiger Name (vor, zweit, nach) payment.account.state=Bundesland/Landkreis/Region @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=E-Mail oder Telefonnummer payment.venmo.venmoUserName=Venmo Nutzername payment.popmoney.accountId=E-Mail oder Telefonnummer -payment.revolut.email=E-Mail -payment.revolut.phoneNr=Registrierte Telefonnummer payment.promptPay.promptPayId=Personalausweis/Steuernummer oder Telefonnr. payment.supportedCurrencies=Unterstützte Währungen payment.limitations=Einschränkungen @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=Um das Rückbuchungsrisiko zu begrenzen, setzt B payment.cashDeposit.info=Bitte bestätigen Sie, dass Ihre Bank Bareinzahlungen in Konten von anderen Personen erlaubt. Zum Beispiel werden diese Einzahlungen bei der Bank of America und Wells Fargo nicht mehr erlaubt. -payment.revolut.info=Bitte vergewissern sie sich, dass die Telefonnummer die sie verwendet haben auch bei Revolut registriert ist. Ansonsten kann der BTC-Käufer ihnen den Betrag nicht überweisen. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Zahlungsanweisungen sind eine der privateren Fiat-Kaufmethoden, die auf Bisq verfügbar sind.\n\nBitte beachten Sie jedoch die mit der Verwendung verbundenen potenziell erhöhten Risiken. Bisq trägt keine Verantwortung für den Fall, dass eine gesendete Zahlungsanweisung gestohlen wird, und der Mediator oder Vermittler wird in solchen Fällen die BTC an den Absender der Zahlungsanweisung vergeben, sofern er Trackinginformationen und Belege vorlegen kann. Es kann für den Absender ratsam sein, den Namen des BTC-Verkäufers auf die Zahlungsanweisung zu schreiben, um das Risiko zu minimieren, dass die Zahlungsanweisung von jemand anderem eingelöst wird. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=Kontaktinformationen payment.f2f.contact.prompt=Wie möchten Sie vom Handeslpartner kontaktiert werden? (E-Mailadresse, Telefonnummer,...) diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties index 8b689b2c109..0ee316d1a03 100644 --- a/core/src/main/resources/i18n/displayStrings_es.properties +++ b/core/src/main/resources/i18n/displayStrings_es.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediación support.tab.arbitration.support=Arbitraje support.tab.legacyArbitration.support=Legado de arbitraje support.tab.ArbitratorsSupportTickets=Tickets de {0} -support.filter=Filtrar lista +support.filter=Search disputes support.filter.prompt=Introduzca ID de transacción, fecha, dirección onion o datos de cuenta. +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Enviar notificación privada +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=No hay tickets abiertos support.sendingMessage=Enviando mensaje... support.receiverNotOnline=El receptor no está conectado. El mensaje se ha guardado en su bandeja de entrada. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=Ha solicitado mediación\n\n{0}\n\nVersión support.peerOpenedTicket=Su par de intercambio ha solicitado soporte debido a problemas técnicos\n\n{0}\n\nVersión Bisq: {1} support.peerOpenedDispute=Su pareja de intercambio ha solicitado una disputa.\n\n{0}\n\nVersión Bisq: {1} support.peerOpenedDisputeForMediation=Su par de intercambio ha solicitado mediación.\n\n{0}\n\nVersión Bisq: {1} -support.mediatorsDisputeSummary=Sistema de mensaje:\nResumen de disputa del mediador:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Dirección del nodo del mediador: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Pares conectados settings.net.onionAddressColumn=Dirección onion settings.net.creationDateColumn=Establecido settings.net.connectionTypeColumn=Dentro/Fuera -settings.net.totalTrafficLabel=Tráfico total +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Tiempo de ida y vuelta settings.net.sentBytesColumn=Enviado settings.net.receivedBytesColumn=Recibido @@ -989,7 +995,8 @@ settings.net.heightColumn=Altura settings.net.needRestart=Necesita reiniciar la aplicación para aplicar ese cambio.\n¿Quiere hacerlo ahora? settings.net.notKnownYet=Aún no conocido... -settings.net.sentReceived=Enviado: {0}, recibido: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[Dirección IP:puerto | host:puerto | dirección onion:puerto] (separado por coma). El puerto puede ser omitido si se utiliza el predeterminado (8333). settings.net.seedNode=Nodo semilla settings.net.directPeer=Par (directo) @@ -1011,8 +1018,8 @@ setting.about.support=Apoye a Bisq setting.about.def=Bisq no es una compañía - es un proyecto abierto a la comunidad. Si quiere participar o ayudar a Bisq por favor siga los enlaces de abajo. setting.about.contribute=Contribuir setting.about.providers=Proveedores de datos -setting.about.apisWithFee=Bisq usa APIs de terceros para los precios de los mercados Fiat y Altcoin así como para la estimación de tasas de minado. -setting.about.apis=Bisq utiliza APIs de terceros para los precios de mercado de Fiat y Altcoin. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Precios de mercado proporcionados por: setting.about.feeEstimation.label=Estimación de comisión de minería proporcionada por: setting.about.versionDetails=Detalles de la versión @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Abrir información de par account.tab.arbitratorRegistration=Registro de árbitro account.tab.mediatorRegistration=Registro de mediador account.tab.refundAgentRegistration=Registro de agente de devolución de fondos +account.tab.signing=Signing account.tab.account=Cuenta account.info.headline=Bienvenido a su cuenta Bisq account.info.msg=Aquí puede añadir cuentas de intercambio para monedas nacionales y altcoins y crear una copia de su cartera y datos de cuenta.\n\nUna nueva cartera Bitcoin fue creada la primera vez que inició Bisq.\n\nRecomendamos encarecidamente que escriba sus palabras de la semilla de la cartera Bitcoin (mire pestaña arriba) y considere añadir una contraseña antes de enviar fondos. Los ingresos y retiros de Bitcoin se administran en la sección \"Fondos\".\n\nNota de privacidad y seguridad: Debido a que Bisq es un exchange descentralizado, todos sus datos se guardan en su ordenador. No hay servidores, así que no tenemos acceso a su información personal, sus saldos, o incluso su dirección IP. Datos como número de cuenta bancaria, direcciones altcoin y Bitcoin, etc son solo compartidos con su par de intercambio para completar intercambios iniciados (en caso de una disputa el mediador o árbitro verá los mismos datos que el par de intercambio). @@ -1151,7 +1159,7 @@ account.password.info=Con protección por contraseña necesitará introducir su account.seed.backup.title=Copia de seguridad de palabras semilla del monedero account.seed.info=Por favor apunte en un papel tanto las palabras semilla del monedero como la fecha! Puede recuperar su monedero en cualquier momento con las palabras semilla y la fecha.\nLas mismas palabras semilla se usan para el monedero BTC como BSQ\n\nDebe apuntar las palabras semillas en una hoja de papel. No la guarde en su computadora.\n\nPor favor, tenga en cuenta que las palabras semilla no son un sustituto de la copia de seguridad.\nNecesita hacer la copia de seguridad de todo el directorio de aplicación en la pantalla \"Cuenta/Copia de Seguridad\" para recuperar un estado de aplicación válido y los datos.\nImportar las palabras semilla solo se recomienda para casos de emergencia. La aplicación no será funcional sin una buena copia de seguridad de los archivos de la base de datos y las claves! -account.seed.backup.warning=Please note that the seed words are NOT a replacement for a backup.\nYou need to create a backup of the whole application directory from the \"Account/Backup\" screen to recover application state and data.\nImporting seed words is only recommended for emergency cases. The application will not be functional without a proper backup of the database files and keys!\n\nSee the wiki page https://bisq.wiki/Backing_up_application_data for extended info. +account.seed.backup.warning=Por favor tenga en cuenta que las palabras semilla NO SON un sustituto de la copia de seguridad.\nTiene que crear una copia de todo el directorio de aplicación desde la pantalla \"Cuenta/Copia de seguridad\ para recuperar el estado y datos de la aplicación.\nImportar las palabras semilla solo se recomienda para casos de emergencia. La aplicación no funcionará sin una copia de seguridad adecuada de las llaves y archivos de sistema!\n\nVea la página wiki https://bisq.wiki/Backing_up_application_data para más información. account.seed.warn.noPw.msg=No ha establecido una contraseña de cartera que proteja la visualización de las palabras semilla.\n\n¿Quiere que se muestren las palabras semilla? account.seed.warn.noPw.yes=Sí, y no preguntar de nuevo account.seed.enterPw=Introducir contraseña para ver las palabras semilla @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Cantidad de pago del comprador disputeSummaryWindow.payoutAmount.seller=Cantidad de pago del vendedor disputeSummaryWindow.payoutAmount.invert=Usar perdedor como publicador disputeSummaryWindow.reason=Razón de la disputa -disputeSummaryWindow.reason.bug=Bug -disputeSummaryWindow.reason.usability=Usabilidad -disputeSummaryWindow.reason.protocolViolation=Violación del protocolo -disputeSummaryWindow.reason.noReply=Sin respuesta -disputeSummaryWindow.reason.scam=Estafa -disputeSummaryWindow.reason.other=Otro -disputeSummaryWindow.reason.bank=Banco + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Bug +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Usabilidad +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Violación del protocolo +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Sin respuesta +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Estafa +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Otro +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Banco +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Nota de resumen disputeSummaryWindow.addSummaryNotes=Añadir notas de sumario disputeSummaryWindow.close.button=Cerrar ticket -disputeSummaryWindow.close.msg=Ticket cerrado en {0}\n\nSumario:\nCantidad de pago para comprador de BTC: {1}\nCantidad de pago para vendedor de BTC: {2}\n\nNotas del sumario:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nSiguientes pasos:\nAbrir intercambio y aceptar o rechazar la sugerencia del mediador disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nSiguientes pasos:\nNo se requiere ninguna acción adicional. Si el árbitro decide en su favor, verá una transacción de "Devolución de fondos de arbitraje" en Fondos/Transacciones disputeSummaryWindow.close.closePeer=Necesitar cerrar también el ticket del par de intercambio! @@ -1985,7 +2013,8 @@ filterWindow.onions=Direcciones onion filtradas (separadas por coma) filterWindow.accounts=Cuentas de intercambio filtradas:\nFormato: lista de [ID método de pago | campo de datos | valor] separada por coma. filterWindow.bannedCurrencies=Códigos de moneda filtrados (separados por coma) filterWindow.bannedPaymentMethods=ID's de métodos de pago filtrados (separados por coma) -filterWindow.bannedSignerPubKeys=Llaves públicas de los firmantes filtrados (separados por coma, hex de las llaves públicas)\n +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Árbitros filtrados (direcciones onion separadas por coma) filterWindow.mediators=Mediadores filtrados (direcciones onion separadas por coma) filterWindow.refundAgents=Agentes de devolución de fondos filtrados (direcciones onion separadas por coma) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=No tiene suficientes fondos BTC para popup.warning.bsqChangeBelowDustException=Esta transacción crea un output BSQ de cambio que está por debajo del límite dust (5.46 BSQ) y sería rechazado por la red Bitcoin.\n\nTiene que enviar una cantidad mayor para evitar el output de cambio (Ej. agregando la cantidad de dust a su cantidad de envío) o añadir más fondos BSQ a su cartera para evitar generar un output de dust.\n\nEl output dust es {0}. popup.warning.btcChangeBelowDustException=Esta transacción crea un output de cambio que está por debajo del límite de dust (546 Satoshi) y sería rechazada por la red Bitcoin.\n\nDebe agregar la cantidad de dust a su cantidad de envío para evitar generar un output de dust.\n\nEl output de dust es {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=No tiene suficientes fondos BSQ para pagar la comisión de intercambio en BSQ. Puede pagar la tasa en BTC o tiene que fondear su monedero BSQ. Puede comprar BSQ en Bisq.\n\nFondos BSQ necesarios: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Su monedero BSQ no tiene suficientes fondos para pagar la comisión de intercambio en BSQ. popup.warning.messageTooLong=Su mensaje excede el tamaño máximo permitido. Por favor, envíelo por partes o súbalo a un servicio como https://pastebin.com popup.warning.lockedUpFunds=Ha bloqueado fondos de un intercambio fallido.\nBalance bloqueado: {0}\nDirección de depósito TX: {1}\nID de intercambio: {2}.\n\nPor favor, abra un ticket de soporte seleccionando el intercambio en la pantalla de intercambios pendientes y haciendo clic en \"alt + o\" o \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=Un error ocurrió el restaurar los monederos con las palabras payment.account=Cuenta payment.account.no=Número de cuenta payment.account.name=Nombre de cuenta +payment.account.userName=User name payment.account.owner=Nombre completo del propietario de la cuenta payment.account.fullName=Nombre completo payment.account.state=Estado/Provincia/Región @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=Correo electrónico o núm. de telefóno payment.venmo.venmoUserName=Nombre de usuario Venmo payment.popmoney.accountId=Correo electrónico o núm. de telefóno -payment.revolut.email=Email -payment.revolut.phoneNr=Número de teléfono registrado payment.promptPay.promptPayId=Citizen ID/Tax ID o número de teléfono payment.supportedCurrencies=Monedas soportadas payment.limitations=Límitaciones: @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=Para limitar el riesgo de devolución de cargo, payment.cashDeposit.info=Por favor confirme que su banco permite enviar depósitos de efectivo a cuentas de otras personas. Por ejemplo, Bank of America y Wells Fargo ya no permiten estos depósitos. -payment.revolut.info=Por favor asegúrese de que el número de teléfono que ha usado para la cuenta Revolut está registrada en Revolut, de lo contrario el comprador BTC no podrá enviarle los fondos. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Los giros postales son uno de los métodos de adquisición de fiat más privados disponibles en Bisq\n\nAún así, por favor tenga en cuenta el elevado riesgo potencial asociado a su uso. Bisq no contrae ninguna responsabilidad en caso de que el dinero enviado sea robado, y el mediador o árbitro en estos casos adjudiquen los BTC al emisor del giro postal, siempre que puedan mostrar recibos e información de seguimiento. Es aconsejable para el emisor escribir el nombre del emisor en el giro postal, para minimizar el riesgo de que el giro postal sea retirado por otra persona. +payment.usPostalMoneyOrder.info=Los intercambios usando US Postal Money Orders (USPMO) en Bisq requiere que entienda lo siguiente:\n\n- Los compradores de BTC deben escribir la dirección del vendedor en los campos de "Payer" y "Payee" y tomar una foto en alta resolución de la USPMO y del sobre con la prueba de seguimiento antes de enviar.\n- Los compradores de BTC deben enviar la USPMO con confirmación de entrega.\n\nEn caso de que sea necesaria la mediación, se requerirá al comprador que entregue las fotos al mediador o agente de devolución de fondos, junto con el número de serie de la USPMO, número de oficina postal, y la cantidad de USD, para que puedan verificar los detalles en la web de US Post Office.\n\nNo entregar la información requerida al Mediador o Árbitro resultará en pérdida del caso de disputa. \n\nEn todos los casos de disputa, el emisor de la USPMO tiene el 100% de responsabilidad en aportar la evidencia al Mediador o Árbitro.\n\nSi no entiende estos requerimientos, no comercie usando USPMO en Bisq. payment.f2f.contact=Información de contacto payment.f2f.contact.prompt=¿Cómo quiere ser contactado por el par de intercambio? (dirección email, número de teléfono...) diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties index f27daa08595..eab54e76b1f 100644 --- a/core/src/main/resources/i18n/displayStrings_fa.properties +++ b/core/src/main/resources/i18n/displayStrings_fa.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediation support.tab.arbitration.support=Arbitration support.tab.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets -support.filter=لیست فیلتر +support.filter=Search disputes support.filter.prompt=Enter trade ID, date, onion address or account data +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=ارسال اطلاع رسانی خصوصی +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=هیچ تیکتی به صورت باز وجود ندارد support.sendingMessage=در حال ارسال پیام ... support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq ver support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1} support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1} support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} -support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=همتایان متصل settings.net.onionAddressColumn=آدرس Onion settings.net.creationDateColumn=تثبیت شده settings.net.connectionTypeColumn=درون/بیرون -settings.net.totalTrafficLabel=مجموع ترافیک +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=تاخیر چرخشی settings.net.sentBytesColumn=ارسال شده settings.net.receivedBytesColumn=دریافت شده @@ -989,7 +995,8 @@ settings.net.heightColumn=Height settings.net.needRestart=به منظور اعمال آن تغییر باید برنامه را مجدداً راه اندازی کنید.\nآیا می‌خواهید این کار را هم اکنون انجام دهید؟ settings.net.notKnownYet=هنوز شناخته شده نیست ... -settings.net.sentReceived=ارسال شده: {0}، دریافت شده: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[آدرس آی پی: پورت | نام میزبان: پورت | آدرس Onion : پورت] (جدا شده با ویرگول). اگر از پیش فرض (8333) استفاده می شود، پورت می تواند حذف شود. settings.net.seedNode=گره ی اصلی settings.net.directPeer=همتا (مستقیم) @@ -1011,8 +1018,8 @@ setting.about.support=پشتیبانی از Bisq setting.about.def=Bisq یک شرکت نیست — یک پروژه اجتماعی است و برای مشارکت آزاد است. اگر می‌خواهید در آن مشارکت کنید یا از آن حمایت نمایید، لینک‌‌های زیر را دنبال کنید. setting.about.contribute=مشارکت setting.about.providers=ارائه دهندگان داده -setting.about.apisWithFee=Bisq از APIهای شخص ثالث 3rd party برای قیمت های روز فیات و آلت کوین و همچنین برای برآورد هزینه تراکنش شبکه استفاده می کند. -setting.about.apis=Bisq از APIهای شخص ثالث 3rd party برای قیمت های روز فیات و آلت کوین استفاده می کند. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=قیمت‌های بازار ارائه شده توسط setting.about.feeEstimation.label=برآورد کارمزد استخراج ارائه شده توسط setting.about.versionDetails=جزئیات نسخه @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar o account.tab.arbitratorRegistration=ثبت نام داور account.tab.mediatorRegistration=Mediator registration account.tab.refundAgentRegistration=Refund agent registration +account.tab.signing=Signing account.tab.account=حساب account.info.headline=به حساب Bisq خود خوش آمدید account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\nA new Bitcoin wallet was created the first time you started Bisq.\n\nWe strongly recommend that you write down your Bitcoin wallet seed words (see tab on the top) and consider adding a password before funding. Bitcoin deposits and withdrawals are managed in the \"Funds\" section.\n\nPrivacy & security note: because Bisq is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no access to your personal info, your funds, or even your IP address. Data such as bank account numbers, altcoin & Bitcoin addresses, etc are only shared with your trading partner to fulfill trades you initiate (in case of a dispute the mediator or arbitrator will see the same data as your trading peer). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=مقدار پرداختی خریدار disputeSummaryWindow.payoutAmount.seller=مقدار پرداختی فروشنده disputeSummaryWindow.payoutAmount.invert=استفاده از بازنده به عنوان منتشر کننده disputeSummaryWindow.reason=دلیل اختلاف -disputeSummaryWindow.reason.bug=اشکال -disputeSummaryWindow.reason.usability=قابلیت استفاده -disputeSummaryWindow.reason.protocolViolation=نقض پروتکل -disputeSummaryWindow.reason.noReply=بدون پاسخ -disputeSummaryWindow.reason.scam=کلاهبرداری -disputeSummaryWindow.reason.other=سایر -disputeSummaryWindow.reason.bank=بانک + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=اشکال +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=قابلیت استفاده +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=نقض پروتکل +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=بدون پاسخ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=کلاهبرداری +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=سایر +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=بانک +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=نکات خلاصه disputeSummaryWindow.addSummaryNotes=افزودن نکات خلاصه disputeSummaryWindow.close.button=بستن تیکت -disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nSummary notes:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\nOpen trade and accept or reject suggestion from mediator disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\nNo further action is required from you. If the arbitrator decided in your favor, you'll see a "Refund from arbitration" transaction in Funds/Transactions disputeSummaryWindow.close.closePeer=شما باید همچنین تیکت همتایان معامله را هم ببندید! @@ -1985,7 +2013,8 @@ filterWindow.onions=آدرس های Onion فیلتر شده (جدا شده با filterWindow.accounts=داده های حساب معاملاتی فیلترشده:\nفرمت: لیست جدا شده با ویرگول [شناسه روش پرداخت، زمینه داده، ارزش] filterWindow.bannedCurrencies=کدهای ارز فیلترشده (جدا شده با ویرگول) filterWindow.bannedPaymentMethods=شناسه‌های روش پرداخت فیلتر شده (جدا شده با ویرگول) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=داوران فیلتر شده (آدرس های Onion جدا شده با ویرگول) filterWindow.mediators=Filtered mediators (comma sep. onion addresses) filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=شما BTC کافی برای پردا popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\nYou need to either send a higher amount to avoid the change output (e.g. by adding the dust amount to your sending amount) or add more BSQ funds to your wallet so you avoid to generate a dust output.\n\nThe dust output is {0}. popup.warning.btcChangeBelowDustException=This transaction creates a change output which is below dust limit (546 Satoshi) and would be rejected by the Bitcoin network.\n\nYou need to add the dust amount to your sending amount to avoid to generate a dust output.\n\nThe dust output is {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=شما BTC کافی برای پرداخت کارمزد معامله آن تراکنش BSQ را ندارید. می‌توانید کارمزد را با BTC پرداخت کنید و یا اینکه کیف پول BSQ خود را شارژ کنید. می‌توانید از Bisq اقدام به خرید BSQ کنید.\n\nBSQ موردنیاز: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=کیف‌پول BSQ شما BSQ کافی برای پرداخت کارمزد معامله به BSQ را ندارد. popup.warning.messageTooLong=پیام شما بیش از حداکثر اندازه مجاز است. لطفا آن را در چند بخش ارسال کنید یا آن را در یک سرویس مانند https://pastebin.com آپلود کنید. popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\nLocked up balance: {0} \nDeposit tx address: {1}\nTrade ID: {2}.\n\nPlease open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=هنگام بازگرداندن کیف پول با کلمات payment.account=حساب payment.account.no=شماره حساب payment.account.name=نام حساب +payment.account.userName=User name payment.account.owner=نام کامل مالک حساب payment.account.fullName=نام کامل (اول، وسط، آخر) payment.account.state=ایالت/استان/ناحیه @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=ایمیل یا شماره تلفن payment.venmo.venmoUserName=نام کاربری Venmo payment.popmoney.accountId=ایمیل یا شماره تلفن -payment.revolut.email=ایمیل -payment.revolut.phoneNr=Registered phone no. payment.promptPay.promptPayId=شناسه شهروندی/شناسه مالیاتی یا شماره تلفن payment.supportedCurrencies=ارزهای مورد حمایت payment.limitations=محدودیت‌ها @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=لطفا مطمئن شوید که بانک شما اجازه پرداخت سپرده نفد به حساب دیگر افراد را می‌دهد. برای مثال، Bank of America و Wells Fargo دیگر اجازه چنین پرداخت‌هایی را نمی‌دهند. -payment.revolut.info=Please be sure that the phone number you used for your Revolut account is registered at Revolut otherwise the BTC buyer cannot send you the funds. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Money orders are one of the more private fiat purchase methods available on Bisq.\n\nHowever, please be aware of potentially increased risks associated with their use. Bisq will not bear any responsibility in case a sent money order is stolen, and the mediator or arbitrator will in such cases award the BTC to the sender of the money order, provided they can produce tracking information and receipts. It may be advisable for the sender to write the BTC seller's name on the money order, in order to minimize the risk that the money order is cashed by someone else. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=اطلاعات تماس payment.f2f.contact.prompt=از چه طریقی می‌خواهید توسط همتای معاملاتی با شما تماس حاصل شود؟ (آدرس ایمیل، شماره تلفن، ...) diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties index 2d126fb1625..6629a86411c 100644 --- a/core/src/main/resources/i18n/displayStrings_fr.properties +++ b/core/src/main/resources/i18n/displayStrings_fr.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Médiation support.tab.arbitration.support=Arbitrage support.tab.legacyArbitration.support=Conclusion d'arbitrage support.tab.ArbitratorsSupportTickets=Tickets de {0} -support.filter=Liste de filtre +support.filter=Search disputes support.filter.prompt=Saisissez l'ID du trade, la date, l'adresse "onion" ou les données du compte. +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Envoyer une notification privée +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=Il n'y a pas de tickets ouverts support.sendingMessage=Envoi du message... support.receiverNotOnline=Le destinataire n'est pas en ligne. Le message est enregistré dans leur boîte mail. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=Vous avez demandé une médiation.\n\n{0}\n support.peerOpenedTicket=Votre pair de trading a demandé une assistance en raison de problèmes techniques.\n\n{0}\n\nVersion de Bisq: {1} support.peerOpenedDispute=Votre pair de trading a fait une demande de litige.\n\n{0}\n\nBisq version: {1} support.peerOpenedDisputeForMediation=Votre pair de trading a demandé une médiation.\n\n{0}\n\nVersion de Bisq: {1} -support.mediatorsDisputeSummary=Message du système:\nSynthèse du litige du médiateur:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Adresse du nœud du médiateur: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Pairs connectés settings.net.onionAddressColumn=Adresse onion settings.net.creationDateColumn=Établi settings.net.connectionTypeColumn=In/Out -settings.net.totalTrafficLabel=Traffic total +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Roundtrip settings.net.sentBytesColumn=Envoyé settings.net.receivedBytesColumn=Reçu @@ -989,7 +995,8 @@ settings.net.heightColumn=Hauteur settings.net.needRestart=Vous devez redémarrer l'application pour appliquer cette modification.\nVous voulez faire cela maintenant? settings.net.notKnownYet=Pas encore connu... -settings.net.sentReceived=Envoyé: {0}, reçu: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[IP address:port | host name:port | onion address:port] (séparés par des virgules). Le port peut être ignoré si utilisé par défaut (8333). settings.net.seedNode=Seed node settings.net.directPeer=Pair (direct) @@ -1011,8 +1018,8 @@ setting.about.support=Soutenir Bisq setting.about.def=Bisq n'est pas une entreprise, c'est un projet ouvert vers la communauté. Si vous souhaitez participer ou soutenir Bisq, veuillez suivre les liens ci-dessous. setting.about.contribute=Contribuer setting.about.providers=Fournisseurs de données -setting.about.apisWithFee=Bisq utilise des APIs tierces ou 3rd party pour le taux de change des devises nationales et des cryptomonnaies, aussi bien que pour obtenir une estimation des frais de minage. -setting.about.apis=Bisq utilise des APIs tierces ou 3rd party pour le taux de change des devises nationales et des cryptomonnaies. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Prix de marché fourni par setting.about.feeEstimation.label=Estimation des frais de minage fournie par setting.about.versionDetails=Détails sur la version @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Ouvrez l''information du p account.tab.arbitratorRegistration=Enregistrement de l'arbitre account.tab.mediatorRegistration=Enregistrement du médiateur account.tab.refundAgentRegistration=Enregistrement de l'agent de remboursement +account.tab.signing=Signing account.tab.account=Compte account.info.headline=Bienvenue sur votre compte Bisq account.info.msg=Ici, vous pouvez ajouter des comptes de trading en devises nationales et en altcoins et créer une sauvegarde de votre portefeuille ainsi que des données de votre compte.\n\nUn nouveau portefeuille Bitcoin a été créé un premier lancement de Bisq.\n\nNous vous recommandons vivement d'écrire les mots-clés de votre seed de portefeuille Bitcoin (voir l'onglet en haut) et d'envisager d'ajouter un mot de passe avant le transfert de fonds. Les dépôts et retraits de Bitcoin sont gérés dans la section \"Fonds\".\n\nNotice de confidentialité et de sécurité : Bisq étant une plateforme d'échange décentralisée, toutes vos données sont conservées sur votre ordinateur. Il n'y a pas de serveurs, nous n'avons donc pas accès à vos informations personnelles, à vos fonds ou même à votre adresse IP. Les données telles que les numéros de compte bancaire, les adresses altcoin & Bitcoin, etc ne sont partagées avec votre pair de trading que pour effectuer les transactions que vous initiez (en cas de litige, le médiateur et l’arbitre verront les mêmes données que votre pair de trading). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Montant du versement de l'acheteur disputeSummaryWindow.payoutAmount.seller=Montant du versement au vendeur disputeSummaryWindow.payoutAmount.invert=Utiliser le perdant comme publicateur disputeSummaryWindow.reason=Motif du litige -disputeSummaryWindow.reason.bug=Bug -disputeSummaryWindow.reason.usability=Utilisabilité -disputeSummaryWindow.reason.protocolViolation=Violation du protocole -disputeSummaryWindow.reason.noReply=Pas de réponse -disputeSummaryWindow.reason.scam=Scam -disputeSummaryWindow.reason.other=Autre -disputeSummaryWindow.reason.bank=Banque + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Bug +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Utilisabilité +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Violation du protocole +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Pas de réponse +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Scam +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Autre +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Banque +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Notes de synthèse disputeSummaryWindow.addSummaryNotes=Ajouter des notes de synthèse disputeSummaryWindow.close.button=Fermer le ticket -disputeSummaryWindow.close.msg=Ticket fermé le {0}\n\nRésumé:\nMontant du paiement pour l''acheteur BTC: {1}\nMontant du paiement pour le vendeur BTC: {2}\n\nNotes de synthèse:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nProchaines étapes:\nOuvrir la transaction et accepter ou rejeter la suggestion du médiateur disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nProchaines étapes:\nAucune autre action n'est requise de votre part. Si l'arbitre a rendu une décision en votre faveur, vous verrez une opération de " Remboursement à la suite de l'arbitrage " dans Fonds/Transactions disputeSummaryWindow.close.closePeer=Vous devez également clore le ticket des pairs de trading ! @@ -1985,7 +2013,8 @@ filterWindow.onions=Adresses onion filtrées (virgule de sep.) filterWindow.accounts=Données filtrées du compte de trading:\nFormat: séparer par une virgule liste des [ID du mode de paiement | champ de données | valeur]. filterWindow.bannedCurrencies=Codes des devises filtrées (séparer avec une virgule.) filterWindow.bannedPaymentMethods=IDs des modes de paiements filtrés (séparer avec une virgule.) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Arbitres filtrés (adresses onion séparées par une virgule) filterWindow.mediators=Médiateurs filtrés (adresses onion sep. par une virgule) filterWindow.refundAgents=Agents de remboursement filtrés (adresses onion sep. par virgule) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=Vous ne disposez pas de suffisamment popup.warning.bsqChangeBelowDustException=Cette transaction crée une BSQ change output qui est inférieure à la dust limit (5,46 BSQ) et serait rejetée par le réseau Bitcoin.\n\nVous devez soit envoyer un montant plus élevé pour éviter la change output (par exemple en ajoutant le montant de dust à votre montant d''envoi), soit ajouter plus de fonds BSQ à votre portefeuille pour éviter de générer une dust output.\n\nLa dust output est {0}. popup.warning.btcChangeBelowDustException=Cette transaction crée une change output qui est inférieure à la dust limit (546 Satoshi) et serait rejetée par le réseau Bitcoin.\n\nVous devez ajouter la quantité de dust à votre montant envoyé pour éviter de générer une dust output.\n\nLa dust output est {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=Vous ne disposez pas de suffisamment de fonds en BSQ pour payer les frais de transaction en BSQ. Vous pouvez payer les frais en BTC ou vous aurez besoin de provisionner votre portefeuille BSQ. Vous pouvez acheter des BSQ sur Bisq.\n\nFonds BSQ manquants: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Votre portefeuille BSQ ne dispose pas de suffisamment de fonds pour payer les frais de transaction en BSQ. popup.warning.messageTooLong=Votre message dépasse la taille maximale autorisée. Veuillez l'envoyer en plusieurs parties ou le télécharger depuis un service comme https://pastebin.com. popup.warning.lockedUpFunds=Vous avez des fonds bloqués d''une transaction qui a échoué.\nSolde bloqué: {0}\nAdresse de la tx de dépôt: {1}\nID de l''échange: {2}.\n\nVeuillez ouvrir un ticket de support en sélectionnant la transaction dans l'écran des transactions ouvertes et en appuyant sur \"alt + o\" ou \"option + o\". @@ -2437,6 +2466,7 @@ seed.restore.error=Une erreur est survenue lors de la restauration des portefeui payment.account=Compte payment.account.no=N° de compte payment.account.name=Nom du compte +payment.account.userName=User name payment.account.owner=Nom et prénoms du propriétaire du compte payment.account.fullName=Nom complet (prénom, deuxième prénom, nom de famille) payment.account.state=État/Département/Région @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=Email ou N° de téléphone payment.venmo.venmoUserName=Nom d'utilisateur Venmo payment.popmoney.accountId=Email ou N° de téléphone -payment.revolut.email=Email -payment.revolut.phoneNr=N° de téléphone enregistré payment.promptPay.promptPayId=N° de carte d'identité/d'identification du contribuable ou numéro de téléphone payment.supportedCurrencies=Devises acceptées payment.limitations=Restrictions @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=Veuillez confirmer que votre banque vous permet d'envoyer des dépôts en espèces sur le compte d'autres personnes. Par exemple, Bank of America et Wells Fargo n'autorisent plus de tels dépôts. -payment.revolut.info=Veuillez vous assurer que le numéro de téléphone que vous avez utilisé pour votre compte Revolut est enregistré chez Revolut sinon l'acheteur de BTC ne pourra pas vous envoyer les fonds. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Les mandats sont l'une des méthodes d'achat de fiat les plus privées disponibles sur Bisq.\n\nCependant, veuillez être conscient des risques potentiellement accrus associés à leur utilisation. Bisq n'assumera aucune responsabilité en cas de vol d'un mandat envoyé, et le médiateur ou l'arbitre accordera dans ce cas les BTC à l'expéditeur du mandat, à condition qu'il puisse produire les informations de suivi et les reçus. Il peut être conseillé à l'expéditeur d'écrire le nom du vendeur des BTC sur le mandat, afin de minimiser le risque que le mandat soit encaissé par quelqu'un d'autre. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=information de contact payment.f2f.contact.prompt=Comment souhaitez-vous être contacté par votre pair de trading? (adresse mail, numéro de téléphone,...) diff --git a/core/src/main/resources/i18n/displayStrings_ja.properties b/core/src/main/resources/i18n/displayStrings_ja.properties index b7a9f67485f..0574ac0dad1 100644 --- a/core/src/main/resources/i18n/displayStrings_ja.properties +++ b/core/src/main/resources/i18n/displayStrings_ja.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediation support.tab.arbitration.support=Arbitration support.tab.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets -support.filter=フィルターリスト +support.filter=Search disputes support.filter.prompt=トレードID、日付、onionアドレスまたはアカウントデータを入力してください +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=プライベート通知を送信 +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=オープンなチケットはありません support.sendingMessage=メッセージを送信中 support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq ver support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1} support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1} support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} -support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=接続されたピア settings.net.onionAddressColumn=Onionアドレス settings.net.creationDateColumn=既定 settings.net.connectionTypeColumn=イン/アウト -settings.net.totalTrafficLabel=総トラフィック +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=往復 settings.net.sentBytesColumn=送信済 settings.net.receivedBytesColumn=受信済 @@ -989,7 +995,8 @@ settings.net.heightColumn=Height settings.net.needRestart=その変更を適用するには、アプリケーションを再起動する必要があります。\n今すぐ行いますか? settings.net.notKnownYet=まだわかりません... -settings.net.sentReceived=送信済: {0}, 受信済: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[IPアドレス:ポート | ホスト名:ポート | onionアドレス:ポート](コンマ区切り)。デフォルト(8333)が使用される場合、ポートは省略できます。 settings.net.seedNode=シードノード settings.net.directPeer=ピア (ダイレクト) @@ -1011,8 +1018,8 @@ setting.about.support=Bisqをサポートする setting.about.def=Bisqは会社ではなく、開かれたコミュニティのプロジェクトです。Bisqにサポートしたい時は下のURLをチェックしてください。 setting.about.contribute=貢献 setting.about.providers=データプロバイダー -setting.about.apisWithFee=Bisqは、法定通貨とアルトコインの市場価格や、マイニング料金の推定にサードパーティAPIを使用します。 -setting.about.apis=Bisqは法定通貨とアルトコインの市場価格の為にサードパーティAPIを使用します。 +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=市場価格を提供している: setting.about.feeEstimation.label=推定マイニング手数料の提供: setting.about.versionDetails=バージョン詳細 @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar o account.tab.arbitratorRegistration=調停人登録 account.tab.mediatorRegistration=Mediator registration account.tab.refundAgentRegistration=Refund agent registration +account.tab.signing=Signing account.tab.account=アカウント account.info.headline=あなたのBisqアカウントへようこそ! account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\nA new Bitcoin wallet was created the first time you started Bisq.\n\nWe strongly recommend that you write down your Bitcoin wallet seed words (see tab on the top) and consider adding a password before funding. Bitcoin deposits and withdrawals are managed in the \"Funds\" section.\n\nPrivacy & security note: because Bisq is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no access to your personal info, your funds, or even your IP address. Data such as bank account numbers, altcoin & Bitcoin addresses, etc are only shared with your trading partner to fulfill trades you initiate (in case of a dispute the mediator or arbitrator will see the same data as your trading peer). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=買い手の支払額 disputeSummaryWindow.payoutAmount.seller=売り手の支払額 disputeSummaryWindow.payoutAmount.invert=発行者として敗者を使用 disputeSummaryWindow.reason=係争の理由 -disputeSummaryWindow.reason.bug=バグ -disputeSummaryWindow.reason.usability=使いやすさ -disputeSummaryWindow.reason.protocolViolation=プロトコル違反 -disputeSummaryWindow.reason.noReply=返信無し -disputeSummaryWindow.reason.scam=詐欺 -disputeSummaryWindow.reason.other=その他 -disputeSummaryWindow.reason.bank=銀行 + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=バグ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=使いやすさ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=プロトコル違反 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=返信無し +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=詐欺 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=その他 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=銀行 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=概要ノート disputeSummaryWindow.addSummaryNotes=概要ノートを追加 disputeSummaryWindow.close.button=チケットを閉じる -disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nSummary notes:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\nOpen trade and accept or reject suggestion from mediator disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\nNo further action is required from you. If the arbitrator decided in your favor, you'll see a "Refund from arbitration" transaction in Funds/Transactions disputeSummaryWindow.close.closePeer=取引相手のチケットも閉じる必要があります! @@ -1985,7 +2013,8 @@ filterWindow.onions=フィルター済onionアドレス(コンマ区切り) filterWindow.accounts=フィルター済トレードアカウントデータ:\n形式: コンマ区切りのリスト [支払方法id | データフィールド | 値] filterWindow.bannedCurrencies=フィルター済通貨コード(コンマ区切り) filterWindow.bannedPaymentMethods=フィルター済支払方法ID(コンマ区切り) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=フィルター済調停人(コンマ区切り onionアドレス) filterWindow.mediators=Filtered mediators (comma sep. onion addresses) filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=あなたはそのトランザクシ popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\nYou need to either send a higher amount to avoid the change output (e.g. by adding the dust amount to your sending amount) or add more BSQ funds to your wallet so you avoid to generate a dust output.\n\nThe dust output is {0}. popup.warning.btcChangeBelowDustException=This transaction creates a change output which is below dust limit (546 Satoshi) and would be rejected by the Bitcoin network.\n\nYou need to add the dust amount to your sending amount to avoid to generate a dust output.\n\nThe dust output is {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=BSQのトレード手数料を支払うのに十分なBSQ残高がありません。 BTCで手数料を支払うか、BSQウォレットに入金する必要があります。 BisqでBSQを購入できます。\n\n不足しているBSQ残高:{0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=BSQウォレットにBSQのトレード手数料を支払うのに十分な残高がありません。 popup.warning.messageTooLong=メッセージが許容サイズ上限を超えています。いくつかに分けて送信するか、 https://pastebin.com のようなサービスにアップロードしてください。 popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\nLocked up balance: {0} \nDeposit tx address: {1}\nTrade ID: {2}.\n\nPlease open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=シードワードを使用したウォレットの復元中 payment.account=アカウント payment.account.no=アカウント番号 payment.account.name=アカウント名 +payment.account.userName=User name payment.account.owner=アカウント所有者の氏名 payment.account.fullName=氏名(名、ミドルネーム、姓) payment.account.state=州/県/区 @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=メールか電話番号 payment.venmo.venmoUserName=Venmo ユーザー名 payment.popmoney.accountId=メールか電話番号 -payment.revolut.email=メール -payment.revolut.phoneNr=登録された電話番号 payment.promptPay.promptPayId=市民ID/納税者番号または電話番号 payment.supportedCurrencies=サポートされている通貨 payment.limitations=制限事項 @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=あなたの銀行が他の人の口座に現金入金を送ることを許可していることを確認してください。たとえば、Bank of America と Wells Fargo では、こうした預金は許可されなくなりました。 -payment.revolut.info=Revolutアカウントに使用した電話番号がRevolutに登録されていることを確認してください。そうでなければ、BTCの買い手はあなたに資金を送ることができません。 +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Money orders are one of the more private fiat purchase methods available on Bisq.\n\nHowever, please be aware of potentially increased risks associated with their use. Bisq will not bear any responsibility in case a sent money order is stolen, and the mediator or arbitrator will in such cases award the BTC to the sender of the money order, provided they can produce tracking information and receipts. It may be advisable for the sender to write the BTC seller's name on the money order, in order to minimize the risk that the money order is cashed by someone else. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=連絡情報 payment.f2f.contact.prompt=取引相手からどのように連絡を受け取りたいですか?(メールアドレス、電話番号…) diff --git a/core/src/main/resources/i18n/displayStrings_pt-br.properties b/core/src/main/resources/i18n/displayStrings_pt-br.properties index 186e692a66c..8bc92fc11a8 100644 --- a/core/src/main/resources/i18n/displayStrings_pt-br.properties +++ b/core/src/main/resources/i18n/displayStrings_pt-br.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediação support.tab.arbitration.support=Arbitragem support.tab.legacyArbitration.support=Arbitração antiga support.tab.ArbitratorsSupportTickets=Tickets de {0} -support.filter=Lista de filtragem +support.filter=Search disputes support.filter.prompt=Insira ID da negociação. data. endereço onion ou dados da conta +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Enviar notificação privada +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=Não há tickets de suporte abertos support.sendingMessage=Enviando mensagem... support.receiverNotOnline=O recipiente não está online. A mensagem será salva na caixa postal dele. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=Você solicitou mediação.\n\n{0}\n\nVers support.peerOpenedTicket=O seu parceiro de negociação solicitou suporte devido a problemas técnicos.\n\n{0}\n\nVersão do Bisq: {1} support.peerOpenedDispute=O seu parceiro de negociação solicitou uma disputa.\n\n{0}\n\nVersão do Bisq: {1} support.peerOpenedDisputeForMediation=O seu parceiro de negociação solicitou mediação.\n\n{0}\n\nVersão do Bisq: {1} -support.mediatorsDisputeSummary=Mensagem do sistema:\nResumo da disputa:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Endereço do nó do mediador: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Pares conectados settings.net.onionAddressColumn=Endereço onion settings.net.creationDateColumn=Estabelecida settings.net.connectionTypeColumn=Entrada/Saída -settings.net.totalTrafficLabel=Tráfego total +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Ping settings.net.sentBytesColumn=Enviado settings.net.receivedBytesColumn=Recebido @@ -989,7 +995,8 @@ settings.net.heightColumn=Altura settings.net.needRestart=Você precisa reiniciar o programa para aplicar esta alteração.\nDeseja fazer isso agora? settings.net.notKnownYet=Ainda desconhecido... -settings.net.sentReceived=Enviado: {0}, recebido: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[Endeço IP:porta | nome do host:porta | endereço onion:porta] (seperados por vírgulas). A porta pode ser omitida quando a porta padrão (8333) for usada. settings.net.seedNode=Nó semente settings.net.directPeer=Par (direto) @@ -1011,8 +1018,8 @@ setting.about.support=Suporte Bisq setting.about.def=Bisq não é uma empresa — é um projeto aberto à participação da comunidade. Se você tem interesse em participar do projeto ou apoiá-lo, visite os links abaixo. setting.about.contribute=Contribuir setting.about.providers=Provedores de dados -setting.about.apisWithFee=O Bisq utiliza APIs de terceiros para obter os preços de moedas fiduciárias e de altcoins, assim como para estimar a taxa de mineração. -setting.about.apis=Bisq utiliza APIs de terceiros para os preços de moedas fiduciárias e altcoins. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Preços de mercado fornecidos por setting.about.feeEstimation.label=Estimativa da taxa de mineração fornecida por setting.about.versionDetails=Detalhes da versão @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Abrir informações de par account.tab.arbitratorRegistration=Registro de árbitro account.tab.mediatorRegistration=Registro de mediador account.tab.refundAgentRegistration=Registro de agente de reembolsos +account.tab.signing=Signing account.tab.account=Conta account.info.headline=Bem vindo à sua conta Bisq account.info.msg=Aqui você pode adicionar contas de negociação para moedas nacionais & altcoins e criar um backup da sua carteira e dados da sua conta.\n\nUma nova carteira Bitcoin foi criada na primeira vez em que você iniciou a Bisq.\nNós encorajamos fortemente que você anote as palavras semente da sua carteira Bitcoin (veja a aba no topo) e considere adicionar uma senha antes de depositar fundos. Depósitos e retiradas de Bitcoin são gerenciados na seção "Fundos".\n\nNota de privacidade & segurança: visto que a Bisq é uma exchange decentralizada, todos os seus dados são mantidos no seu computador. Não existem servidores, então não temos acesso às suas informações pessoais, seus fundos ou até mesmo ao seu endereço IP. Dados como número de conta bancária, endereços de Bitcoin & altcoin, etc apenas são compartilhados com seu parceiro de negociação para completar as negociações iniciadas por você (em caso de disputa, o mediador ou árbitro verá as mesmas informações que seu parceiro de negociação). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Quantia do pagamento do comprador disputeSummaryWindow.payoutAmount.seller=Quantia de pagamento do vendedor disputeSummaryWindow.payoutAmount.invert=Usar perdedor como publicador disputeSummaryWindow.reason=Motivo da disputa -disputeSummaryWindow.reason.bug=Bug (problema técnico) -disputeSummaryWindow.reason.usability=Usabilidade -disputeSummaryWindow.reason.protocolViolation=Violação de protocolo -disputeSummaryWindow.reason.noReply=Sem resposta -disputeSummaryWindow.reason.scam=Golpe (Scam) -disputeSummaryWindow.reason.other=Outro -disputeSummaryWindow.reason.bank=Banco + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Bug (problema técnico) +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Usabilidade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Violação de protocolo +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Sem resposta +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Golpe (Scam) +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Outro +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Banco +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Notas de resumo disputeSummaryWindow.addSummaryNotes=Adicionar notas de resumo disputeSummaryWindow.close.button=Fechar ticket -disputeSummaryWindow.close.msg=Ticket fechado em {0}\n\nResumo:\nPagamento para comprador de BTC: {1}\nPagamento para vendedor de BTC: {2}\n\nNotas do resumo:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nPróximos passos:\nAbra a negociação e aceite ou recuse a sugestão do mediador disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nPróximos passos:\nNão é preciso fazer mais nada. Se o árbitro decidir em seu favor, você verá uma transação com etiqueta "Reembolso de arbitração" em Fundos/Transações disputeSummaryWindow.close.closePeer=Você também precisa fechar o ticket dos parceiros de negociação! @@ -1985,7 +2013,8 @@ filterWindow.onions=Endereços onion filtrados (sep. por vírgula) filterWindow.accounts=Dados de conta de negociação filtrados:\nFormato: lista separada por vírgulas de [id do método de pagamento | dados | valor] filterWindow.bannedCurrencies=Códigos de moedas filtrados (sep. por vírgula) filterWindow.bannedPaymentMethods=IDs de método de pagamento filtrados (sep. por vírgula) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Árbitros filtrados (endereços onion sep. por vírgula) filterWindow.mediators=Mediadores filtrados (endereços onion separados por vírgula) filterWindow.refundAgents=Agentes de reembolso filtrados (endereços onion separados por vírgula) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=Você não possui fundos BTC suficien popup.warning.bsqChangeBelowDustException=Esta transação cria um troco BSQ menor do que o limite poeira (5.46 BSQ) e seria rejeitada pela rede Bitcoin.\nVocê precisa ou enviar uma quantia maior para evitar o troco (ex: adicionando a quantia poeira ao montante a ser enviado) ou adicionar mais fundos BSQ à sua carteira para evitar gerar uma saída de poeira.\nA saída de poeira é {0}. popup.warning.btcChangeBelowDustException=Esta transação cria um troco menor do que o limite poeira (546 Satoshi) e seria rejeitada pela rede Bitcoin.\nVocê precisa adicionar a quantia poeira ao montante de envio para evitar gerar uma saída de poeira.\nA saída de poeira é {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=Você não tem fundos de BSQ suficientes para pagar a taxa de negociação em BSQ. Você pode pagar a taxa em BTC ou você precisa depositar mais sua carteira BSQ. Você pode comprar BSQ no Bisq.\n\nFundos BSQ faltando: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Sua carteira BSQ não possui fundos suficientes para pagar a taxa de transação em BSQ. popup.warning.messageTooLong=Sua mensagem excede o tamanho máximo permitido. Favor enviá-la em várias partes ou utilizando um serviço como https://pastebin.com. popup.warning.lockedUpFunds=Você possui fundos travados em uma negociação com erro.\nSaldo travado: {0}\nEndereço da transação de depósito: {1}\nID da negociação: {2}.\n\nPor favor, abra um ticket de suporte selecionando a negociação na tela de negociações em aberto e depois pressionando "\alt+o\" ou \"option+o\". @@ -2437,6 +2466,7 @@ seed.restore.error=Ocorreu um erro ao restaurar as carteiras com palavras sement payment.account=Conta payment.account.no=Nº da conta payment.account.name=Nome da conta +payment.account.userName=User name payment.account.owner=Nome completo do titular da conta payment.account.fullName=Nome completo (nome e sobrenome) payment.account.state=Estado/Província/Região @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag: payment.moneyBeam.accountId=E-mail ou nº de telefone payment.venmo.venmoUserName=Nome do usuário do Venmo payment.popmoney.accountId=E-mail ou nº de telefone -payment.revolut.email=E-mail -payment.revolut.phoneNr=Nº de telefone registrado payment.promptPay.promptPayId=ID de cidadão/ID de impostos ou nº de telefone payment.supportedCurrencies=Moedas disponíveis payment.limitations=Limitações @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=Certifique-se de que o seu banco permite a realização de depósitos em espécie na conta de terceiros. -payment.revolut.info=Certifique-se de que o número de telefone que você usou para sua conta Revolut está registrado na Revolut, caso contrário o comprador de BTC não poderá enviar-lhe os fundos. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Ordens de pagamento são uma das formas de compra em dinheiro mais privadas disponíveis na Bisq.\n\nNo entanto, esteja atento ao potencial aumento nos riscos associados com essa forma de operar. A Bisq não se responsabilizará em hipótese alguma caso o dinheiro enviado seja perdido. Além disso, o mediador ou árbitro irá liberar o BTC à parte que envia o dinheiro, desde que existam informações e recibos atrelados ao pagamento. Aconselha-se que quem for enviar o dinheiro escreva o nome do vendedor de BTC na ordem de pagamento minimizando, assim, o risco que esta seja sacada por outra pessoa. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=Informações para contato payment.f2f.contact.prompt=Como prefere ser contatado pelo seu parceiro de negociação? (e-mail, telefone...) diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties index 70f75827de0..2cc66d379d9 100644 --- a/core/src/main/resources/i18n/displayStrings_pt.properties +++ b/core/src/main/resources/i18n/displayStrings_pt.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediação support.tab.arbitration.support=Arbitragem support.tab.legacyArbitration.support=Arbitragem Antiga support.tab.ArbitratorsSupportTickets=Bilhetes de {0} -support.filter=Lista de filtros +support.filter=Search disputes support.filter.prompt=Insira o ID do negócio, data, endereço onion ou dados da conta +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Enviar notificação privada +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=Não há bilhetes abertos support.sendingMessage=Enviando mensagem... support.receiverNotOnline=O recipiente não está online. A mensagem foi guardada na caixa de correio. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=Você solicitou mediação.\n\n{0}\n\nVers support.peerOpenedTicket=O seu par de negociação solicitou suporte devido a problemas técnicos.\n\n{0}\n\nVersão Bisq: {1} support.peerOpenedDispute=O seu par de negociação solicitou uma disputa.\n\n{0}\n\nVersão Bisq: {1} support.peerOpenedDisputeForMediation=O seu par de negociação solicitou uma mediação.\n\n{0}\n\nVersão Bisq: {1} -support.mediatorsDisputeSummary=Messagem do sistema:\nResumo do mediador sobre a disputa:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Endereço do nó do mediador: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Pares conectados settings.net.onionAddressColumn=Endereço onion settings.net.creationDateColumn=Estabelecida settings.net.connectionTypeColumn=Entrando/Saindo -settings.net.totalTrafficLabel=Tráfico total +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Ida-e-volta settings.net.sentBytesColumn=Enviado settings.net.receivedBytesColumn=Recebido @@ -989,7 +995,8 @@ settings.net.heightColumn=Altura settings.net.needRestart=Você precisa reiniciar o programa para aplicar essa alteração.\nVocê quer fazer isso agora?? settings.net.notKnownYet=Ainda desconhecido... -settings.net.sentReceived=Enviado: {0}, recebido: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[endereço IP:porta | nome de host:porta | endereço onion:porta] (separado por vírgula). A porta pode ser omitida se a padrão for usada (8333). settings.net.seedNode=Nó semente settings.net.directPeer=Par (direto) @@ -1011,8 +1018,8 @@ setting.about.support=Apoio Bisq setting.about.def=O Bisq não é uma empresa - é um projeto aberto à comunidade. Se você quiser participar ou apoiar o Bisq, siga os links abaixo. setting.about.contribute=Contribuir setting.about.providers=Provedores de dados -setting.about.apisWithFee=A Bisq usa APIs de terceiros para os preços de mercado de moedas fiduciárias e Altcoin, bem como para estimativas de taxas de mineração. -setting.about.apis=Bisq utiliza APIs de terceiros para os preços de moedas fiduciárias e altcoins. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Preços de mercado fornecidos por setting.about.feeEstimation.label=Taxa de mineração fornecida por setting.about.versionDetails=Detalhes da versão @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Abrir informação do par account.tab.arbitratorRegistration=Registo de árbitro account.tab.mediatorRegistration=Registo do Mediador account.tab.refundAgentRegistration=Registro de agente de reembolso +account.tab.signing=Signing account.tab.account=Conta account.info.headline=Bem vindo à sua conta Bisq account.info.msg=Aqui você pode adicionar contas de negociação para moedas nacionais e altcoins e criar um backup da sua carteira e dos dados da conta.\n\nUma nova carteira de Bitcoin foi criada na primeira vez que você iniciou o Bisq.\n\nÉ altamente recomendável que você anote as sua palavras-semente da carteira do Bitcoin (consulte a guia na parte superior) e considere adicionar uma senha antes do financiamento. Depósitos e retiradas de Bitcoin são gerenciados na secção \"Fundos\".\n\nNota sobre privacidade e segurança: como o Bisq é uma exchange descentralizada, todos os seus dados são mantidos no seu computador. Como não há servidores, não temos acesso às suas informações pessoais, fundos ou mesmo seu endereço IP. Dados como números de contas bancárias, endereços de altcoin e Bitcoin etc. são compartilhados apenas com seu par de negociação para realizar negociações iniciadas (no caso de uma disputa, o mediador ou o árbitro verá os mesmos dados que o seu parceiro de negociação). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Quantia de pagamento do comprador disputeSummaryWindow.payoutAmount.seller=Quantia de pagamento do vendedor disputeSummaryWindow.payoutAmount.invert=Usar perdedor como publicador disputeSummaryWindow.reason=Razão da disputa -disputeSummaryWindow.reason.bug=Erro -disputeSummaryWindow.reason.usability=Usabilidade -disputeSummaryWindow.reason.protocolViolation=Violação de protocolo -disputeSummaryWindow.reason.noReply=Sem resposta -disputeSummaryWindow.reason.scam=Golpe -disputeSummaryWindow.reason.other=Outro -disputeSummaryWindow.reason.bank=Banco + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Erro +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Usabilidade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Violação de protocolo +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Sem resposta +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Golpe +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Outro +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Banco +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Notas de resumo disputeSummaryWindow.addSummaryNotes=Adicionar notas de resumo disputeSummaryWindow.close.button=Fechar bilhete -disputeSummaryWindow.close.msg=Bilhete fechado em {0}\n\nResumo:\nQuantia do pagamento para comprador de BTC: {1}\nQuantia do pagamento para vendedor de BTC: {2}\n\nNotas de resumo:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nPróximos passos:\nAbrir negócio e aceitar ou rejeitar as sugestões do mediador disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nPróximos passos:\nNenhuma ação adicional é necessária. Se o árbitro decidir a seu favor, você verá uma transação de "Reembolso da arbitragem" em Fundos/Transações disputeSummaryWindow.close.closePeer=Você também precisa fechar o bilhete dos pares de negociação! @@ -1985,7 +2013,8 @@ filterWindow.onions=Endereços onion filtrados (sep. por vírgula) filterWindow.accounts=Dados da conta de negociação filtrados:\nFormato: lista de [id de método de pagamento | campo de dados | valor] sep. por vírgula filterWindow.bannedCurrencies=Códigos de moedas filtrados (sep. por vírgula) filterWindow.bannedPaymentMethods=IDs de método de pagamento filtrados (sep. por vírgula) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Árbitros filtrados (endereços onion sep. por vírgula) filterWindow.mediators=Mediadores filtrados (endereços onion separados por vírgula) filterWindow.refundAgents=Agentes de reembolso filtrados (endereços onion sep. por virgula) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=Você não tem fundos BTC suficientes popup.warning.bsqChangeBelowDustException=Esta transação cria um output de trocos de BSQ que está abaixo do limite de poeira (5,46 BSQ) e seria rejeitada pela rede do bitcoin.\n\nVocê precisa enviar uma quantia mais elevada para evitar o output de trocos (por exemplo, adicionando a quantia de poeira ao seu montante a ser enviado) ou adicionar mais fundos de BSQ à sua carteira de modo a evitar a gerar um output de poeira.\n\nO output de poeira é {0}. popup.warning.btcChangeBelowDustException=Esta transação cria um output de trocos que está abaixo do limite de poeira (546 satoshis) e seria rejeitada pela rede do bitcoin.\n\nVocê precisa adicionar a quantia de poeira ao seu montante à ser enviado para evitar gerar um output de poeira.\n\nO output de poeira é {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=Você não tem fundos de BSQ suficientes para pagar a taxa de negócio em BSQ. Você pode pagar a taxa em BTC ou você precisa financiar sua carteira BSQ. Você pode comprar BSQ no Bisq.\n\nFundos BSQ em falta: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Sua carteira BSQ não possui fundos suficientes para pagar a taxa de negócio em BSQ. popup.warning.messageTooLong=Sua mensagem excede o tamanho máx. permitido. Por favor enviá-la em várias partes ou carregá-la utilizando um serviço como https://pastebin.com. popup.warning.lockedUpFunds=Você trancou fundos de um negócio falhado..\nSaldo trancado: {0} \nEndereço da tx de Depósito: {1}\nID de negócio: {2}.\n\nPor favor abra um bilhete de apoio selecionando o negócio no ecrã de negócios abertos e pressione \"alt + o\" ou \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=Um erro ocorreu ao restaurar as carteiras com palavras-sement payment.account=Conta payment.account.no=Nº da conta payment.account.name=Nome da conta +payment.account.userName=User name payment.account.owner=Nome completo do titular da conta payment.account.fullName=Nome completo (primeiro, nome do meio, último) payment.account.state=Estado/Província/Região @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=Email ou nº de telemóvel payment.venmo.venmoUserName=Nome de utilizador do Venmo payment.popmoney.accountId=Email ou nº de telemóvel -payment.revolut.email=Email -payment.revolut.phoneNr=Nº de telemóvel registado payment.promptPay.promptPayId=ID de cidadão/ID de impostos ou nº de telemóvel payment.supportedCurrencies=Moedas suportadas payment.limitations=Limitações @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=Por favor, confirme que seu banco permite-lhe enviar depósitos em dinheiro para contas de outras pessoas. Por exemplo, o Bank of America e o Wells Fargo não permitem mais esses depósitos. -payment.revolut.info=Certifique-se de que o número de telemóvel que você usou para sua conta Revolut está registado na Revolut, caso contrário o comprador de BTC não poderá enviar-lhe os fundos. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=As Ordens de pagamento são um dos métodos de compra de moedas fiduciárias mais privados disponíveis no Bisq.\n\nEntretanto, esteja ciente dos riscos potencialmente aumentados associados ao seu uso. O Bisq não assumirá qualquer responsabilidade no caso de uma ordem de pagamento enviada ser roubada, e o mediador ou o árbitro, nesses casos, concederão o BTC ao remetente da ordem de pagamento, desde que possam produzir informações de rastreamento e recibos. Pode ser aconselhável que o remetente escreva o nome do vendedor de BTC na ordem de pagamento, a fim de minimizar o risco de que a ordem de pagamento seja levantado por outra pessoa. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=Informação de contacto payment.f2f.contact.prompt=Como deseja ser contactado pelo par de negociação? (endereço de e-mail, número de telefone, ...) diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties index 74cd61e260e..c0124177f9a 100644 --- a/core/src/main/resources/i18n/displayStrings_ru.properties +++ b/core/src/main/resources/i18n/displayStrings_ru.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediation support.tab.arbitration.support=Arbitration support.tab.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets -support.filter=Фильтры +support.filter=Search disputes support.filter.prompt=Введите идентификатор сделки, дату, onion-адрес или данные учётной записи +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Отправить личное уведомление +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=Нет текущих обращений support.sendingMessage=Отправка сообщения... support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq ver support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1} support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1} support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} -support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Подключенные пиры settings.net.onionAddressColumn=Onion-адрес settings.net.creationDateColumn=Создано settings.net.connectionTypeColumn=Вх./Вых. -settings.net.totalTrafficLabel=Общий трафик +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Задержка settings.net.sentBytesColumn=Отправлено settings.net.receivedBytesColumn=Получено @@ -989,7 +995,8 @@ settings.net.heightColumn=Height settings.net.needRestart=Необходимо перезагрузить приложение, чтобы применить это изменение.\nСделать это сейчас? settings.net.notKnownYet=Пока неизвестно... -settings.net.sentReceived=Отправлено: {0}, получено: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[IP-адрес:порт | хост:порт | onion-адрес:порт] (через запятые). Порт можно не указывать, если используется порт по умолчанию (8333). settings.net.seedNode=Исходный узел settings.net.directPeer=Пир (прямой) @@ -1011,8 +1018,8 @@ setting.about.support=Поддержать Bisq setting.about.def=Bisq не является компанией, а представляет собой общественный проект, открытый для участия. Если вы хотите принять участие или поддержать Bisq, перейдите по ссылкам ниже. setting.about.contribute=Помочь setting.about.providers=Источники данных -setting.about.apisWithFee=Bisq использует сторонние API для определения рыночного курса валют и альткойнов, а также расчёта комиссии майнера. -setting.about.apis=Bisq использует сторонние API для определения рыночного курса валют и альткойнов. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Рыночный курс предоставлен setting.about.feeEstimation.label=Расчёт комиссии майнера предоставлен setting.about.versionDetails=Подробности версии @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar o account.tab.arbitratorRegistration=Регистрация арбитра account.tab.mediatorRegistration=Mediator registration account.tab.refundAgentRegistration=Refund agent registration +account.tab.signing=Signing account.tab.account=Счёт account.info.headline=Добро пожаловать в ваш счёт Bisq account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\nA new Bitcoin wallet was created the first time you started Bisq.\n\nWe strongly recommend that you write down your Bitcoin wallet seed words (see tab on the top) and consider adding a password before funding. Bitcoin deposits and withdrawals are managed in the \"Funds\" section.\n\nPrivacy & security note: because Bisq is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no access to your personal info, your funds, or even your IP address. Data such as bank account numbers, altcoin & Bitcoin addresses, etc are only shared with your trading partner to fulfill trades you initiate (in case of a dispute the mediator or arbitrator will see the same data as your trading peer). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Сумма выплаты покупат disputeSummaryWindow.payoutAmount.seller=Сумма выплаты продавца disputeSummaryWindow.payoutAmount.invert=Проигравший публикует disputeSummaryWindow.reason=Причина спора -disputeSummaryWindow.reason.bug=Ошибка -disputeSummaryWindow.reason.usability=Удобство использования -disputeSummaryWindow.reason.protocolViolation=Нарушение протокола -disputeSummaryWindow.reason.noReply=Отсутствие ответа -disputeSummaryWindow.reason.scam=Мошенничество -disputeSummaryWindow.reason.other=Другое -disputeSummaryWindow.reason.bank=Банк + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Ошибка +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Удобство использования +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Нарушение протокола +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Отсутствие ответа +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Мошенничество +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Другое +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Банк +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Примечания disputeSummaryWindow.addSummaryNotes=Добавить примечания disputeSummaryWindow.close.button=Закрыть обращение -disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nSummary notes:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\nOpen trade and accept or reject suggestion from mediator disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\nNo further action is required from you. If the arbitrator decided in your favor, you'll see a "Refund from arbitration" transaction in Funds/Transactions disputeSummaryWindow.close.closePeer=Вам также необходимо закрыть обращение контрагента! @@ -1985,7 +2013,8 @@ filterWindow.onions=Отфильтрованные onion-адреса (чере filterWindow.accounts=Отфильтрованные данные торгового счёта:\nФормат: список, через запятые [идентификатор метода платежа | поле данных | значение] filterWindow.bannedCurrencies=Отфильтрованные коды валют (через запят.) filterWindow.bannedPaymentMethods=Отфильтрованные идент. методов платежа (через запят.) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Отфильтрованные арбитры (onion-адреса через запят.) filterWindow.mediators=Filtered mediators (comma sep. onion addresses) filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=У вас недостаточно BT popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\nYou need to either send a higher amount to avoid the change output (e.g. by adding the dust amount to your sending amount) or add more BSQ funds to your wallet so you avoid to generate a dust output.\n\nThe dust output is {0}. popup.warning.btcChangeBelowDustException=This transaction creates a change output which is below dust limit (546 Satoshi) and would be rejected by the Bitcoin network.\n\nYou need to add the dust amount to your sending amount to avoid to generate a dust output.\n\nThe dust output is {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=У Вас недостаточно BSQ для оплаты комиссии в BSQ. Вы можете оплатить комиссию в BTC или пополнить свой кошелёк BSQ. BSQ можно купить в Bisq.\n\nНедостающая сумма в BSQ: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=В вашем кошельке BSQ недостаточно средств для оплаты комиссии за сделку в BSQ. popup.warning.messageTooLong=Ваше сообщение превышает макс. разрешённый размер. Разбейте его на несколько частей или загрузите в веб-приложение для работы с отрывками текста, например https://pastebin.com. popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\nLocked up balance: {0} \nDeposit tx address: {1}\nTrade ID: {2}.\n\nPlease open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=Произошла ошибка при восстановле payment.account=Счёт payment.account.no=Номер счёта payment.account.name=Название счёта +payment.account.userName=User name payment.account.owner=Полное имя владельца счёта payment.account.fullName=Полное имя (имя, отчество, фамилия) payment.account.state=Штат/Провинция/Область @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=Эл. адрес или тел. номер payment.venmo.venmoUserName=Имя пользователя Venmo payment.popmoney.accountId=Эл. адрес или тел. номер -payment.revolut.email=Электронный адрес -payment.revolut.phoneNr=Зарегистрированный номер телефона payment.promptPay.promptPayId=Удостовер. личности / налог. номер или номер телефона payment.supportedCurrencies=Поддерживаемые валюты payment.limitations=Ограничения @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=Убедитесь, что ваш банк позволяет отправлять денежные переводы на счета других лиц. Например, Bank of America и Wells Fargo больше не разрешают такие переводы. -payment.revolut.info=Убедитесь, что номер телефона, который вы использовали для своего счета Revolut, зарегистрирован в Revolut. Иначе, покупатель BTC не сможет отправить вам средства. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Money orders are one of the more private fiat purchase methods available on Bisq.\n\nHowever, please be aware of potentially increased risks associated with their use. Bisq will not bear any responsibility in case a sent money order is stolen, and the mediator or arbitrator will in such cases award the BTC to the sender of the money order, provided they can produce tracking information and receipts. It may be advisable for the sender to write the BTC seller's name on the money order, in order to minimize the risk that the money order is cashed by someone else. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=Контактная информация payment.f2f.contact.prompt=Как вы хотите связаться с контрагентом? (электронная почта, телефон...) diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties index 7688013f4a0..378c2344b48 100644 --- a/core/src/main/resources/i18n/displayStrings_th.properties +++ b/core/src/main/resources/i18n/displayStrings_th.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediation support.tab.arbitration.support=Arbitration support.tab.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets -support.filter=รายการตัวกรอง +support.filter=Search disputes support.filter.prompt=Enter trade ID, date, onion address or account data +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=ส่งการแจ้งเตือนส่วนตัว +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=ไม่มีการเปิดรับคำขอร้องหรือความช่วยเหลือ support.sendingMessage=กำลังส่งข้อความ... support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq ver support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1} support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1} support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} -support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=เชื่อมต่อกับเน็ตเ settings.net.onionAddressColumn=ที่อยู่ Onion settings.net.creationDateColumn=ที่จัดตั้งขึ้น settings.net.connectionTypeColumn=เข้า/ออก -settings.net.totalTrafficLabel=ปริมาณการเข้าชมทั้งหมด +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=ไป - กลับ settings.net.sentBytesColumn=ส่งแล้ว settings.net.receivedBytesColumn=ได้รับแล้ว @@ -989,7 +995,8 @@ settings.net.heightColumn=Height settings.net.needRestart=คุณต้องรีสตาร์ทแอ็พพลิเคชั่นเพื่อทำให้การเปลี่ยนแปลงนั้นเป็นผล\nคุณต้องการทำตอนนี้หรือไม่ settings.net.notKnownYet=ยังไม่ทราบ ... -settings.net.sentReceived=ส่ง: {0} ได้รับ: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[ที่อยู่ IP: พอร์ต | ชื่อโฮสต์: พอร์ต | ที่อยู่ onion: พอร์ต] (คั่นด้วยเครื่องหมายจุลภาค) Port สามารถละเว้นได้ถ้าใช้ค่าเริ่มต้น (8333) settings.net.seedNode=แหล่งโหนดข้อมูล settings.net.directPeer=Peer (โดยตรง) @@ -1011,8 +1018,8 @@ setting.about.support=สนับสนุน Bisq setting.about.def=Bisq ไม่ใช่ บริษัท แต่เป็นโปรเจคชุมชนและเปิดให้คนมีส่วนร่วม ถ้าคุณต้องการจะเข้าร่วมหรือสนับสนุน Bisq โปรดทำตามลิงค์ข้างล่างนี้ setting.about.contribute=สนับสนุน setting.about.providers=ผู้ให้บริการข้อมูล -setting.about.apisWithFee=Bisq ใช้ APIs ของบุคคลที่ 3 สำหรับราคาตลาดของ Fiat และ Altcoin ตลอดจนการประมาณค่าการขุด -setting.about.apis=Bisq ใช้ APIs ของบุคคลที่ 3 สำหรับ Fiat และ Altcoin ในราคาตลาด +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=ราคาตลาดจัดโดย setting.about.feeEstimation.label=การประมาณค่าธรรมเนียมการขุดโดย setting.about.versionDetails=รายละเอียดของเวอร์ชั่น @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar o account.tab.arbitratorRegistration=การลงทะเบียนผู้ไกล่เกลี่ย account.tab.mediatorRegistration=Mediator registration account.tab.refundAgentRegistration=Refund agent registration +account.tab.signing=Signing account.tab.account=บัญชี account.info.headline=ยินดีต้อนรับสู่บัญชี Bisq ของคุณ account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\nA new Bitcoin wallet was created the first time you started Bisq.\n\nWe strongly recommend that you write down your Bitcoin wallet seed words (see tab on the top) and consider adding a password before funding. Bitcoin deposits and withdrawals are managed in the \"Funds\" section.\n\nPrivacy & security note: because Bisq is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no access to your personal info, your funds, or even your IP address. Data such as bank account numbers, altcoin & Bitcoin addresses, etc are only shared with your trading partner to fulfill trades you initiate (in case of a dispute the mediator or arbitrator will see the same data as your trading peer). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=จำนวนเงินที่จ disputeSummaryWindow.payoutAmount.seller=จำนวนเงินที่จ่ายของผู้ขาย disputeSummaryWindow.payoutAmount.invert=ใช้ผู้แพ้เป็นผู้เผยแพร่ disputeSummaryWindow.reason=เหตุผลในการพิพาท -disputeSummaryWindow.reason.bug=ปัญหา -disputeSummaryWindow.reason.usability=การใช้งาน -disputeSummaryWindow.reason.protocolViolation=การละเมิดโปรโตคอล -disputeSummaryWindow.reason.noReply=ไม่มีการตอบ -disputeSummaryWindow.reason.scam=การหลอกลวง -disputeSummaryWindow.reason.other=อื่น ๆ -disputeSummaryWindow.reason.bank=ธนาคาร + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=ปัญหา +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=การใช้งาน +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=การละเมิดโปรโตคอล +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=ไม่มีการตอบ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=การหลอกลวง +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=อื่น ๆ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=ธนาคาร +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=สรุปบันทึกย่อ disputeSummaryWindow.addSummaryNotes=เพิ่มสรุปบันทึกย่อ: disputeSummaryWindow.close.button=ปิดการยื่นคำขอและความช่วยเหลือ -disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nSummary notes:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\nOpen trade and accept or reject suggestion from mediator disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\nNo further action is required from you. If the arbitrator decided in your favor, you'll see a "Refund from arbitration" transaction in Funds/Transactions disputeSummaryWindow.close.closePeer=คุณจำเป็นต้องยุติคำขอความช่วยเหลือคู่ค้าด้วย ! @@ -1985,7 +2013,8 @@ filterWindow.onions=ที่อยู่ onion ที่ได้รับก filterWindow.accounts=ข้อมูลบัญชีการซื้อขายที่ถูกกรอง: \nรูปแบบ: เครื่องหมายจุลภาค รายการของ [id วิธีการชำระเงิน | ด้านข้อมูล | มูลค่า] filterWindow.bannedCurrencies=รหัสโค้ดสกุลเงินที่ได้รับการกรอง (คั่นด้วยเครื่องหมายจุลภาค) filterWindow.bannedPaymentMethods=รหัส ID วิธีการชำระเงินที่ได้รับการกรอง (คั่นด้วยเครื่องหมายจุลภาค) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=ผู้ไกล่เกลี่ยที่ได้รับการคัดกรอง (คั่นด้วยเครื่องหมายจุลภาค ที่อยู่ onion) filterWindow.mediators=Filtered mediators (comma sep. onion addresses) filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=คุณไม่มีเงิน popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\nYou need to either send a higher amount to avoid the change output (e.g. by adding the dust amount to your sending amount) or add more BSQ funds to your wallet so you avoid to generate a dust output.\n\nThe dust output is {0}. popup.warning.btcChangeBelowDustException=This transaction creates a change output which is below dust limit (546 Satoshi) and would be rejected by the Bitcoin network.\n\nYou need to add the dust amount to your sending amount to avoid to generate a dust output.\n\nThe dust output is {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=คุณไม่มีเงินทุน BSQ เพียงพอสำหรับการจ่ายค่าธรรมเนียมการเทรดใน BSQ คุณสามารถชำระได้ใน BTC หรือคุณสามารถใช้เงินทุนจากกระเป๋าสตางค์ BSQ ของคุณ คุณสามารถซื้อ BSQ ใน Bisq ได้ \n\nกองทุน BSQ ที่หายไป: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=กระเป๋าสตางค์ BSQ ของคุณไม่มีจำนวนเงินทุนที่มากพอสำหรับการชำระการเทรดใน BSQ popup.warning.messageTooLong=ข้อความของคุณเกินขีดจำกัดสูงสุดที่อนุญาต โปรดแบ่งส่งเป็นหลายส่วนหรืออัปโหลดไปยังบริการเช่น https://pastebin.com popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\nLocked up balance: {0} \nDeposit tx address: {1}\nTrade ID: {2}.\n\nPlease open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=เกิดข้อผิดพลาดขณะกู้ payment.account=บัญชี payment.account.no=หมายเลขบัญชี payment.account.name=ชื่อบัญชี +payment.account.userName=User name payment.account.owner=ชื่อเต็มของเจ้าของบัญชี payment.account.fullName=ชื่อเต็ม (ชื่อจริง, ชื่อกลาง, นามสกุล) payment.account.state=รัฐ / จังหวัด / ภูมิภาค @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=อีเมลหรือหมายเลขโทรศัพท์ payment.venmo.venmoUserName=ชื่อผู้ใช้ Venmo payment.popmoney.accountId=อีเมลหรือหมายเลขโทรศัพท์ -payment.revolut.email=อีเมล -payment.revolut.phoneNr=Registered phone no. payment.promptPay.promptPayId=รหัสบัตรประชาชน/รหัสประจำตัวผู้เสียภาษี หรือเบอร์โทรศัพท์ payment.supportedCurrencies=สกุลเงินที่ได้รับการสนับสนุน payment.limitations=ข้อจำกัด @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=โปรดยืนยันว่าธนาคารของคุณได้อนุมัติให้คุณสามารถส่งเงินสดให้กับบัญชีบุคคลอื่นได้ ตัวอย่างเช่น บางธนาคารที่ไม่ได้มีการบริการถ่ายโอนเงินสดอย่าง Bank of America และ Wells Fargo -payment.revolut.info=Please be sure that the phone number you used for your Revolut account is registered at Revolut otherwise the BTC buyer cannot send you the funds. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Money orders are one of the more private fiat purchase methods available on Bisq.\n\nHowever, please be aware of potentially increased risks associated with their use. Bisq will not bear any responsibility in case a sent money order is stolen, and the mediator or arbitrator will in such cases award the BTC to the sender of the money order, provided they can produce tracking information and receipts. It may be advisable for the sender to write the BTC seller's name on the money order, in order to minimize the risk that the money order is cashed by someone else. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=ข้อมูลติดต่อ payment.f2f.contact.prompt=วิธีการที่คุณต้องการได้รับการติดต่อจากการค้าจากระบบ peer (ที่อยู่อีเมล , หมายเลขโทรศัพท์ ... ) diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties index 137dd2909f9..5bf014ceaa4 100644 --- a/core/src/main/resources/i18n/displayStrings_vi.properties +++ b/core/src/main/resources/i18n/displayStrings_vi.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=Mediation support.tab.arbitration.support=Arbitration support.tab.legacyArbitration.support=Legacy Arbitration support.tab.ArbitratorsSupportTickets={0}'s tickets -support.filter=Danh sách lọc +support.filter=Search disputes support.filter.prompt=Nhập ID giao dịch, ngày tháng, địa chỉ onion hoặc dữ liệu tài khoản +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=Gửi thông báo riêng tư +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=Không có đơn hỗ trợ được mở support.sendingMessage=Đang gửi tin nhắn... support.receiverNotOnline=Receiver is not online. Message is saved to their mailbox. @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=You requested mediation.\n\n{0}\n\nBisq ver support.peerOpenedTicket=Your trading peer has requested support due to technical problems.\n\n{0}\n\nBisq version: {1} support.peerOpenedDispute=Your trading peer has requested a dispute.\n\n{0}\n\nBisq version: {1} support.peerOpenedDisputeForMediation=Your trading peer has requested mediation.\n\n{0}\n\nBisq version: {1} -support.mediatorsDisputeSummary=System message:\nMediator''s dispute summary:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=Mediator''s node address: {0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=Các đối tác được kết nối settings.net.onionAddressColumn=Địa chỉ onion settings.net.creationDateColumn=Đã thiết lập settings.net.connectionTypeColumn=Vào/Ra -settings.net.totalTrafficLabel=Tổng lưu lượng +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=Khứ hồi settings.net.sentBytesColumn=Đã gửi settings.net.receivedBytesColumn=Đã nhận @@ -989,7 +995,8 @@ settings.net.heightColumn=Height settings.net.needRestart=Bạn cần khởi động lại ứng dụng để thay đổi.\nBạn có muốn khởi động bây giờ không? settings.net.notKnownYet=Chưa biết... -settings.net.sentReceived=Đã gửi: {0}, đã nhận: {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=[Địa chỉ IP:tên cổng | máy chủ:cổng | Địa chỉ onion:cổng] (tách bằng dấu phẩy). Cổng có thể bỏ qua nếu sử dụng mặc định (8333). settings.net.seedNode=nút cung cấp thông tin settings.net.directPeer=Đối tác (trực tiếp) @@ -1011,8 +1018,8 @@ setting.about.support=Hỗ trợ Bisq setting.about.def=Bisq không phải là một công ty mà là một dự án mở cho cả cộng đồng. Nếu bạn muốn tham gia hoặc hỗ trợ Bisq, vui lòng truy cập link dưới đây. setting.about.contribute=Góp vốn setting.about.providers=Nhà cung cấp dữ liệu -setting.about.apisWithFee=Bisq sử dụng API bên thứ 3 để ước tính giá thị trường Fiat và Altcoin cũng như phí đào. -setting.about.apis=Bisq sử dụng API bên thứ 3 để ước tính giá thị trường Fiat và Altcoin. +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=Giá thị trường cung cấp bởi setting.about.feeEstimation.label=Ước tính phí đào cung cấp bởi setting.about.versionDetails=Thông tin về phiên bản @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar o account.tab.arbitratorRegistration=Đăng ký trọng tài account.tab.mediatorRegistration=Mediator registration account.tab.refundAgentRegistration=Refund agent registration +account.tab.signing=Signing account.tab.account=Tài khoản account.info.headline=Chào mừng đến với tài khoản Bisq của bạn account.info.msg=Here you can add trading accounts for national currencies & altcoins and create a backup of your wallet & account data.\n\nA new Bitcoin wallet was created the first time you started Bisq.\n\nWe strongly recommend that you write down your Bitcoin wallet seed words (see tab on the top) and consider adding a password before funding. Bitcoin deposits and withdrawals are managed in the \"Funds\" section.\n\nPrivacy & security note: because Bisq is a decentralized exchange, all your data is kept on your computer. There are no servers, so we have no access to your personal info, your funds, or even your IP address. Data such as bank account numbers, altcoin & Bitcoin addresses, etc are only shared with your trading partner to fulfill trades you initiate (in case of a dispute the mediator or arbitrator will see the same data as your trading peer). @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=Khoản tiền hoàn lại của ngườ disputeSummaryWindow.payoutAmount.seller=Khoản tiền hoàn lại của người bán disputeSummaryWindow.payoutAmount.invert=Sử dụng người thua như người công bố disputeSummaryWindow.reason=Lý do khiếu nại -disputeSummaryWindow.reason.bug=Sự cố -disputeSummaryWindow.reason.usability=Khả năng sử dụng -disputeSummaryWindow.reason.protocolViolation=Vi phạm giao thức -disputeSummaryWindow.reason.noReply=Không có phản hồi -disputeSummaryWindow.reason.scam=Scam -disputeSummaryWindow.reason.other=Khác -disputeSummaryWindow.reason.bank=Ngân hàng + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Sự cố +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=Khả năng sử dụng +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=Vi phạm giao thức +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=Không có phản hồi +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=Scam +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=Khác +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=Ngân hàng +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=Lưu ý tóm tắt disputeSummaryWindow.addSummaryNotes=Thêm lưu ý tóm tắt disputeSummaryWindow.close.button=Đóng đơn -disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nSummary notes:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\nNext steps:\nOpen trade and accept or reject suggestion from mediator disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\nNext steps:\nNo further action is required from you. If the arbitrator decided in your favor, you'll see a "Refund from arbitration" transaction in Funds/Transactions disputeSummaryWindow.close.closePeer=Bạn cũng cần phải đóng Đơn Đối tác giao dịch! @@ -1985,7 +2013,8 @@ filterWindow.onions=Địa chỉ onion đã lọc (cách nhau bằng dấu phẩ filterWindow.accounts=Dữ liệu tài khoản giao dịch đã lọc:\nĐịnh dạng: cách nhau bằng dấu phẩy danh sách [ID phương thức thanh toán | trường dữ liệu | giá trị] filterWindow.bannedCurrencies=Mã tiền tệ đã lọc (cách nhau bằng dấu phẩy) filterWindow.bannedPaymentMethods=ID phương thức thanh toán đã lọc (cách nhau bằng dấu phẩy) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=Các trọng tài đã lọc (địa chỉ onion cách nhau bằng dấu phẩy) filterWindow.mediators=Filtered mediators (comma sep. onion addresses) filterWindow.refundAgents=Filtered refund agents (comma sep. onion addresses) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=Bạn không có đủ vốn BTC đ popup.warning.bsqChangeBelowDustException=This transaction creates a BSQ change output which is below dust limit (5.46 BSQ) and would be rejected by the Bitcoin network.\n\nYou need to either send a higher amount to avoid the change output (e.g. by adding the dust amount to your sending amount) or add more BSQ funds to your wallet so you avoid to generate a dust output.\n\nThe dust output is {0}. popup.warning.btcChangeBelowDustException=This transaction creates a change output which is below dust limit (546 Satoshi) and would be rejected by the Bitcoin network.\n\nYou need to add the dust amount to your sending amount to avoid to generate a dust output.\n\nThe dust output is {0}. -popup.warning.insufficientBsqFundsForBtcFeePayment=Bạn không đủ BSQ để thanh toán phí giao dịch bằng BSQ. Bạn có thể thanh toán phí bằng BTC hoặc nạp tiền vào ví BSQ của bạn. Bạn có thể mua BSQ tại Bisq.\nSố BSQ còn thiếu: {0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=Ví BSQ của bạn không đủ tiền để trả phí giao dịch bằng BSQ. popup.warning.messageTooLong=Tin nhắn của bạn vượt quá kích cỡ tối đa cho phép. Vui lòng gửi thành nhiều lần hoặc tải lên mạng như https://pastebin.com. popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\nLocked up balance: {0} \nDeposit tx address: {1}\nTrade ID: {2}.\n\nPlease open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." @@ -2437,6 +2466,7 @@ seed.restore.error=Có lỗi xảy ra khi khôi phục ví với Seed words.{0} payment.account=Tài khoản payment.account.no=Tài khoản số payment.account.name=Tên tài khoản +payment.account.userName=User name payment.account.owner=Họ tên chủ tài khoản payment.account.fullName=Họ tên (họ, tên lót, tên) payment.account.state=Bang/Tỉnh/Vùng @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=Email hoặc số điện thoại payment.venmo.venmoUserName=Tên người dùng Venmo payment.popmoney.accountId=Email hoặc số điện thoại -payment.revolut.email=Email -payment.revolut.phoneNr=Số điện thoại đã đăng ký. payment.promptPay.promptPayId=ID công dân/ ID thuế hoặc số điện thoại payment.supportedCurrencies=Tiền tệ hỗ trợ payment.limitations=Hạn chế @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=Vui lòng xác nhận rằng ngân hàng của bạn cho phép nạp tiền mặt vào tài khoản của người khác. Chẳng hạn, Ngân Hàng Mỹ và Wells Fargo không còn cho phép nạp tiền như vậy nữa. -payment.revolut.info=Vui lòng đảm bảo rằng số điện thoại bạn dùng cho tài khoản Revolut đã được đăng ký tại Revolut nếu không thì người mua BTC sẽ không thể chuyển tiền cho bạn. +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=Money orders are one of the more private fiat purchase methods available on Bisq.\n\nHowever, please be aware of potentially increased risks associated with their use. Bisq will not bear any responsibility in case a sent money order is stolen, and the mediator or arbitrator will in such cases award the BTC to the sender of the money order, provided they can produce tracking information and receipts. It may be advisable for the sender to write the BTC seller's name on the money order, in order to minimize the risk that the money order is cashed by someone else. +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=thông tin liên hệ payment.f2f.contact.prompt=Bạn muốn liên hệ với đối tác giao dịch qua đâu? (email, địa chỉ, số điện thoại,....) diff --git a/core/src/main/resources/i18n/displayStrings_zh-hans.properties b/core/src/main/resources/i18n/displayStrings_zh-hans.properties index 24259e586ca..7c08c8015b7 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hans.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hans.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=调解 support.tab.arbitration.support=仲裁 support.tab.legacyArbitration.support=历史仲裁 support.tab.ArbitratorsSupportTickets={0} 的工单 -support.filter=筛选列表 +support.filter=Search disputes support.filter.prompt=输入 交易 ID、日期、洋葱地址或账户信息 +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=发送私人通知 +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=没有创建的话题 support.sendingMessage=发送消息... support.receiverNotOnline=收件人未在线。消息被保存到他们的邮箱。 @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=您创建了一个调解请求。\n\n{0}\n\ support.peerOpenedTicket=对方因技术问题请求获取帮助。\n\n{0}\n\nBisq 版本:{1} support.peerOpenedDispute=对方创建了一个纠纷请求。\n\n{0}\n\nBisq 版本:{1} support.peerOpenedDisputeForMediation=对方创建了一个调解请求。\n\n{0}\n\nBisq 版本:{1} -support.mediatorsDisputeSummary=系统消息:\n调解纠纷总结:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=仲裁员的节点地址:{0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=已连接节点 settings.net.onionAddressColumn=匿名地址 settings.net.creationDateColumn=已建立连接 settings.net.connectionTypeColumn=入/出 -settings.net.totalTrafficLabel=总流量: +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=延迟 settings.net.sentBytesColumn=发送 settings.net.receivedBytesColumn=接收 @@ -989,7 +995,8 @@ settings.net.heightColumn=高度 settings.net.needRestart=您需要重启应用程序以同意这次变更。\n您需要现在重启吗? settings.net.notKnownYet=至今未知... -settings.net.sentReceived=发送 {0},接收 {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=添加逗号分隔的 IP 地址及端口,如使用8333端口可不填写。 settings.net.seedNode=种子节点 settings.net.directPeer=节点(直连) @@ -1011,8 +1018,8 @@ setting.about.support=支持 Bisq setting.about.def=Bisq 不是一个公司,而是一个社区项目,开放参与。如果您想参与或支持 Bisq,请点击下面连接。 setting.about.contribute=贡献 setting.about.providers=数据提供商 -setting.about.apisWithFee=Bisq 使用第三方 API 获取法定货币与虚拟币的市场价以及矿工手续费的估价。 -setting.about.apis=Bisq 使用第三方 API 获取法定货币与虚拟币的市场价。 +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=交易所价格提供商 setting.about.feeEstimation.label=矿工手续费估算提供商 setting.about.versionDetails=版本详情 @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=打开在头像或纠纷 account.tab.arbitratorRegistration=仲裁员注册 account.tab.mediatorRegistration=调解员注册 account.tab.refundAgentRegistration=退款助理注册 +account.tab.signing=Signing account.tab.account=账户 account.info.headline=欢迎来到 Bisq 账户 account.info.msg=在这里你可以设置交易账户的法定货币及数字货币,选择仲裁员和备份你的钱包及账户数据。\n\n当你开始运行 Bisq 就已经创建了一个空的比特币钱包。\n\n我们建议你在充值之前写下你比特币钱包的还原密钥(在左边的列表)和考虑添加密码。在“资金”选项中管理比特币存入和提现。\n\n隐私 & 安全:\nBisq 是一个去中心化的交易所 – 意味着您的所有数据都保存在您的电脑上,没有服务器,我们无法访问您的个人信息,您的资金,甚至您的 IP 地址。如银行账号、数字货币、比特币地址等数据只分享给与您交易的人,以实现您发起的交易(如果有争议,仲裁员将会看到您的交易数据)。 @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=买家支付金额 disputeSummaryWindow.payoutAmount.seller=卖家支付金额 disputeSummaryWindow.payoutAmount.invert=使用失败者作为发布者 disputeSummaryWindow.reason=纠纷的原因 -disputeSummaryWindow.reason.bug=Bug -disputeSummaryWindow.reason.usability=可用性 -disputeSummaryWindow.reason.protocolViolation=违反协议 -disputeSummaryWindow.reason.noReply=不回复 -disputeSummaryWindow.reason.scam=诈骗 -disputeSummaryWindow.reason.other=其他 -disputeSummaryWindow.reason.bank=银行 + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Bug +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=可用性 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=违反协议 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=不回复 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=诈骗 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=其他 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=银行 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=总结说明 disputeSummaryWindow.addSummaryNotes=添加总结说明 disputeSummaryWindow.close.button=关闭话题 -disputeSummaryWindow.close.msg=工单已关闭 {0}\n\n摘要:\nBTC 买家的支付金额:{1}\nBTC 卖家的支付金额:{2}\n\n总结说明:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\n下一个步骤:\n打开未完成交易,接受或拒绝建议的调解员的建议 disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\n下一个步骤:\n不需要您采取进一步的行动。如果仲裁员做出了对你有利的裁决,你将在 资金/交易 页中看到“仲裁退款”交易 disputeSummaryWindow.close.closePeer=你也需要关闭交易对象的话题! @@ -1985,7 +2013,8 @@ filterWindow.onions=筛选匿名地址(用逗号“,”隔开) filterWindow.accounts=筛选交易账户数据:\n格式:逗号分割的 [付款方式ID|数据字段|值] filterWindow.bannedCurrencies=筛选货币代码(用逗号“,”隔开) filterWindow.bannedPaymentMethods=筛选支付方式 ID(用逗号“,”隔开) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=筛选后的仲裁人(用逗号“,”隔开的洋葱地址) filterWindow.mediators=筛选后的调解员(用逗号“,”隔开的洋葱地址) filterWindow.refundAgents=筛选后的退款助理(用逗号“,”隔开的洋葱地址) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=你没有足够的 BTC 资金支付 popup.warning.bsqChangeBelowDustException=该交易产生的 BSQ 变化输出低于零头限制(5.46 BSQ),将被比特币网络拒绝。\n\n您需要发送更高的金额以避免更改输出(例如,通过在您的发送金额中添加零头),或者向您的钱包中添加更多的 BSQ 资金,以避免生成零头输出。\n\n零头输出为 {0}。 popup.warning.btcChangeBelowDustException=该交易创建的更改输出低于零头限制(546 聪),将被比特币网络拒绝。\n\n您需要将零头添加到发送量中,以避免生成零头输出。\n\n零头输出为{0}。 -popup.warning.insufficientBsqFundsForBtcFeePayment=您没有足够的 BSQ 资金支付 BSQ 的交易费用。您可以在 BTC 支付费用,或者您需要充值您的 BSQ 钱包。你可以在 Bisq 买到 BSQ 。\n\n缺少 BSQ 资金:{0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=您的 BSQ 钱包没有足够的资金支付 BSQ 的交易费用。 popup.warning.messageTooLong=您的信息超过最大允许的大小。请将其分成多个部分发送,或将其上传到 https://pastebin.com 之类的服务器。 popup.warning.lockedUpFunds=你已经从一个失败的交易中冻结了资金。\n冻结余额:{0}\n存款tx地址:{1}\n交易单号:{2}\n\n请通过选择待处理交易界面中的交易并点击“alt + o”或“option+ o”打开帮助话题。 @@ -2437,6 +2466,7 @@ seed.restore.error=使用还原密钥恢复钱包时出现错误。{0} payment.account=账户 payment.account.no=账户编号 payment.account.name=账户名称 +payment.account.userName=User name payment.account.owner=账户拥有者姓名: payment.account.fullName=全称(名,中间名,姓) payment.account.state=州/省/地区 @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=电子邮箱或者电话号码 payment.venmo.venmoUserName=Venmo 用户名: payment.popmoney.accountId=电子邮箱或者电话号码 -payment.revolut.email=电子邮箱 -payment.revolut.phoneNr=注册的电话号码 payment.promptPay.promptPayId=公民身份证/税号或电话号码 payment.supportedCurrencies=支持的货币 payment.limitations=限制条件 @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=请确认您的银行允许您将现金存款汇入他人账户。例如,美国银行和富国银行不再允许此类存款。 -payment.revolut.info=请确保您用于您的 Revolut 账户的电话号码是注册在 Revolut 上的,否则 BTC 买家无法将资金发送给您。 +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=汇款单是 Bisq 上比较私人的法定货币购买方式之一。\n\n但是,请注意与它们的使用相关的潜在增加的风险。如果汇款单被盗, Bisq 将不承担任何责任,在这种情况下,调解员或仲裁员将把 BTC 判给汇款单的发送方,前提是他们能够提供跟踪信息和收据。寄件人最好在汇款单上写上卖方的名称,以减低汇款单被他人兑现的风险。 +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=联系方式 payment.f2f.contact.prompt=您希望如何与交易伙伴联系?(电子邮箱、电话号码、…) diff --git a/core/src/main/resources/i18n/displayStrings_zh-hant.properties b/core/src/main/resources/i18n/displayStrings_zh-hant.properties index e92e1523941..b7f3b410d8e 100644 --- a/core/src/main/resources/i18n/displayStrings_zh-hant.properties +++ b/core/src/main/resources/i18n/displayStrings_zh-hant.properties @@ -855,8 +855,13 @@ support.tab.mediation.support=調解 support.tab.arbitration.support=仲裁 support.tab.legacyArbitration.support=歷史仲裁 support.tab.ArbitratorsSupportTickets={0} 的工單 -support.filter=篩選列表 +support.filter=Search disputes support.filter.prompt=輸入 交易 ID、日期、洋蔥地址或賬戶資訊 +support.reOpenByTrader.prompt=Are you sure you want to re-open the dispute? +support.reOpenButton.label=Re-open dispute +support.sendNotificationButton.label=傳送私人通知 +support.reportButton.label=Generate report +support.fullReportButton.label=Get text dump of all disputes support.noTickets=沒有建立的話題 support.sendingMessage=傳送訊息... support.receiverNotOnline=收件人未線上。訊息被儲存到他們的郵箱。 @@ -898,7 +903,7 @@ support.youOpenedDisputeForMediation=您建立了一個調解請求。\n\n{0}\n\ support.peerOpenedTicket=對方因技術問題請求獲取幫助。\n\n{0}\n\nBisq 版本:{1} support.peerOpenedDispute=對方建立了一個糾紛請求。\n\n{0}\n\nBisq 版本:{1} support.peerOpenedDisputeForMediation=對方建立了一個調解請求。\n\n{0}\n\nBisq 版本:{1} -support.mediatorsDisputeSummary=系統訊息:\n調解糾紛總結:\n{0} +support.mediatorsDisputeSummary=System message: Mediator''s dispute summary:\n{0} support.mediatorsAddress=仲裁員的節點地址:{0} @@ -976,7 +981,8 @@ settings.net.p2PPeersLabel=已連線節點 settings.net.onionAddressColumn=匿名地址 settings.net.creationDateColumn=已建立連線 settings.net.connectionTypeColumn=入/出 -settings.net.totalTrafficLabel=總流量: +settings.net.sentDataLabel=Sent data statistics +settings.net.receivedDataLabel=Received data statistics settings.net.roundTripTimeColumn=延遲 settings.net.sentBytesColumn=傳送 settings.net.receivedBytesColumn=接收 @@ -989,7 +995,8 @@ settings.net.heightColumn=高度 settings.net.needRestart=您需要重啟應用程式以同意這次變更。\n您需要現在重啟嗎? settings.net.notKnownYet=至今未知... -settings.net.sentReceived=傳送 {0},接收 {1} +settings.net.sentData=Sent data: {0}, {1} messages, {2} messages/sec +settings.net.receivedData=Received data: {0}, {1} messages, {2} messages/sec settings.net.ips=新增逗號分隔的 IP 地址及埠,如使用8333埠可不填寫。 settings.net.seedNode=種子節點 settings.net.directPeer=節點(直連) @@ -1011,8 +1018,8 @@ setting.about.support=支援 Bisq setting.about.def=Bisq 不是一個公司,而是一個社群項目,開放參與。如果您想參與或支援 Bisq,請點選下面連線。 setting.about.contribute=貢獻 setting.about.providers=資料提供商 -setting.about.apisWithFee=Bisq 使用第三方 API 獲取法定貨幣與虛擬幣的市場價以及礦工手續費的估價。 -setting.about.apis=Bisq 使用第三方 API 獲取法定貨幣與虛擬幣的市場價。 +setting.about.apisWithFee=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices, and Bisq Mempool Nodes for mining fee estimation. +setting.about.apis=Bisq uses Bisq Price Indices for Fiat and Altcoin market prices. setting.about.pricesProvided=交易所價格提供商 setting.about.feeEstimation.label=礦工手續費估算提供商 setting.about.versionDetails=版本詳情 @@ -1083,6 +1090,7 @@ setting.about.shortcuts.sendPrivateNotification.value=開啟在頭像或糾紛 account.tab.arbitratorRegistration=仲裁員註冊 account.tab.mediatorRegistration=調解員註冊 account.tab.refundAgentRegistration=退款助理註冊 +account.tab.signing=Signing account.tab.account=賬戶 account.info.headline=歡迎來到 Bisq 賬戶 account.info.msg=在這裡你可以設定交易賬戶的法定貨幣及數字貨幣,選擇仲裁員和備份你的錢包及賬戶資料。\n\n當你開始執行 Bisq 就已經建立了一個空的比特幣錢包。\n\n我們建議你在充值之前寫下你比特幣錢包的還原金鑰(在左邊的列表)和考慮新增密碼。在“資金”選項中管理比特幣存入和提現。\n\n隱私 & 安全:\nBisq 是一個去中心化的交易所 – 意味著您的所有資料都儲存在您的電腦上,沒有伺服器,我們無法訪問您的個人資訊,您的資金,甚至您的 IP 地址。如銀行賬號、數字貨幣、比特幣地址等資料只分享給與您交易的人,以實現您發起的交易(如果有爭議,仲裁員將會看到您的交易資料)。 @@ -1947,17 +1955,37 @@ disputeSummaryWindow.payoutAmount.buyer=買家支付金額 disputeSummaryWindow.payoutAmount.seller=賣家支付金額 disputeSummaryWindow.payoutAmount.invert=使用失敗者作為釋出者 disputeSummaryWindow.reason=糾紛的原因 -disputeSummaryWindow.reason.bug=Bug -disputeSummaryWindow.reason.usability=可用性 -disputeSummaryWindow.reason.protocolViolation=違反協議 -disputeSummaryWindow.reason.noReply=不回覆 -disputeSummaryWindow.reason.scam=詐騙 -disputeSummaryWindow.reason.other=其他 -disputeSummaryWindow.reason.bank=銀行 + +# dynamic values are not recognized by IntelliJ +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BUG=Bug +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.USABILITY=可用性 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PROTOCOL_VIOLATION=違反協議 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.NO_REPLY=不回覆 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SCAM=詐騙 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OTHER=其他 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.BANK_PROBLEMS=銀行 +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.OPTION_TRADE=Option trade +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.SELLER_NOT_RESPONDING=Seller not responding +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.WRONG_SENDER_ACCOUNT=Wrong sender account +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.PEER_WAS_LATE=Peer was late +# suppress inspection "UnusedProperty" +disputeSummaryWindow.reason.TRADE_ALREADY_SETTLED=Trade already settled + disputeSummaryWindow.summaryNotes=總結說明 disputeSummaryWindow.addSummaryNotes=新增總結說明 disputeSummaryWindow.close.button=關閉話題 -disputeSummaryWindow.close.msg=工單已關閉 {0}\n\n摘要:\nBTC 買家的支付金額:{1}\nBTC 賣家的支付金額:{2}\n\n總結說明:\n{3} +disputeSummaryWindow.close.msg=Ticket closed on {0}\n\nSummary:\nPayout amount for BTC buyer: {1}\nPayout amount for BTC seller: {2}\n\nReason for dispute: {3}\n\nSummary notes:\n{4} disputeSummaryWindow.close.nextStepsForMediation=\n\n下一個步驟:\n開啟未完成交易,接受或拒絕建議的調解員的建議 disputeSummaryWindow.close.nextStepsForRefundAgentArbitration=\n\n下一個步驟:\n不需要您採取進一步的行動。如果仲裁員做出了對你有利的裁決,你將在 資金/交易 頁中看到“仲裁退款”交易 disputeSummaryWindow.close.closePeer=你也需要關閉交易物件的話題! @@ -1985,7 +2013,8 @@ filterWindow.onions=篩選匿名地址(用逗號“,”隔開) filterWindow.accounts=篩選交易賬戶資料:\n格式:逗號分割的 [付款方式ID|資料欄位|值] filterWindow.bannedCurrencies=篩選貨幣程式碼(用逗號“,”隔開) filterWindow.bannedPaymentMethods=篩選支付方式 ID(用逗號“,”隔開) -filterWindow.bannedSignerPubKeys=Filtered signer pubkeys (comma sep. hex of pubkeys) +filterWindow.bannedAccountWitnessSignerPubKeys=Filtered account witness signer pub keys (comma sep. hex of pub keys) +filterWindow.bannedPrivilegedDevPubKeys=Filtered privileged dev pub keys (comma sep. hex of pub keys) filterWindow.arbitrators=篩選後的仲裁人(用逗號“,”隔開的洋蔥地址) filterWindow.mediators=篩選後的調解員(用逗號“,”隔開的洋蔥地址) filterWindow.refundAgents=篩選後的退款助理(用逗號“,”隔開的洋蔥地址) @@ -2140,7 +2169,7 @@ popup.warning.insufficientBtcFundsForBsqTx=你沒有足夠的 BTC 資金支付 popup.warning.bsqChangeBelowDustException=該交易產生的 BSQ 變化輸出低於零頭限制(5.46 BSQ),將被比特幣網路拒絕。\n\n您需要傳送更高的金額以避免更改輸出(例如,通過在您的傳送金額中新增零頭),或者向您的錢包中新增更多的 BSQ 資金,以避免生成零頭輸出。\n\n零頭輸出為 {0}。 popup.warning.btcChangeBelowDustException=該交易建立的更改輸出低於零頭限制(546 聰),將被比特幣網路拒絕。\n\n您需要將零頭新增到傳送量中,以避免生成零頭輸出。\n\n零頭輸出為{0}。 -popup.warning.insufficientBsqFundsForBtcFeePayment=您沒有足夠的 BSQ 資金支付 BSQ 的交易費用。您可以在 BTC 支付費用,或者您需要充值您的 BSQ 錢包。你可以在 Bisq 買到 BSQ 。\n\n缺少 BSQ 資金:{0} +popup.warning.insufficientBsqFundsForBtcFeePayment=You''ll need more BSQ to do this transaction—the last 5.46 BSQ in your wallet cannot be used to pay trade fees because of dust limits in the Bitcoin protocol.\n\nYou can either buy more BSQ or pay trade fees with BTC.\n\nMissing funds: {0} popup.warning.noBsqFundsForBtcFeePayment=您的 BSQ 錢包沒有足夠的資金支付 BSQ 的交易費用。 popup.warning.messageTooLong=您的資訊超過最大允許的大小。請將其分成多個部分發送,或將其上傳到 https://pastebin.com 之類的伺服器。 popup.warning.lockedUpFunds=你已經從一個失敗的交易中凍結了資金。\n凍結餘額:{0}\n存款tx地址:{1}\n交易單號:{2}\n\n請通過選擇待處理交易介面中的交易並點選“alt + o”或“option+ o”開啟幫助話題。 @@ -2437,6 +2466,7 @@ seed.restore.error=使用還原金鑰恢復錢包時出現錯誤。{0} payment.account=賬戶 payment.account.no=賬戶編號 payment.account.name=賬戶名稱 +payment.account.userName=User name payment.account.owner=賬戶擁有者姓名: payment.account.fullName=全稱(名,中間名,姓) payment.account.state=州/省/地區 @@ -2468,8 +2498,6 @@ payment.cashApp.cashTag=$Cashtag payment.moneyBeam.accountId=電子郵箱或者電話號碼 payment.venmo.venmoUserName=Venmo 使用者名稱: payment.popmoney.accountId=電子郵箱或者電話號碼 -payment.revolut.email=電子郵箱 -payment.revolut.phoneNr=註冊的電話號碼 payment.promptPay.promptPayId=公民身份證/稅號或電話號碼 payment.supportedCurrencies=支援的貨幣 payment.limitations=限制條件 @@ -2518,9 +2546,11 @@ payment.limits.info.withSigning=To limit chargeback risk, Bisq sets per-trade li payment.cashDeposit.info=請確認您的銀行允許您將現金存款匯入他人賬戶。例如,美國銀行和富國銀行不再允許此類存款。 -payment.revolut.info=請確保您用於您的 Revolut 賬戶的電話號碼是註冊在 Revolut 上的,否則 BTC 買家無法將資金髮送給您。 +payment.revolut.info=Revolut requires the 'User name' as account ID not the phone number or email as it was the case in the past. +payment.account.revolut.addUserNameInfo={0}\nYour existing Revolut account ({1}) does not has set the ''User name''.\nPlease enter your Revolut ''User name'' to update your account data.\nThis will not affect your account age signing status. +payment.revolut.addUserNameInfo.headLine=Update Revolut account -payment.usPostalMoneyOrder.info=匯款單是 Bisq 上比較私人的法定貨幣購買方式之一。\n\n但是,請注意與它們的使用相關的潛在增加的風險。如果匯款單被盜, Bisq 將不承擔任何責任,在這種情況下,調解員或仲裁員將把 BTC 判給匯款單的傳送方,前提是他們能夠提供跟蹤資訊和收據。寄件人最好在匯款單上寫上賣方的名稱,以減低匯款單被他人兌現的風險。 +payment.usPostalMoneyOrder.info=Trading using US Postal Money Orders (USPMO) on Bisq requires that you understand the following:\n\n- BTC buyers must write the BTC Seller’s name in both the Payer and the Payee’s fields & take a high-resolution photo of the USPMO and envelope with proof of tracking before sending.\n- BTC buyers must send the USPMO to the BTC seller with Delivery Confirmation.\n\nIn the event mediation is necessary, or if there is a trade dispute, you will be required to send the photos to the Bisq mediator or refund agent, together with the USPMO Serial Number, Post Office Number, and dollar amount, so they can verify the details on the US Post Office website.\n\nFailure to provide the required information to the Mediator or Arbitrator will result in losing the dispute case.\n\nIn all dispute cases, the USPMO sender bears 100% of the burden of responsibility in providing evidence/proof to the Mediator or Arbitrator.\n\nIf you do not understand these requirements, do not trade using USPMO on Bisq. payment.f2f.contact=聯絡方式 payment.f2f.contact.prompt=您希望如何與交易夥伴聯絡?(電子郵箱、電話號碼、…) From ab2ac1712a4bbfc62fe0c8e95b25e57b7d55688f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 10:54:43 -0500 Subject: [PATCH 003/108] Add dontShowAgain button in osxKeyLoggerWarning window --- .../src/main/java/bisq/desktop/main/MainViewModel.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index 6f468f70121..5504947bce3 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -389,9 +389,13 @@ private void setupHandlers() { showRevolutAccountUpdateWindow(new ArrayList<>(revolutAccountList)); }); bisqSetup.setOsxKeyLoggerWarningHandler(() -> { - new Popup().warning(Res.get("popup.warning.osxKeyLoggerWarning")) - .closeButtonText(Res.get("shared.iUnderstand")) - .show(); + String key = "osxKeyLoggerWarning"; + if (preferences.showAgain(key)) { + new Popup().warning(Res.get("popup.warning.osxKeyLoggerWarning")) + .closeButtonText(Res.get("shared.iUnderstand")) + .dontShowAgainId(key) + .show(); + } }); corruptedDatabaseFilesHandler.getCorruptedDatabaseFiles().ifPresent(files -> new Popup() From 688bef885a792722470102d9fa4389ffcf1f247b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 12:20:22 -0500 Subject: [PATCH 004/108] Rmove short cut info for removed key handlers --- core/src/main/resources/i18n/displayStrings.properties | 8 +------- .../bisq/desktop/main/settings/about/AboutView.java | 10 +--------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 1474fb0628e..00b27cb8515 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1245,9 +1245,6 @@ setting.about.shortcuts.openEmergencyBsqWalletTool=Open emergency wallet tool fo setting.about.shortcuts.showTorLogs=Toggle log level for Tor messages between DEBUG and WARN -setting.about.shortcuts.showDisputeStatistics=Show summary of all disputes -setting.about.shortcuts.showDisputeStatistics.value=Navigate to disputes view and press: {0} - setting.about.shortcuts.manualPayoutTxWindow=Open window for manual payout from 2of2 Multisig deposit tx setting.about.shortcuts.reRepublishAllGovernanceData=Republish DAO governance data (proposals, votes) @@ -1261,9 +1258,6 @@ setting.about.shortcuts.registerArbitrator.value=Navigate to account and press: setting.about.shortcuts.registerMediator=Register mediator (mediator/arbitrator only) setting.about.shortcuts.registerMediator.value=Navigate to account and press: {0} -setting.about.shortcuts.reOpenDispute=Re-open already closed dispute (mediator/arbitrator only) -setting.about.shortcuts.reOpenDispute.value=Select closed dispute and press: {0} - setting.about.shortcuts.openSignPaymentAccountsWindow=Open window for account age signing (legacy arbitrators only) setting.about.shortcuts.openSignPaymentAccountsWindow.value=Navigate to legacy arbitrator view and press: {0} @@ -1272,7 +1266,7 @@ setting.about.shortcuts.sendAlertMsg=Send alert or update message (privileged ac setting.about.shortcuts.sendFilter=Set Filter (privileged activity) setting.about.shortcuts.sendPrivateNotification=Send private notification to peer (privileged activity) -setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar or dispute and press: {0} +setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar and press: {0} setting.info.headline=New XMR auto-confirm Feature setting.info.msg=When selling BTC for XMR you can use the auto-confirm feature to verify that the correct amount of \ diff --git a/desktop/src/main/java/bisq/desktop/main/settings/about/AboutView.java b/desktop/src/main/java/bisq/desktop/main/settings/about/AboutView.java index 8b0113fd379..5dcedaf1528 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/about/AboutView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/about/AboutView.java @@ -93,7 +93,7 @@ public void initialize() { Version.LOCAL_DB_VERSION, Version.TRADE_PROTOCOL_VERSION)); - addTitledGroupBg(root, ++gridRow, 20, Res.get("setting.about.shortcuts"), Layout.GROUP_DISTANCE); + addTitledGroupBg(root, ++gridRow, 18, Res.get("setting.about.shortcuts"), Layout.GROUP_DISTANCE); // basics addCompactTopLabelTextField(root, gridRow, Res.get("setting.about.shortcuts.menuNav"), @@ -122,10 +122,6 @@ public void initialize() { addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.shortcuts.openEmergencyBsqWalletTool"), Res.get("setting.about.shortcuts.ctrlOrAltOrCmd", "b")); - addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.shortcuts.showDisputeStatistics"), - Res.get("setting.about.shortcuts.showDisputeStatistics.value", - Res.get("setting.about.shortcuts.ctrlOrAltOrCmd", "l"))); - addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.shortcuts.showTorLogs"), Res.get("setting.about.shortcuts.ctrlOrAltOrCmd", "t")); @@ -149,10 +145,6 @@ public void initialize() { Res.get("setting.about.shortcuts.registerMediator.value", Res.get("setting.about.shortcuts.ctrlOrAltOrCmd", "d"))); - addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.shortcuts.reOpenDispute"), - Res.get("setting.about.shortcuts.reOpenDispute.value", - Res.get("setting.about.shortcuts.ctrlOrAltOrCmd", "u"))); - addCompactTopLabelTextField(root, ++gridRow, Res.get("setting.about.shortcuts.openSignPaymentAccountsWindow"), Res.get("setting.about.shortcuts.openSignPaymentAccountsWindow.value", Res.get("setting.about.shortcuts.ctrlOrAltOrCmd", "s"))); From 2da08a56209f82e3c43695c528063657f7d16c6e Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 14:36:28 -0500 Subject: [PATCH 005/108] Refactor dependency structure to enable adding dispute managers If we would add DisputeManager to previous structure it would cause a circular dependency error from guice. We change dependency structure so that TradeManager does not know XmrTxProofService but XmrTxProofService gets an instance of TradeManager. It makes code cleaner in total as well as responsibility is better defined. Next commit will contain the DisputeManager addition. --- .../java/bisq/core/app/BisqExecutable.java | 2 + .../main/java/bisq/core/app/BisqSetup.java | 5 + .../java/bisq/core/trade/TradeManager.java | 44 +------ ...CounterCurrencyTransferStartedMessage.java | 6 - .../trade/txproof/AssetTxProofService.java | 12 +- .../trade/txproof/xmr/XmrTxProofService.java | 110 +++++++++++------- .../pendingtrades/PendingTradesDataModel.java | 2 +- 7 files changed, 83 insertions(+), 98 deletions(-) diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 2cb4760f386..2fe5be9ee0c 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -26,6 +26,7 @@ import bisq.core.setup.CoreSetup; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.trade.TradeManager; +import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.network.p2p.P2PService; @@ -221,6 +222,7 @@ public void gracefulShutDown(ResultHandler resultHandler) { try { injector.getInstance(ArbitratorManager.class).shutDown(); injector.getInstance(TradeManager.class).shutDown(); + injector.getInstance(XmrTxProofService.class).shutDown(); injector.getInstance(DaoSetup.class).shutDown(); injector.getInstance(AvoidStandbyModeService.class).shutDown(); injector.getInstance(OpenOfferManager.class).shutDown(() -> { diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 9440a98559b..2bafa44b858 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -59,6 +59,7 @@ import bisq.core.trade.TradeManager; import bisq.core.trade.TradeTxException; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.FormattingUtils; @@ -167,6 +168,7 @@ default void onRequestWalletPassword() { private final PrivateNotificationManager privateNotificationManager; private final FilterManager filterManager; private final TradeStatisticsManager tradeStatisticsManager; + private final XmrTxProofService xmrTxProofService; private final ClockWatcher clockWatcher; private final FeeService feeService; private final DaoSetup daoSetup; @@ -263,6 +265,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, PrivateNotificationManager privateNotificationManager, FilterManager filterManager, TradeStatisticsManager tradeStatisticsManager, + XmrTxProofService xmrTxProofService, ClockWatcher clockWatcher, FeeService feeService, DaoSetup daoSetup, @@ -308,6 +311,7 @@ public BisqSetup(P2PNetworkSetup p2PNetworkSetup, this.privateNotificationManager = privateNotificationManager; this.filterManager = filterManager; this.tradeStatisticsManager = tradeStatisticsManager; + this.xmrTxProofService = xmrTxProofService; this.clockWatcher = clockWatcher; this.feeService = feeService; this.daoSetup = daoSetup; @@ -686,6 +690,7 @@ private void initDomainServices() { traderChatManager.onAllServicesInitialized(); tradeManager.onAllServicesInitialized(); + xmrTxProofService.onAllServicesInitialized(); if (walletsSetup.downloadPercentageProperty().get() == 1) { checkForLockedUpFunds(); diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 8d3c2d738ca..66962f10d68 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -46,8 +46,6 @@ import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.core.trade.txproof.AssetTxProofResult; -import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.user.User; import bisq.core.util.Validator; @@ -59,7 +57,6 @@ import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.ClockWatcher; -import bisq.common.UserThread; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import bisq.common.handlers.ErrorMessageHandler; @@ -111,8 +108,6 @@ import javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkArgument; - public class TradeManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(TradeManager.class); @@ -132,7 +127,6 @@ public class TradeManager implements PersistedDataHost { private final ReferralIdService referralIdService; private final AccountAgeWitnessService accountAgeWitnessService; @Getter - private final XmrTxProofService xmrTxProofService; private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; @@ -174,7 +168,6 @@ public TradeManager(User user, TradeStatisticsManager tradeStatisticsManager, ReferralIdService referralIdService, AccountAgeWitnessService accountAgeWitnessService, - XmrTxProofService xmrTxProofService, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, @@ -197,7 +190,6 @@ public TradeManager(User user, this.tradeStatisticsManager = tradeStatisticsManager; this.referralIdService = referralIdService; this.accountAgeWitnessService = accountAgeWitnessService; - this.xmrTxProofService = xmrTxProofService; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; @@ -286,7 +278,7 @@ public void onUpdatedDataReceived() { } public void shutDown() { - xmrTxProofService.shutDown(); + // Do nothing here } private void initPendingTrades() { @@ -329,13 +321,6 @@ private void initPendingTrades() { addTradeToFailedTradesList.add(trade); } } - - if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { - // This state can be only appear at a SellerTrade - checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); - // We delay a bit as at startup lots of stuff is happening - UserThread.runAfter(() -> maybeStartXmrTxProofServices((SellerTrade) trade), 1); - } } ); @@ -360,31 +345,10 @@ private void initPendingTrades() { pendingTradesInitialized.set(true); } - public void maybeStartXmrTxProofServices(SellerTrade sellerTrade) { - xmrTxProofService.maybeStartRequests(sellerTrade, tradableList.getList(), - assetTxProofResult -> { - if (assetTxProofResult == AssetTxProofResult.COMPLETED) { - log.info("###########################################################################################"); - log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", sellerTrade.getShortId()); - log.info("###########################################################################################"); - autoConfirmFiatPaymentReceived(sellerTrade); - } - }, - (errorMessage, throwable) -> { - log.error(errorMessage); - }); - } - - private void autoConfirmFiatPaymentReceived(SellerTrade sellerTrade) { - onFiatPaymentReceived(sellerTrade, - () -> { - }, errorMessage -> { - }); - } - public void onFiatPaymentReceived(SellerTrade sellerTrade, - ResultHandler resultHandler, - ErrorMessageHandler errorMessageHandler) { + public void onUserConfirmedFiatPaymentReceived(SellerTrade sellerTrade, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { sellerTrade.onFiatPaymentReceived(resultHandler, errorMessageHandler); //TODO move to trade protocol task diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 29ca5bc63ea..ee996ffd9c1 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -17,7 +17,6 @@ package bisq.core.trade.protocol.tasks.seller; -import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.TradeTask; @@ -27,7 +26,6 @@ import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -62,10 +60,6 @@ protected void run() { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); } - checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); - // We return early in the service if its not XMR. We prefer to not have additional checks outside... - processModel.getTradeManager().maybeStartXmrTxProofServices((SellerTrade) trade); - processModel.removeMailboxMessageAfterProcessing(trade); trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG); diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java index 56e2a7462e8..c5c8476a41b 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java @@ -17,18 +17,8 @@ package bisq.core.trade.txproof; -import bisq.core.trade.Trade; - -import bisq.common.handlers.FaultHandler; - -import java.util.List; -import java.util.function.Consumer; - public interface AssetTxProofService { - void maybeStartRequests(Trade trade, - List activeTrades, - Consumer resultHandler, - FaultHandler faultHandler); + void onAllServicesInitialized(); void shutDown(); } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index bd63e1df114..5590c56c5ef 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -23,6 +23,7 @@ import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.txproof.AssetTxProofHttpClient; @@ -34,16 +35,16 @@ import bisq.network.p2p.P2PService; import bisq.common.app.DevEnv; -import bisq.common.handlers.FaultHandler; import javax.inject.Inject; import javax.inject.Singleton; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; @@ -59,12 +60,14 @@ public class XmrTxProofService implements AssetTxProofService { private final FilterManager filterManager; private final Preferences preferences; + private final TradeManager tradeManager; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; private final AssetTxProofHttpClient httpClient; private final Map servicesByTradeId = new HashMap<>(); + private AutoConfirmSettings autoConfirmSettings; /////////////////////////////////////////////////////////////////////////////////////////// @@ -75,6 +78,7 @@ public class XmrTxProofService implements AssetTxProofService { @Inject public XmrTxProofService(FilterManager filterManager, Preferences preferences, + TradeManager tradeManager, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, P2PService p2PService, @@ -82,11 +86,25 @@ public XmrTxProofService(FilterManager filterManager, AssetTxProofHttpClient httpClient) { this.filterManager = filterManager; this.preferences = preferences; + this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; this.p2PService = p2PService; this.walletsSetup = walletsSetup; this.httpClient = httpClient; + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void onAllServicesInitialized() { + if (!preferences.findAutoConfirmSettings("XMR").isPresent()) { + log.error("AutoConfirmSettings is not present"); + } + autoConfirmSettings = preferences.findAutoConfirmSettings("XMR").get(); filterManager.filterProperty().addListener((observable, oldValue, newValue) -> { if (isAutoConfDisabledByFilter()) { @@ -96,22 +114,39 @@ public XmrTxProofService(FilterManager filterManager, shutDown(); } }); + + ObservableList tradableList = tradeManager.getTradableList(); + tradableList.addListener((ListChangeListener) c -> { + c.next(); + if (c.wasAdded()) { + processTrades(c.getAddedSubList()); + } + }); + processTrades(tradableList); + } + + @Override + public void shutDown() { + servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); + servicesByTradeId.clear(); } /////////////////////////////////////////////////////////////////////////////////////////// - // API + // Private /////////////////////////////////////////////////////////////////////////////////////////// - @Override - public void maybeStartRequests(Trade trade, - List activeTrades, - Consumer resultHandler, - FaultHandler faultHandler) { - if (!isXmrBuyer(trade)) { - return; - } + private void processTrades(List trades) { + trades.stream() + .filter(trade -> trade instanceof SellerTrade) + .map(trade -> (SellerTrade) trade) + .filter(this::isExpectedTradeState) + .filter(this::isXmrBuyer) + .filter(trade -> networkAndWalletReady()) + .forEach(this::processTrade); + } + private void processTrade(SellerTrade trade) { String txId = trade.getCounterCurrencyTxId(); String txHash = trade.getCounterCurrencyExtraData(); if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { @@ -119,25 +154,13 @@ public void maybeStartRequests(Trade trade, return; } - if (!networkAndWalletReady()) { - return; - } - - Optional optionalAutoConfirmSettings = preferences.findAutoConfirmSettings("XMR"); - if (!optionalAutoConfirmSettings.isPresent()) { - // Not expected - log.error("autoConfirmSettings is not present"); - return; - } - AutoConfirmSettings autoConfirmSettings = optionalAutoConfirmSettings.get(); - if (isAutoConfDisabledByFilter()) { trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature"))); return; } - if (wasTxKeyReUsed(trade, activeTrades)) { + if (wasTxKeyReUsed(trade, tradeManager.getTradableList())) { trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA .details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused"))); return; @@ -151,19 +174,22 @@ public void maybeStartRequests(Trade trade, assetTxProofResult -> { trade.setAssetTxProofResult(assetTxProofResult); + if (assetTxProofResult == AssetTxProofResult.COMPLETED) { + log.info("###########################################################################################"); + log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", trade.getShortId()); + log.info("###########################################################################################"); + trade.onFiatPaymentReceived(() -> { + }, errorMessage -> { + }); + } + if (assetTxProofResult.isTerminal()) { servicesByTradeId.remove(trade.getId()); } - - resultHandler.accept(assetTxProofResult); }, - faultHandler); - } - - @Override - public void shutDown() { - servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); - servicesByTradeId.clear(); + (errorMessage, throwable) -> { + log.error(errorMessage); + }); } @@ -171,6 +197,10 @@ public void shutDown() { // Validation /////////////////////////////////////////////////////////////////////////////////////////// + private boolean isExpectedTradeState(SellerTrade sellerTrade) { + return sellerTrade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG; + } + private boolean isXmrBuyer(Trade trade) { if (!checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) { return false; @@ -183,6 +213,12 @@ private boolean isXmrBuyer(Trade trade) { return checkNotNull(trade.getContract()).getSellerPaymentAccountPayload() instanceof AssetsAccountPayload; } + private boolean networkAndWalletReady() { + return p2PService.isBootstrapped() && + walletsSetup.isDownloadComplete() && + walletsSetup.hasSufficientPeersForBroadcast(); + } + private boolean is32BitHexStringInValid(String hexString) { if (hexString == null || hexString.isEmpty() || !hexString.matches("[a-fA-F0-9]{64}")) { log.warn("Invalid hexString: {}", hexString); @@ -192,12 +228,6 @@ private boolean is32BitHexStringInValid(String hexString) { return false; } - private boolean networkAndWalletReady() { - return p2PService.isBootstrapped() && - walletsSetup.isDownloadComplete() && - walletsSetup.hasSufficientPeersForBroadcast(); - } - private boolean isAutoConfDisabledByFilter() { return filterManager.getFilter() != null && filterManager.getFilter().isDisableAutoConf(); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index 19e2ec7e1ad..fb03b413f8a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -194,7 +194,7 @@ public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { checkNotNull(getTrade(), "trade must not be null"); checkArgument(getTrade() instanceof SellerTrade, "Trade must be instance of SellerTrade"); - tradeManager.onFiatPaymentReceived((SellerTrade) getTrade(), resultHandler, errorMessageHandler); + tradeManager.onUserConfirmedFiatPaymentReceived((SellerTrade) getTrade(), resultHandler, errorMessageHandler); } public void onWithdrawRequest(String toAddress, From 05296af47e503e30541874ed5a365c8cafb503f7 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 15:38:47 -0500 Subject: [PATCH 006/108] Add check if dispute is open If a mediation or arbitration dispute has been opened we do not use the auto-confirm feature. --- .../trade/txproof/AssetTxProofResult.java | 1 + .../xmr/XmrTxProofRequestsPerTrade.java | 119 +++++++++++++++--- .../trade/txproof/xmr/XmrTxProofService.java | 88 +++++++++---- .../resources/i18n/displayStrings.properties | 2 + .../main/java/bisq/desktop/util/GUIUtil.java | 1 + 5 files changed, 168 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java index 0fc61d7cfd0..0f6ba959709 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java @@ -26,6 +26,7 @@ public enum AssetTxProofResult { TRADE_LIMIT_EXCEEDED, INVALID_DATA, // Peer provided invalid data. Might be a scam attempt (e.g. txKey reused) PAYOUT_TX_ALREADY_PUBLISHED, + DISPUTE_OPENED, REQUESTS_STARTED(false), PENDING(false), diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index f49919232b5..774423b3292 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -18,6 +18,9 @@ package bisq.core.trade.txproof.xmr; import bisq.core.locale.Res; +import bisq.core.support.dispute.Dispute; +import bisq.core.support.dispute.mediation.MediationManager; +import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.Trade; import bisq.core.trade.txproof.AssetTxProofHttpClient; import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade; @@ -30,6 +33,9 @@ import javafx.beans.value.ChangeListener; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -46,6 +52,8 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { @Getter private final Trade trade; private final AutoConfirmSettings autoConfirmSettings; + private final MediationManager mediationManager; + private final RefundManager refundManager; private final AssetTxProofHttpClient httpClient; private int numRequiredSuccessResults; @@ -54,6 +62,7 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { private int numSuccessResults; private ChangeListener tradeStateListener; private AutoConfirmSettings.Listener autoConfirmSettingsListener; + private ListChangeListener mediationListener, refundListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -62,10 +71,14 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { XmrTxProofRequestsPerTrade(AssetTxProofHttpClient httpClient, Trade trade, - AutoConfirmSettings autoConfirmSettings) { + AutoConfirmSettings autoConfirmSettings, + MediationManager mediationManager, + RefundManager refundManager) { this.httpClient = httpClient; this.trade = trade; this.autoConfirmSettings = autoConfirmSettings; + this.mediationManager = mediationManager; + this.refundManager = refundManager; } @@ -75,44 +88,56 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { @Override public void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler) { - // We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started - // it will have no impact on serviceAddresses and numRequiredSuccessResults. - // Thought numRequiredConfirmations can be changed during request process and will be read from - // autoConfirmSettings at result parsing. - List serviceAddresses = autoConfirmSettings.getServiceAddresses(); - numRequiredSuccessResults = serviceAddresses.size(); - + // isTradeAmountAboveLimit if (isTradeAmountAboveLimit(trade)) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED); return; } + // isPayoutPublished if (trade.isPayoutPublished()) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); return; } + // IsEnabled() // We will stop all our services if the user changes the enable state in the AutoConfirmSettings - autoConfirmSettingsListener = () -> { - if (!autoConfirmSettings.isEnabled()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); - } - }; - autoConfirmSettings.addListener(autoConfirmSettingsListener); if (!autoConfirmSettings.isEnabled()) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); return; } + addSettingsListener(resultHandler); - tradeStateListener = (observable, oldValue, newValue) -> { - if (trade.isPayoutPublished()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); - } - }; - trade.stateProperty().addListener(tradeStateListener); + // TradeState + setupTradeStateListener(resultHandler); + // We checked initially for current trade state so no need to check again here + + // Check if mediation dispute and add listener + ObservableList mediationDisputes = mediationManager.getDisputesAsObservableList(); + if (isDisputed(mediationDisputes)) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); + return; + } + setupMediationListener(resultHandler, mediationDisputes); + + // Check if arbitration dispute and add listener + ObservableList refundDisputes = refundManager.getDisputesAsObservableList(); + if (isDisputed(refundDisputes)) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); + return; + } + setupArbitrationListener(resultHandler, refundDisputes); + // All good so we start callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED); + // We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started + // it will have no impact on serviceAddresses and numRequiredSuccessResults. + // Thought numRequiredConfirmations can be changed during request process and will be read from + // autoConfirmSettings at result parsing. + List serviceAddresses = autoConfirmSettings.getServiceAddresses(); + numRequiredSuccessResults = serviceAddresses.size(); + for (String serviceAddress : serviceAddresses) { XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings); XmrTxProofRequest request = new XmrTxProofRequest(httpClient, model); @@ -174,16 +199,66 @@ public void requestFromAllServices(Consumer resultHandler, F } } + protected void addSettingsListener(Consumer resultHandler) { + autoConfirmSettingsListener = () -> { + if (!autoConfirmSettings.isEnabled()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); + } + }; + autoConfirmSettings.addListener(autoConfirmSettingsListener); + } + + protected void setupTradeStateListener(Consumer resultHandler) { + tradeStateListener = (observable, oldValue, newValue) -> { + if (trade.isPayoutPublished()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); + } + }; + trade.stateProperty().addListener(tradeStateListener); + } + + protected void setupArbitrationListener(Consumer resultHandler, + ObservableList refundDisputes) { + refundListener = c -> { + c.next(); + if (c.wasAdded() && isDisputed(c.getAddedSubList())) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); + } + }; + refundDisputes.addListener(refundListener); + } + + protected void setupMediationListener(Consumer resultHandler, + ObservableList mediationDisputes) { + mediationListener = c -> { + c.next(); + if (c.wasAdded() && isDisputed(c.getAddedSubList())) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); + } + }; + mediationDisputes.addListener(mediationListener); + } + @Override public void terminate() { requests.forEach(XmrTxProofRequest::terminate); requests.clear(); + if (tradeStateListener != null) { trade.stateProperty().removeListener(tradeStateListener); } + if (autoConfirmSettingsListener != null) { autoConfirmSettings.removeListener(autoConfirmSettingsListener); } + + if (mediationListener != null) { + mediationManager.getDisputesAsObservableList().removeListener(mediationListener); + } + + if (refundListener != null) { + refundManager.getDisputesAsObservableList().removeListener(refundListener); + } } @@ -235,4 +310,8 @@ private boolean isTradeAmountAboveLimit(Trade trade) { } return false; } + + private boolean isDisputed(List disputes) { + return disputes.stream().anyMatch(e -> e.getTradeId().equals(trade.getId())); + } } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 5590c56c5ef..86920ebf59e 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -20,7 +20,8 @@ import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; import bisq.core.locale.Res; -import bisq.core.payment.payload.AssetsAccountPayload; +import bisq.core.support.dispute.mediation.MediationManager; +import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; @@ -34,11 +35,14 @@ import bisq.network.p2p.P2PService; +import bisq.common.UserThread; import bisq.common.app.DevEnv; import javax.inject.Inject; import javax.inject.Singleton; +import javafx.beans.value.ChangeListener; + import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -63,11 +67,14 @@ public class XmrTxProofService implements AssetTxProofService { private final TradeManager tradeManager; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; + private final MediationManager mediationManager; + private final RefundManager refundManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; private final AssetTxProofHttpClient httpClient; private final Map servicesByTradeId = new HashMap<>(); private AutoConfirmSettings autoConfirmSettings; + private Map> tradeStateListenerMap = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -81,6 +88,8 @@ public XmrTxProofService(FilterManager filterManager, TradeManager tradeManager, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, + MediationManager mediationManager, + RefundManager refundManager, P2PService p2PService, WalletsSetup walletsSetup, AssetTxProofHttpClient httpClient) { @@ -89,6 +98,8 @@ public XmrTxProofService(FilterManager filterManager, this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; + this.mediationManager = mediationManager; + this.refundManager = refundManager; this.p2PService = p2PService; this.walletsSetup = walletsSetup; this.httpClient = httpClient; @@ -122,7 +133,9 @@ public void onAllServicesInitialized() { processTrades(c.getAddedSubList()); } }); - processTrades(tradableList); + // Network is usually not ready at onAllServicesInitialized + //TODO we need to add listeners + UserThread.runAfter(() -> processTrades(tradableList), 1); } @Override @@ -140,13 +153,49 @@ private void processTrades(List trades) { trades.stream() .filter(trade -> trade instanceof SellerTrade) .map(trade -> (SellerTrade) trade) - .filter(this::isExpectedTradeState) + .filter(this::isXmrTrade) + .forEach(this::processCandidate); + + /* trades.stream() + .filter(trade -> trade instanceof SellerTrade) + .map(trade -> (SellerTrade) trade) .filter(this::isXmrBuyer) + .filter(this::isExpectedTradeState) .filter(trade -> networkAndWalletReady()) .forEach(this::processTrade); + */ + } + + // Basic requirements are fulfilled. + // We might register a state listener to process further if expected state appears + private void processCandidate(SellerTrade trade) { + if (trade.isFiatReceived()) { + // We are done already. + return; + } + + if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { + processTrade(trade); + } else { + // We are expecting SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG in the future, so listen to changes + ChangeListener tradeStateListener = (observable, oldValue, newValue) -> { + if (newValue == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { + processTrade(trade); + } + }; + tradeStateListenerMap.put(trade.getId(), tradeStateListener); + trade.stateProperty().addListener(tradeStateListener); + } } private void processTrade(SellerTrade trade) { + tradeStateListenerMap.remove(trade.getId()); + + if (!networkAndWalletReady()) { + //TODO handle listeners + return; + } + String txId = trade.getCounterCurrencyTxId(); String txHash = trade.getCounterCurrencyExtraData(); if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { @@ -168,7 +217,9 @@ private void processTrade(SellerTrade trade) { XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(httpClient, trade, - autoConfirmSettings); + autoConfirmSettings, + mediationManager, + refundManager); servicesByTradeId.put(trade.getId(), service); service.requestFromAllServices( assetTxProofResult -> { @@ -178,9 +229,10 @@ private void processTrade(SellerTrade trade) { log.info("###########################################################################################"); log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", trade.getShortId()); log.info("###########################################################################################"); - trade.onFiatPaymentReceived(() -> { + + /* trade.onFiatPaymentReceived(() -> { }, errorMessage -> { - }); + });*/ } if (assetTxProofResult.isTerminal()) { @@ -197,26 +249,16 @@ private void processTrade(SellerTrade trade) { // Validation /////////////////////////////////////////////////////////////////////////////////////////// - private boolean isExpectedTradeState(SellerTrade sellerTrade) { - return sellerTrade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG; - } - - private boolean isXmrBuyer(Trade trade) { - if (!checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) { - return false; - } - - if (!(trade instanceof SellerTrade)) { - return false; - } - - return checkNotNull(trade.getContract()).getSellerPaymentAccountPayload() instanceof AssetsAccountPayload; + private boolean isXmrTrade(Trade trade) { + return (checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")); } private boolean networkAndWalletReady() { - return p2PService.isBootstrapped() && - walletsSetup.isDownloadComplete() && - walletsSetup.hasSufficientPeersForBroadcast(); + //TODO We need to check if false and add listeners + boolean bootstrapped = p2PService.isBootstrapped(); + boolean downloadComplete = walletsSetup.isDownloadComplete(); + boolean hasSufficientPeersForBroadcast = walletsSetup.hasSufficientPeersForBroadcast(); + return bootstrapped && downloadComplete && hasSufficientPeersForBroadcast; } private boolean is32BitHexStringInValid(String hexString) { diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 00b27cb8515..177e0cad3d4 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -588,6 +588,8 @@ portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data. {0} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published. # suppress inspection "UnusedProperty" +portfolio.pending.autoConf.state.DISPUTE_OPENED=Dispute was opened. Service stopped. +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.PENDING=Service results: {0}/{1}; {2} diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 3fb3ad6b27f..375347939a2 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -1176,6 +1176,7 @@ public static String getProofResultAsString(@Nullable AssetTxProofResult result) case INVALID_DATA: return Res.get(key, result.getDetails()); case PAYOUT_TX_ALREADY_PUBLISHED: + case DISPUTE_OPENED: case REQUESTS_STARTED: return Res.get(key); case PENDING: From 5732053382a3025a025c658a379c97cd55cb483d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 16:39:14 -0500 Subject: [PATCH 007/108] Enable onFiatPaymentReceived again (was for dev testing) - Remove commented out code - do isFiatReceived in stream filter --- .../trade/txproof/xmr/XmrTxProofService.java | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 86920ebf59e..f74c2034bb3 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -154,26 +154,13 @@ private void processTrades(List trades) { .filter(trade -> trade instanceof SellerTrade) .map(trade -> (SellerTrade) trade) .filter(this::isXmrTrade) - .forEach(this::processCandidate); - - /* trades.stream() - .filter(trade -> trade instanceof SellerTrade) - .map(trade -> (SellerTrade) trade) - .filter(this::isXmrBuyer) - .filter(this::isExpectedTradeState) - .filter(trade -> networkAndWalletReady()) - .forEach(this::processTrade); - */ + .filter(trade -> !trade.isFiatReceived()) + .forEach(this::processOrAddListener); } // Basic requirements are fulfilled. // We might register a state listener to process further if expected state appears - private void processCandidate(SellerTrade trade) { - if (trade.isFiatReceived()) { - // We are done already. - return; - } - + private void processOrAddListener(SellerTrade trade) { if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { processTrade(trade); } else { @@ -230,9 +217,9 @@ private void processTrade(SellerTrade trade) { log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", trade.getShortId()); log.info("###########################################################################################"); - /* trade.onFiatPaymentReceived(() -> { + trade.onFiatPaymentReceived(() -> { }, errorMessage -> { - });*/ + }); } if (assetTxProofResult.isTerminal()) { From 33599132472511ea48f4c003c424a4ee0386627e Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 17:32:03 -0500 Subject: [PATCH 008/108] Ensure that p2p network and wallet are ready --- .../trade/txproof/xmr/XmrTxProofService.java | 154 +++++++++++++----- 1 file changed, 117 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index f74c2034bb3..8585af47aa0 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -33,14 +33,19 @@ import bisq.core.user.AutoConfirmSettings; import bisq.core.user.Preferences; +import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.P2PService; -import bisq.common.UserThread; import bisq.common.app.DevEnv; import javax.inject.Inject; import javax.inject.Singleton; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.monadic.MonadicBinding; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; @@ -75,6 +80,9 @@ public class XmrTxProofService implements AssetTxProofService { private final Map servicesByTradeId = new HashMap<>(); private AutoConfirmSettings autoConfirmSettings; private Map> tradeStateListenerMap = new HashMap<>(); + private ChangeListener btcPeersListener, btcBlockListener; + private BootstrapListener bootstrapListener; + private MonadicBinding p2pNetworkAndWalletReady; /////////////////////////////////////////////////////////////////////////////////////////// @@ -112,11 +120,41 @@ public XmrTxProofService(FilterManager filterManager, @Override public void onAllServicesInitialized() { + // As we might trigger the payout tx we want to be sure that we are well connected to the Bitcoin network. + // onAllServicesInitialized is called once we have received the initial data but we want to have our + // hidden service published and upDatedDataResponse received before we start. + p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped(), hasSufficientBtcPeers(), isBtcBlockDownloadComplete(), + (isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete) -> { + log.info("isP2pBootstrapped={}, hasSufficientBtcPeers={} isBtcBlockDownloadComplete={}", + isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete); + return isP2pBootstrapped && hasSufficientBtcPeers && isBtcBlockDownloadComplete; + }); + + p2pNetworkAndWalletReady.subscribe((observable, oldValue, newValue) -> { + if (newValue) { + onP2pNetworkAndWalletReady(); + } + }); + } + + @Override + public void shutDown() { + servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); + servicesByTradeId.clear(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void onP2pNetworkAndWalletReady() { if (!preferences.findAutoConfirmSettings("XMR").isPresent()) { log.error("AutoConfirmSettings is not present"); } autoConfirmSettings = preferences.findAutoConfirmSettings("XMR").get(); + // We register a listener to stop running services. For new trades we check anyway in the trade validation filterManager.filterProperty().addListener((observable, oldValue, newValue) -> { if (isAutoConfDisabledByFilter()) { servicesByTradeId.values().stream().map(XmrTxProofRequestsPerTrade::getTrade).forEach(trade -> @@ -126,6 +164,7 @@ public void onAllServicesInitialized() { } }); + // We listen on new trades ObservableList tradableList = tradeManager.getTradableList(); tradableList.addListener((ListChangeListener) c -> { c.next(); @@ -133,41 +172,35 @@ public void onAllServicesInitialized() { processTrades(c.getAddedSubList()); } }); - // Network is usually not ready at onAllServicesInitialized - //TODO we need to add listeners - UserThread.runAfter(() -> processTrades(tradableList), 1); - } - @Override - public void shutDown() { - servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); - servicesByTradeId.clear(); + // Process existing trades + processTrades(tradableList); } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - private void processTrades(List trades) { trades.stream() .filter(trade -> trade instanceof SellerTrade) .map(trade -> (SellerTrade) trade) .filter(this::isXmrTrade) - .filter(trade -> !trade.isFiatReceived()) - .forEach(this::processOrAddListener); + .filter(trade -> !trade.isFiatReceived()) // Phase name is from the time when it was fiat only. Means counter currency (XMR) received. + .forEach(this::processTradeOrAddListener); } // Basic requirements are fulfilled. - // We might register a state listener to process further if expected state appears - private void processOrAddListener(SellerTrade trade) { - if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { - processTrade(trade); + // We process further if we are in the expected state or register a listener + private void processTradeOrAddListener(SellerTrade trade) { + if (isExpectedTradeState(trade.getState())) { + startRequestsIfValid(trade); } else { - // We are expecting SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG in the future, so listen to changes + // We are expecting SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG in the future, so listen on changes ChangeListener tradeStateListener = (observable, oldValue, newValue) -> { - if (newValue == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { - processTrade(trade); + if (isExpectedTradeState(newValue)) { + ChangeListener listener = tradeStateListenerMap.remove(trade.getId()); + if (listener != null) { + trade.stateProperty().removeListener(listener); + } + + startRequestsIfValid(trade); } }; tradeStateListenerMap.put(trade.getId(), tradeStateListener); @@ -175,14 +208,7 @@ private void processOrAddListener(SellerTrade trade) { } } - private void processTrade(SellerTrade trade) { - tradeStateListenerMap.remove(trade.getId()); - - if (!networkAndWalletReady()) { - //TODO handle listeners - return; - } - + private void startRequestsIfValid(SellerTrade trade) { String txId = trade.getCounterCurrencyTxId(); String txHash = trade.getCounterCurrencyExtraData(); if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { @@ -202,6 +228,10 @@ private void processTrade(SellerTrade trade) { return; } + startRequests(trade); + } + + private void startRequests(SellerTrade trade) { XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(httpClient, trade, autoConfirmSettings, @@ -232,6 +262,60 @@ private void processTrade(SellerTrade trade) { } + /////////////////////////////////////////////////////////////////////////////////////////// + // Startup checks + /////////////////////////////////////////////////////////////////////////////////////////// + + private BooleanProperty isBtcBlockDownloadComplete() { + BooleanProperty result = new SimpleBooleanProperty(); + if (walletsSetup.isDownloadComplete()) { + result.set(true); + } else { + btcBlockListener = (observable, oldValue, newValue) -> { + if (walletsSetup.isDownloadComplete()) { + walletsSetup.downloadPercentageProperty().removeListener(btcBlockListener); + result.set(true); + } + }; + walletsSetup.downloadPercentageProperty().addListener(btcBlockListener); + } + return result; + } + + private BooleanProperty hasSufficientBtcPeers() { + BooleanProperty result = new SimpleBooleanProperty(); + if (walletsSetup.hasSufficientPeersForBroadcast()) { + result.set(true); + } else { + btcPeersListener = (observable, oldValue, newValue) -> { + if (walletsSetup.hasSufficientPeersForBroadcast()) { + walletsSetup.numPeersProperty().removeListener(btcPeersListener); + result.set(true); + } + }; + walletsSetup.numPeersProperty().addListener(btcPeersListener); + } + return result; + } + + private BooleanProperty isP2pBootstrapped() { + BooleanProperty result = new SimpleBooleanProperty(); + if (p2PService.isBootstrapped()) { + result.set(true); + } else { + bootstrapListener = new BootstrapListener() { + @Override + public void onUpdatedDataReceived() { + p2PService.removeP2PServiceListener(bootstrapListener); + result.set(true); + } + }; + p2PService.addP2PServiceListener(bootstrapListener); + } + return result; + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Validation /////////////////////////////////////////////////////////////////////////////////////////// @@ -240,12 +324,8 @@ private boolean isXmrTrade(Trade trade) { return (checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")); } - private boolean networkAndWalletReady() { - //TODO We need to check if false and add listeners - boolean bootstrapped = p2PService.isBootstrapped(); - boolean downloadComplete = walletsSetup.isDownloadComplete(); - boolean hasSufficientPeersForBroadcast = walletsSetup.hasSufficientPeersForBroadcast(); - return bootstrapped && downloadComplete && hasSufficientPeersForBroadcast; + private boolean isExpectedTradeState(Trade.State newValue) { + return newValue == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG; } private boolean is32BitHexStringInValid(String hexString) { From a4a5f18662466e6aa99c54272a1b4f1df7805074 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 18:49:00 -0500 Subject: [PATCH 009/108] Make var local --- .../java/bisq/core/trade/txproof/xmr/XmrTxProofService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 8585af47aa0..99d5f670d36 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -82,7 +82,6 @@ public class XmrTxProofService implements AssetTxProofService { private Map> tradeStateListenerMap = new HashMap<>(); private ChangeListener btcPeersListener, btcBlockListener; private BootstrapListener bootstrapListener; - private MonadicBinding p2pNetworkAndWalletReady; /////////////////////////////////////////////////////////////////////////////////////////// @@ -123,7 +122,7 @@ public void onAllServicesInitialized() { // As we might trigger the payout tx we want to be sure that we are well connected to the Bitcoin network. // onAllServicesInitialized is called once we have received the initial data but we want to have our // hidden service published and upDatedDataResponse received before we start. - p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped(), hasSufficientBtcPeers(), isBtcBlockDownloadComplete(), + MonadicBinding p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped(), hasSufficientBtcPeers(), isBtcBlockDownloadComplete(), (isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete) -> { log.info("isP2pBootstrapped={}, hasSufficientBtcPeers={} isBtcBlockDownloadComplete={}", isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete); From 11827a42c6959e875742439a8bbd102e5b2a8d80 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 19:15:42 -0500 Subject: [PATCH 010/108] Set default services based on localhost check not on devMode check --- .../trade/txproof/xmr/XmrTxProofRequest.java | 4 +-- .../bisq/core/user/AutoConfirmSettings.java | 25 +++++++++++---- .../main/java/bisq/core/user/Preferences.java | 32 +++++++++++-------- .../bisq/core/user/PreferencesPayload.java | 2 -- .../settings/preferences/PreferencesView.java | 2 +- 5 files changed, 39 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index c853af2410c..cfac06f4b23 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -268,12 +268,10 @@ public String toString() { /////////////////////////////////////////////////////////////////////////////////////////// private String getShortId() { - return Utilities.getShortId(model.getTradeId()) + " @ " + - model.getServiceAddress().substring(0, 6); + return Utilities.getShortId(model.getTradeId()) + " @ " + model.getServiceAddress().substring(0, 6); } private boolean isTimeOutReached() { return System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD; } - } diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java index 81fb2a57058..f94e0538492 100644 --- a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -25,10 +25,13 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Getter public final class AutoConfirmSettings implements PersistablePayload { public interface Listener { @@ -42,13 +45,21 @@ public interface Listener { private String currencyCode; private List listeners = new CopyOnWriteArrayList<>(); - static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { - return new AutoConfirmSettings( - false, - 5, - Coin.COIN.value, - serviceAddresses, - "XMR"); + @SuppressWarnings("SameParameterValue") + static Optional getDefault(List serviceAddresses, String currencyCode) { + //noinspection SwitchStatementWithTooFewBranches + switch (currencyCode) { + case "XMR": + return Optional.of(new AutoConfirmSettings( + false, + 5, + Coin.COIN.value, + serviceAddresses, + "XMR")); + default: + log.error("No AutoConfirmSettings supported yet for currency {}", currencyCode); + return Optional.empty(); + } } public AutoConfirmSettings(boolean enabled, diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index d267cfa604e..f4ef6285e0e 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -33,7 +33,6 @@ import bisq.network.p2p.network.BridgeAddressProvider; -import bisq.common.app.DevEnv; import bisq.common.config.BaseCurrencyNetwork; import bisq.common.config.Config; import bisq.common.proto.persistable.PersistedDataHost; @@ -124,16 +123,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid new BlockChainExplorer("bsq.bisq.cc (@m52go)", "https://bsq.bisq.cc/tx.html?tx=", "https://bsq.bisq.cc/Address.html?addr=") )); - // list of XMR proof providers : this list will be used if no preference has been set - public static final List getDefaultXmrProofProviders() { - if (DevEnv.isDevMode()) { - return new ArrayList<>(Arrays.asList("78.47.61.90:8081")); - } else { - // TODO we need at least 2 for release - return new ArrayList<>(Arrays.asList( - "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); - } - } + //TODO add a second before release + private static final ArrayList XMR_TX_PROOF_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList( + "78.47.61.90:8081")); + //TODO add a second before release + private static final ArrayList XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList( + "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; @@ -321,7 +316,11 @@ public void readPersisted() { } if (prefPayload.getAutoConfirmSettingsList().isEmpty()) { - getAutoConfirmSettingsList().add(AutoConfirmSettings.getDefaultForXmr(getDefaultXmrProofProviders())); + List defaultXmrTxProofServices = getDefaultXmrTxProofServices(); + AutoConfirmSettings.getDefault(defaultXmrTxProofServices, "XMR") + .ifPresent(xmrAutoConfirmSettings -> { + getAutoConfirmSettingsList().add(xmrAutoConfirmSettings); + }); } // We set the capability in CoreNetworkCapabilities if the program argument is set. @@ -332,7 +331,6 @@ public void readPersisted() { persist(); } - /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// @@ -875,6 +873,14 @@ public int getBlockNotifyPort() { } } + public List getDefaultXmrTxProofServices() { + if (config.useLocalhostForP2P) { + return XMR_TX_PROOF_SERVICES_CLEAR_NET; + } else { + return XMR_TX_PROOF_SERVICES; + } + } + /////////////////////////////////////////////////////////////////////////////////////////// // Private diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index fbf97ea575f..3af5ecfa86d 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -52,9 +52,7 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnvelope { private String userLanguage; private Country userCountry; - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private List fiatCurrencies = new ArrayList<>(); - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private List cryptoCurrencies = new ArrayList<>(); private BlockChainExplorer blockChainExplorerMainNet; private BlockChainExplorer blockChainExplorerTestNet; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 60ab77a3418..19c07503448 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -677,7 +677,7 @@ private void initializeAutoConfirmOptions() { List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); // revert to default service providers when user empties the list if (serviceAddresses.size() == 1 && serviceAddresses.get(0).isEmpty()) { - serviceAddresses = Preferences.getDefaultXmrProofProviders(); + serviceAddresses = preferences.getDefaultXmrTxProofServices(); } preferences.setAutoConfServiceAddresses("XMR", serviceAddresses); } From e01ebdf6e88bb65258b320bdc887b360fd93ecda Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 20:33:49 -0500 Subject: [PATCH 011/108] Change min required confirmations from 0 to 1. If users accept visible in mempool only txs its their own risk and they can manually confirm anyway. We should not support 0 conf txs. --- .../bisq/desktop/main/settings/preferences/PreferencesView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 19c07503448..2a4075cfbcb 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -661,7 +661,7 @@ private void initializeAutoConfirmOptions() { autoConfirmXmrToggle = addSlideToggleButton(autoConfirmGridPane, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); autoConfRequiredConfirmationsTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); - autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(0, DevEnv.isDevMode() ? 100000000 : 1000)); + autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(1, DevEnv.isDevMode() ? 100000000 : 1000)); autoConfTradeLimitTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); autoConfTradeLimitTf.setValidator(new BtcValidator(formatter)); From 33c8728fde044612b3aa5166f648b73dd19d7633 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 20:50:46 -0500 Subject: [PATCH 012/108] Add monero orange color code for tx confidence indicator --- desktop/src/main/java/bisq/desktop/bisq.css | 6 ++++++ desktop/src/main/java/bisq/desktop/theme-light.css | 3 +++ 2 files changed, 9 insertions(+) diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index e9ab97167cf..5556df770f2 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -856,6 +856,12 @@ textfield */ -fx-max-height: 20; } +#xmr-confidence { + -fx-progress-color: -xmr-orange; + -fx-max-width: 20; + -fx-max-height: 20; +} + .hyperlink, .hyperlink.force-underline .text, .hyperlink:hover, diff --git a/desktop/src/main/java/bisq/desktop/theme-light.css b/desktop/src/main/java/bisq/desktop/theme-light.css index 165d4e9d082..bff781eaf47 100644 --- a/desktop/src/main/java/bisq/desktop/theme-light.css +++ b/desktop/src/main/java/bisq/desktop/theme-light.css @@ -106,6 +106,9 @@ /* dao chart colors */ -bs-chart-dao-line1: -bs-color-green-3; -bs-chart-dao-line2: -bs-color-blue-5; + + /* Monero orange color code */ + -xmr-orange: #F26822; } .warning-box { From fa9893cdd4c86522cdee6de24384cb60ea3d41be Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 20:51:19 -0500 Subject: [PATCH 013/108] Show tx confidence indicator for XMR blocks. Show info icon --- .../trade/txproof/AssetTxProofResult.java | 16 ++++ .../trade/txproof/xmr/XmrTxProofParser.java | 2 +- .../trade/txproof/xmr/XmrTxProofRequest.java | 2 + .../xmr/XmrTxProofRequestsPerTrade.java | 9 ++- .../resources/i18n/displayStrings.properties | 4 +- .../steps/seller/SellerStep3View.java | 73 ++++++++++++++++--- 6 files changed, 91 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java index 0f6ba959709..73f22a5ebfd 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java @@ -45,6 +45,10 @@ public enum AssetTxProofResult { @Getter private int numRequiredSuccessResults; @Getter + private int numConfirmations; + @Getter + private int numRequiredConfirmations; + @Getter private String details = ""; // If isTerminal is set it means that we stop the service @Getter @@ -69,6 +73,16 @@ public AssetTxProofResult numRequiredSuccessResults(int numRequiredSuccessResult return this; } + public AssetTxProofResult numConfirmations(int numConfirmations) { + this.numConfirmations = numConfirmations; + return this; + } + + public AssetTxProofResult numRequiredConfirmations(int numRequiredConfirmations) { + this.numRequiredConfirmations = numRequiredConfirmations; + return this; + } + public AssetTxProofResult details(String details) { this.details = details; return this; @@ -79,6 +93,8 @@ public String toString() { return "AssetTxProofResult{" + "\n numSuccessResults=" + numSuccessResults + ",\n requiredSuccessResults=" + numRequiredSuccessResults + + ",\n numConfirmations=" + numConfirmations + + ",\n numRequiredConfirmations=" + numRequiredConfirmations + ",\n details='" + details + '\'' + "\n} " + super.toString(); } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index cf7b7835cfc..5a11cbc5c6b 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -162,7 +162,7 @@ public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { if (confirmations < confirmsRequired) { return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); } else { - return XmrTxProofRequest.Result.SUCCESS; + return XmrTxProofRequest.Result.SUCCESS.with(XmrTxProofRequest.Detail.SUCCESS.numConfirmations(confirmations)); } } catch (JsonParseException | NullPointerException e) { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index cfac06f4b23..2be07d291cb 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -85,6 +85,8 @@ enum Detail { TX_NOT_FOUND, // Tx not visible in network yet. Could be also other error PENDING_CONFIRMATIONS, + SUCCESS, + // Error states CONNECTION_FAILURE, API_INVALID, diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 774423b3292..634f6b8ae79 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -171,7 +171,12 @@ public void requestFromAllServices(Consumer resultHandler, F // have completed on the service level. log.info("All {} tx proof requests for trade {} have been successful.", numRequiredSuccessResults, trade.getShortId()); - assetTxProofResult = AssetTxProofResult.COMPLETED; + XmrTxProofRequest.Detail detail = result.getDetail(); + assetTxProofResult = AssetTxProofResult.COMPLETED + .numSuccessResults(numSuccessResults) + .numRequiredSuccessResults(numRequiredSuccessResults) + .numConfirmations(detail != null ? detail.getNumConfirmations() : 0) + .numRequiredConfirmations(autoConfirmSettings.getRequiredConfirmations()); } break; case FAILED: @@ -292,6 +297,8 @@ private AssetTxProofResult getAssetTxProofResultForPending(XmrTxProofRequest.Res return AssetTxProofResult.PENDING .numSuccessResults(numSuccessResults) .numRequiredSuccessResults(numRequiredSuccessResults) + .numConfirmations(detail != null ? detail.getNumConfirmations() : 0) + .numRequiredConfirmations(autoConfirmSettings.getRequiredConfirmations()) .details(detailString); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 177e0cad3d4..c5181e56b8f 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -572,9 +572,9 @@ portfolio.pending.step5.completed=Completed portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status portfolio.pending.autoConf=Auto-confirmed - +portfolio.pending.autoConf.blocks=XMR confirmations: {0} / Required: {1} portfolio.pending.autoConf.state.xmr.txKeyReused=Transaction key re-used. Please open a dispute. -portfolio.pending.autoConf.state.confirmations=Confirmations: {0}/{1} +portfolio.pending.autoConf.state.confirmations=XMR confirmations: {0}/{1} portfolio.pending.autoConf.state.txNotFound=Transaction not seen in mem-pool yet portfolio.pending.autoConf.state.txKeyOrTxIdInvalid=No valid transaction ID / transaction key portfolio.pending.autoConf.state.filterDisabledFeature=Disabled by developers. diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 4bff3492dfe..5f25baaa097 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -18,8 +18,10 @@ package bisq.desktop.main.portfolio.pendingtrades.steps.seller; import bisq.desktop.components.BusyAnimation; +import bisq.desktop.components.InfoTextField; import bisq.desktop.components.TextFieldWithCopyIcon; import bisq.desktop.components.TitledGroupBg; +import bisq.desktop.components.indicator.TxConfidenceIndicator; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; @@ -49,6 +51,7 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.DevEnv; +import bisq.common.util.Tuple2; import bisq.common.util.Tuple4; import javafx.scene.control.Button; @@ -57,6 +60,9 @@ import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; + +import javafx.geometry.Insets; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; @@ -65,20 +71,18 @@ import java.util.Optional; -import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup; -import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; -import static bisq.desktop.util.FormBuilder.addTitledGroupBg; -import static bisq.desktop.util.FormBuilder.addTopLabelTextFieldWithCopyIcon; +import static bisq.desktop.util.FormBuilder.*; public class SellerStep3View extends TradeStepView { + private final ChangeListener proofResultListener; private Button confirmButton; private Label statusLabel; private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; - private TextFieldWithCopyIcon assetTxProofResultField; - private final ChangeListener proofResultListener; + private InfoTextField assetTxProofResultField; + private TxConfidenceIndicator assetTxConfidenceIndicator; /////////////////////////////////////////////////////////////////////////////////////////// @@ -230,9 +234,28 @@ protected void addContent() { } if (isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR")) { - assetTxProofResultField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, - Res.get("portfolio.pending.step3_seller.autoConf.status.label"), - "", Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE).second; + assetTxProofResultField = new InfoTextField(); + + Tuple2 topLabelWithVBox = getTopLabelWithVBox(Res.get("portfolio.pending.step3_seller.autoConf.status.label"), assetTxProofResultField); + VBox vBox = topLabelWithVBox.second; + + assetTxConfidenceIndicator = new TxConfidenceIndicator(); + assetTxConfidenceIndicator.setId("xmr-confidence"); + assetTxConfidenceIndicator.setProgress(0); + assetTxConfidenceIndicator.setTooltip(new Tooltip()); + assetTxProofResultField.setContentForInfoPopOver(createPopoverLabel(Res.get("setting.info.msg"))); + + HBox.setMargin(assetTxConfidenceIndicator, new Insets(Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0)); + + HBox hBox = new HBox(); + HBox.setHgrow(vBox, Priority.ALWAYS); + hBox.setSpacing(10); + hBox.getChildren().addAll(vBox, assetTxConfidenceIndicator); + + GridPane.setRowIndex(hBox, gridRow); + GridPane.setColumnIndex(hBox, 1); + GridPane.setMargin(hBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE + Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0)); + gridPane.getChildren().add(hBox); } TextFieldWithCopyIcon myPaymentDetailsTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, @@ -274,7 +297,6 @@ protected void addContent() { // Info /////////////////////////////////////////////////////////////////////////////////////////// - @Override protected String getInfoText() { String currencyCode = model.dataModel.getCurrencyCode(); @@ -446,7 +468,36 @@ else if (paymentAccountPayload instanceof SepaInstantAccountPayload) private void applyAssetTxProofResult(AssetTxProofResult result) { String txt = GUIUtil.getProofResultAsString(result); assetTxProofResultField.setText(txt); - assetTxProofResultField.setTooltip(new Tooltip(txt)); + switch (result) { + case PENDING: + case COMPLETED: + if (result.getNumRequiredConfirmations() > 0) { + int numRequiredConfirmations = result.getNumRequiredConfirmations(); + int numConfirmations = result.getNumConfirmations(); + if (numConfirmations == 0) { + assetTxConfidenceIndicator.setProgress(-1); + } else { + double progress = Math.min(1, (double) numConfirmations / (double) numRequiredConfirmations); + assetTxConfidenceIndicator.setProgress(progress); + assetTxConfidenceIndicator.getTooltip().setText( + Res.get("portfolio.pending.autoConf.blocks", + numConfirmations, numRequiredConfirmations)); + } + } + break; + default: + // Set invisible by default + assetTxConfidenceIndicator.setProgress(0); + break; + } + } + + private Label createPopoverLabel(String text) { + Label label = new Label(text); + label.setPrefWidth(600); + label.setWrapText(true); + label.setPadding(new Insets(10)); + return label; } @Override From 044d23f93313290ba3822152b7f77208d8a62eed Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 21:02:16 -0500 Subject: [PATCH 014/108] Adjust auto-conf css for badge - make it xmr orange - make with dynamic with 10 px padding left/right - fix 1 px vertical offset --- desktop/src/main/java/bisq/desktop/bisq.css | 6 ++++-- .../portfolio/pendingtrades/steps/buyer/BuyerStep4View.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 5556df770f2..90e24fc0c47 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -436,8 +436,10 @@ tree-table-view:focused { -fx-pref-width: 30; } -.jfx-badge.autoconf .badge-pane { - -fx-pref-width: 100; +.jfx-badge.auto-conf .badge-pane { + -fx-background-color: -xmr-orange; + -fx-pref-width: -1; + -fx-padding: -1 10 0 10; } .jfx-badge .badge-pane .label { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index b0d6295cfc2..29076fd47ef 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -112,7 +112,7 @@ protected void addContent() { JFXBadge autoConfBadge = new JFXBadge(new Label(""), Pos.BASELINE_RIGHT); autoConfBadge.setText(Res.get("portfolio.pending.autoConf")); - autoConfBadge.getStyleClass().add("autoconf"); + autoConfBadge.getStyleClass().add("auto-conf"); HBox hBox2 = new HBox(1, completedTradeLabel, autoConfBadge); GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); From 63444b1f77f0eb7af3d25c727a9991033ec0bee2 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 21:14:06 -0500 Subject: [PATCH 015/108] Add null check. Improve text --- core/src/main/resources/i18n/displayStrings.properties | 6 +++--- .../pendingtrades/steps/seller/SellerStep3View.java | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index c5181e56b8f..94d4495f9f8 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -588,11 +588,11 @@ portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data. {0} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published. # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.DISPUTE_OPENED=Dispute was opened. Service stopped. +portfolio.pending.autoConf.state.DISPUTE_OPENED=Dispute was opened. Auto-confirm is deactivated for that trade. # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started +portfolio.pending.autoConf.state.REQUESTS_STARTED=Transaction proof requests started # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.PENDING=Service results: {0}/{1}; {2} +portfolio.pending.autoConf.state.PENDING=Success results: {0}/{1}; {2} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded # suppress inspection "UnusedProperty" diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 5f25baaa097..b0e0fa57c22 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -71,6 +71,8 @@ import java.util.Optional; +import javax.annotation.Nullable; + import static bisq.desktop.util.FormBuilder.*; public class SellerStep3View extends TradeStepView { @@ -465,9 +467,14 @@ else if (paymentAccountPayload instanceof SepaInstantAccountPayload) } } - private void applyAssetTxProofResult(AssetTxProofResult result) { + private void applyAssetTxProofResult(@Nullable AssetTxProofResult result) { String txt = GUIUtil.getProofResultAsString(result); assetTxProofResultField.setText(txt); + if (result == null) { + assetTxConfidenceIndicator.setProgress(0); + return; + } + switch (result) { case PENDING: case COMPLETED: From 720b63fc00c6f091d61ee5268ff272c2359ebbab Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 21:21:18 -0500 Subject: [PATCH 016/108] Do not overwrite useDevMode with useDevModeHeader If --useDevModeHeader is not set it is false by default. If user has --useDevMode=true set it would overwrite his value. --- core/src/main/java/bisq/core/app/BisqExecutable.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/bisq/core/app/BisqExecutable.java b/core/src/main/java/bisq/core/app/BisqExecutable.java index 2fe5be9ee0c..bb36e1ffe49 100644 --- a/core/src/main/java/bisq/core/app/BisqExecutable.java +++ b/core/src/main/java/bisq/core/app/BisqExecutable.java @@ -165,7 +165,6 @@ protected void applyInjector() { protected void setupDevEnv() { DevEnv.setDevMode(config.useDevMode); - DevEnv.setDevMode(config.useDevModeHeader); DevEnv.setDaoActivated(config.daoActivated); } From 2400014db6fdba60d6a88c22911803c0206dedf8 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 21:22:54 -0500 Subject: [PATCH 017/108] Change hex color code to lower case --- desktop/src/main/java/bisq/desktop/theme-light.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/theme-light.css b/desktop/src/main/java/bisq/desktop/theme-light.css index bff781eaf47..910f7f4233d 100644 --- a/desktop/src/main/java/bisq/desktop/theme-light.css +++ b/desktop/src/main/java/bisq/desktop/theme-light.css @@ -108,7 +108,7 @@ -bs-chart-dao-line2: -bs-color-blue-5; /* Monero orange color code */ - -xmr-orange: #F26822; + -xmr-orange: #f26822; } .warning-box { From 3e933c31a5be225fc242eb3b38046dcfdda43db7 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Thu, 3 Sep 2020 23:50:28 -0500 Subject: [PATCH 018/108] Fix bug with all mobile notifications for disputes are sent at startup. --- .../alerts/DisputeMsgEvents.java | 53 +++++++++---------- .../core/support/messages/ChatMessage.java | 6 +-- .../main/offer/MutableOfferViewModel.java | 2 +- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java b/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java index 1c2b9390ffb..f6c4d97e9d1 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java +++ b/core/src/main/java/bisq/core/notifications/alerts/DisputeMsgEvents.java @@ -73,10 +73,20 @@ public void onAllServicesInitialized() { } }); mediationManager.getDisputesAsObservableList().forEach(this::setDisputeListener); + + // We do not need a handling for unread messages as mailbox messages arrive later and will trigger the + // event listeners. But the existing messages are not causing a notification. + } + + public static MobileMessage getTestMsg() { + String shortId = UUID.randomUUID().toString().substring(0, 8); + return new MobileMessage(Res.get("account.notifications.dispute.message.title"), + Res.get("account.notifications.dispute.message.msg", shortId), + shortId, + MobileMessageType.DISPUTE); } private void setDisputeListener(Dispute dispute) { - //TODO use weak ref or remove listener log.debug("We got a dispute added. id={}, tradeId={}", dispute.getId(), dispute.getTradeId()); dispute.getChatMessages().addListener((ListChangeListener) c -> { log.debug("We got a ChatMessage added. id={}, tradeId={}", dispute.getId(), dispute.getTradeId()); @@ -85,31 +95,24 @@ private void setDisputeListener(Dispute dispute) { c.getAddedSubList().forEach(chatMessage -> onChatMessage(chatMessage, dispute)); } }); - - //TODO test - if (!dispute.getChatMessages().isEmpty()) - onChatMessage(dispute.getChatMessages().get(0), dispute); } private void onChatMessage(ChatMessage chatMessage, Dispute dispute) { - // TODO we need to prevent to send msg for old dispute messages again at restart - // Maybe we need a new property in ChatMessage - // As key is not set in initial iterations it seems we don't need an extra handling. - // the mailbox msg is set a bit later so that triggers a notification, but not the old messages. + if (chatMessage.getSenderNodeAddress().equals(p2PService.getAddress())) { + return; + } // We only send msg in case we are not the sender - if (!chatMessage.getSenderNodeAddress().equals(p2PService.getAddress())) { - String shortId = chatMessage.getShortId(); - MobileMessage message = new MobileMessage(Res.get("account.notifications.dispute.message.title"), - Res.get("account.notifications.dispute.message.msg", shortId), - shortId, - MobileMessageType.DISPUTE); - try { - mobileNotificationService.sendMessage(message); - } catch (Exception e) { - log.error(e.toString()); - e.printStackTrace(); - } + String shortId = chatMessage.getShortId(); + MobileMessage message = new MobileMessage(Res.get("account.notifications.dispute.message.title"), + Res.get("account.notifications.dispute.message.msg", shortId), + shortId, + MobileMessageType.DISPUTE); + try { + mobileNotificationService.sendMessage(message); + } catch (Exception e) { + log.error(e.toString()); + e.printStackTrace(); } // We check at every new message if it might be a message sent after the dispute had been closed. If that is the @@ -122,12 +125,4 @@ private void onChatMessage(ChatMessage chatMessage, Dispute dispute) { dispute.setIsClosed(false); } } - - public static MobileMessage getTestMsg() { - String shortId = UUID.randomUUID().toString().substring(0, 8); - return new MobileMessage(Res.get("account.notifications.dispute.message.title"), - Res.get("account.notifications.dispute.message.msg", shortId), - shortId, - MobileMessageType.DISPUTE); - } } diff --git a/core/src/main/java/bisq/core/support/messages/ChatMessage.java b/core/src/main/java/bisq/core/support/messages/ChatMessage.java index 3b38c8a244b..a4e328ec390 100644 --- a/core/src/main/java/bisq/core/support/messages/ChatMessage.java +++ b/core/src/main/java/bisq/core/support/messages/ChatMessage.java @@ -351,8 +351,7 @@ private void notifyChangeListener() { @Override public String toString() { return "ChatMessage{" + - "\n type='" + supportType + '\'' + - ",\n tradeId='" + tradeId + '\'' + + "\n tradeId='" + tradeId + '\'' + ",\n traderId=" + traderId + ",\n senderIsTrader=" + senderIsTrader + ",\n message='" + message + '\'' + @@ -360,10 +359,9 @@ public String toString() { ",\n senderNodeAddress=" + senderNodeAddress + ",\n date=" + date + ",\n isSystemMessage=" + isSystemMessage + + ",\n wasDisplayed=" + wasDisplayed + ",\n arrivedProperty=" + arrivedProperty + ",\n storedInMailboxProperty=" + storedInMailboxProperty + - ",\n ChatMessage.uid='" + uid + '\'' + - ",\n messageVersion=" + messageVersion + ",\n acknowledgedProperty=" + acknowledgedProperty + ",\n sendMessageErrorProperty=" + sendMessageErrorProperty + ",\n ackErrorProperty=" + ackErrorProperty + diff --git a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java index 55b9e75d5de..989d063b25e 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/MutableOfferViewModel.java @@ -231,7 +231,7 @@ public void activate() { if (DevEnv.isDevMode()) { UserThread.runAfter(() -> { amount.set("0.001"); - price.set("0.0001"); // for BSQ + price.set("0.008"); minAmount.set(amount.get()); onFocusOutPriceAsPercentageTextField(true, false); applyMakerFee(); From f5d8f6ddc29f5c79c4aa1b53c57b64e8c9b15510 Mon Sep 17 00:00:00 2001 From: wiz Date: Sun, 6 Sep 2020 18:36:32 +0900 Subject: [PATCH 019/108] Add new Monero Explorer node77.monero.wiz.biz with onion --- core/src/main/java/bisq/core/user/Preferences.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index f4ef6285e0e..624e8c14809 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -123,12 +123,14 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid new BlockChainExplorer("bsq.bisq.cc (@m52go)", "https://bsq.bisq.cc/tx.html?tx=", "https://bsq.bisq.cc/Address.html?addr=") )); - //TODO add a second before release private static final ArrayList XMR_TX_PROOF_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList( - "78.47.61.90:8081")); - //TODO add a second before release + "78.47.61.90:8081", // @emzy + "node77.monero.wiz.biz" // @wiz + )); private static final ArrayList XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList( - "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); + "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion", // @emzy + "wizxmr4hbdxdszqm5rfyqvceyca5jq62ppvtuznasnk66wvhhvgm3uyd.onion", // @wiz + )); public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; From d55cc47284f6810c3c1ea30325299603c10a0e77 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 7 Sep 2020 00:46:06 +0900 Subject: [PATCH 020/108] Fix compile error in core/src/main/java/bisq/core/user/Preferences.java --- core/src/main/java/bisq/core/user/Preferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 624e8c14809..4ae7eba6409 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -129,7 +129,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid )); private static final ArrayList XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList( "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion", // @emzy - "wizxmr4hbdxdszqm5rfyqvceyca5jq62ppvtuznasnk66wvhhvgm3uyd.onion", // @wiz + "wizxmr4hbdxdszqm5rfyqvceyca5jq62ppvtuznasnk66wvhhvgm3uyd.onion" // @wiz )); public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; From 38d33ceab3db2df660c41d80f2dd502cfaa98d05 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 19:45:00 -0500 Subject: [PATCH 021/108] Remove offer from takers offerbook after take offer is completed. We do not wait until the offer got removed by a network remove message but remove it directly from the offer book. The broadcast gets now bundled and has 2 sec. delay so the removal from the network is a bit slower as it has been before. To avoid that the taker gets confused to see the same offer still in the offerbook we remove it manually. This removal has only local effect. Other trader might see the offer for a few seconds still (but cannot take it). --- .../main/offer/offerbook/OfferBook.java | 24 +++++++++++-------- .../offer/takeoffer/TakeOfferDataModel.java | 17 ++++++++++++- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java index 1f60ed26f6b..8cee9fb458a 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/offerbook/OfferBook.java @@ -98,20 +98,24 @@ public void onAdded(Offer offer) { @Override public void onRemoved(Offer offer) { - // Update state in case that that offer is used in the take offer screen, so it gets updated correctly - offer.setState(Offer.State.REMOVED); - - // clean up possible references in openOfferManager - tradeManager.onOfferRemovedFromRemoteOfferBook(offer); - // We don't use the contains method as the equals method in Offer takes state and errorMessage into account. - Optional candidateToRemove = offerBookListItems.stream() - .filter(item -> item.getOffer().getId().equals(offer.getId())) - .findAny(); - candidateToRemove.ifPresent(offerBookListItems::remove); + removeOffer(offer, tradeManager); } }); } + public void removeOffer(Offer offer, TradeManager tradeManager) { + // Update state in case that that offer is used in the take offer screen, so it gets updated correctly + offer.setState(Offer.State.REMOVED); + + // clean up possible references in openOfferManager + tradeManager.onOfferRemovedFromRemoteOfferBook(offer); + // We don't use the contains method as the equals method in Offer takes state and errorMessage into account. + Optional candidateToRemove = offerBookListItems.stream() + .filter(item -> item.getOffer().getId().equals(offer.getId())) + .findAny(); + candidateToRemove.ifPresent(offerBookListItems::remove); + } + public ObservableList getOfferBookListItems() { return offerBookListItems; } diff --git a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java index b35a9d67035..e34b3233a7c 100644 --- a/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/offer/takeoffer/TakeOfferDataModel.java @@ -19,6 +19,7 @@ import bisq.desktop.Navigation; import bisq.desktop.main.offer.OfferDataModel; +import bisq.desktop.main.offer.offerbook.OfferBook; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.util.GUIUtil; @@ -81,6 +82,7 @@ */ class TakeOfferDataModel extends OfferDataModel { private final TradeManager tradeManager; + private final OfferBook offerBook; private final BsqWalletService bsqWalletService; private final User user; private final FeeService feeService; @@ -120,6 +122,7 @@ class TakeOfferDataModel extends OfferDataModel { @Inject TakeOfferDataModel(TradeManager tradeManager, + OfferBook offerBook, BtcWalletService btcWalletService, BsqWalletService bsqWalletService, User user, FeeService feeService, @@ -134,6 +137,7 @@ class TakeOfferDataModel extends OfferDataModel { super(btcWalletService); this.tradeManager = tradeManager; + this.offerBook = offerBook; this.bsqWalletService = bsqWalletService; this.user = user; this.feeService = feeService; @@ -291,6 +295,7 @@ public void onClose() { btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); } + /////////////////////////////////////////////////////////////////////////////////////////// // UI actions /////////////////////////////////////////////////////////////////////////////////////////// @@ -325,7 +330,17 @@ void onTakeOffer(TradeResultHandler tradeResultHandler) { offer, paymentAccount.getId(), useSavingsWallet, - tradeResultHandler, + trade -> { + // We do not wait until the offer got removed by a network remove message but remove it + // directly from the offer book. The broadcast gets now bundled and has 2 sec. delay so the + // removal from the network is a bit slower as it has been before. To avoid that the taker gets + // confused to see the same offer still in the offerbook we remove it manually. This removal has + // only local effect. Other trader might see the offer for a few seconds + // still (but cannot take it). + offerBook.removeOffer(checkNotNull(trade.getOffer()), tradeManager); + + tradeResultHandler.handleResult(trade); + }, errorMessage -> { log.warn(errorMessage); new Popup().warning(errorMessage).show(); From cc2d258915311ebc76a6c0bcd0d28b74d3b5cd90 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 01:40:35 -0500 Subject: [PATCH 022/108] Scan disputes for accounts where same user used diff. real names. Might be fraudulent traders. --- .../payment/payload/BankAccountPayload.java | 2 +- .../payload/CashDepositAccountPayload.java | 2 +- .../payload/ChaseQuickPayAccountPayload.java | 2 +- .../payload/ClearXchangeAccountPayload.java | 2 +- .../InteracETransferAccountPayload.java | 2 +- .../payload/JapanBankAccountPayload.java | 7 +- .../payload/MoneyGramAccountPayload.java | 2 +- .../payload/PayloadWithHolderName.java | 22 ++ .../payload/PopmoneyAccountPayload.java | 2 +- .../payment/payload/SepaAccountPayload.java | 3 +- .../payload/SepaInstantAccountPayload.java | 2 +- .../payment/payload/SwishAccountPayload.java | 2 +- .../USPostalMoneyOrderAccountPayload.java | 2 +- .../payload/WesternUnionAccountPayload.java | 2 +- .../support/dispute/agent/FraudDetection.java | 215 ++++++++++++++++++ desktop/src/main/java/bisq/desktop/bisq.css | 5 + .../main/support/dispute/DisputeView.java | 23 +- .../dispute/agent/DisputeAgentView.java | 60 ++++- 18 files changed, 342 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/bisq/core/payment/payload/PayloadWithHolderName.java create mode 100644 core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java diff --git a/core/src/main/java/bisq/core/payment/payload/BankAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/BankAccountPayload.java index 4132b1934a8..3abfa8b2aea 100644 --- a/core/src/main/java/bisq/core/payment/payload/BankAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/BankAccountPayload.java @@ -39,7 +39,7 @@ @Getter @ToString @Slf4j -public abstract class BankAccountPayload extends CountryBasedPaymentAccountPayload { +public abstract class BankAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName { protected String holderName = ""; @Nullable protected String bankName; diff --git a/core/src/main/java/bisq/core/payment/payload/CashDepositAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/CashDepositAccountPayload.java index 10c39c0023a..afa97764a9d 100644 --- a/core/src/main/java/bisq/core/payment/payload/CashDepositAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/CashDepositAccountPayload.java @@ -42,7 +42,7 @@ @Setter @Getter @Slf4j -public class CashDepositAccountPayload extends CountryBasedPaymentAccountPayload { +public class CashDepositAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName { private String holderName = ""; @Nullable private String holderEmail; diff --git a/core/src/main/java/bisq/core/payment/payload/ChaseQuickPayAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/ChaseQuickPayAccountPayload.java index b8d93695e74..a320c36679e 100644 --- a/core/src/main/java/bisq/core/payment/payload/ChaseQuickPayAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/ChaseQuickPayAccountPayload.java @@ -37,7 +37,7 @@ @Setter @Getter @Slf4j -public final class ChaseQuickPayAccountPayload extends PaymentAccountPayload { +public final class ChaseQuickPayAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String email = ""; private String holderName = ""; diff --git a/core/src/main/java/bisq/core/payment/payload/ClearXchangeAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/ClearXchangeAccountPayload.java index 139da088893..f437d5d6720 100644 --- a/core/src/main/java/bisq/core/payment/payload/ClearXchangeAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/ClearXchangeAccountPayload.java @@ -37,7 +37,7 @@ @Setter @Getter @Slf4j -public final class ClearXchangeAccountPayload extends PaymentAccountPayload { +public final class ClearXchangeAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String emailOrMobileNr = ""; private String holderName = ""; diff --git a/core/src/main/java/bisq/core/payment/payload/InteracETransferAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/InteracETransferAccountPayload.java index f404766ad29..88b2042af87 100644 --- a/core/src/main/java/bisq/core/payment/payload/InteracETransferAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/InteracETransferAccountPayload.java @@ -39,7 +39,7 @@ @Setter @Getter @Slf4j -public final class InteracETransferAccountPayload extends PaymentAccountPayload { +public final class InteracETransferAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String email = ""; private String holderName = ""; private String question = ""; diff --git a/core/src/main/java/bisq/core/payment/payload/JapanBankAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/JapanBankAccountPayload.java index 4cfd4260e6f..807915b2bd8 100644 --- a/core/src/main/java/bisq/core/payment/payload/JapanBankAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/JapanBankAccountPayload.java @@ -37,7 +37,7 @@ @Setter @Getter @Slf4j -public final class JapanBankAccountPayload extends PaymentAccountPayload { +public final class JapanBankAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { // bank private String bankName = ""; private String bankCode = ""; @@ -137,4 +137,9 @@ public byte[] getAgeWitnessInputData() { String all = this.bankName + this.bankBranchName + this.bankAccountType + this.bankAccountNumber + this.bankAccountName; return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8)); } + + @Override + public String getHolderName() { + return bankAccountName; + } } diff --git a/core/src/main/java/bisq/core/payment/payload/MoneyGramAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/MoneyGramAccountPayload.java index e5f4ffc74d7..03734c68f68 100644 --- a/core/src/main/java/bisq/core/payment/payload/MoneyGramAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/MoneyGramAccountPayload.java @@ -39,7 +39,7 @@ @Setter @Getter @Slf4j -public class MoneyGramAccountPayload extends PaymentAccountPayload { +public class MoneyGramAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String holderName; private String countryCode = ""; private String state = ""; // is optional. we don't use @Nullable because it would makes UI code more complex. diff --git a/core/src/main/java/bisq/core/payment/payload/PayloadWithHolderName.java b/core/src/main/java/bisq/core/payment/payload/PayloadWithHolderName.java new file mode 100644 index 00000000000..25efe937f4a --- /dev/null +++ b/core/src/main/java/bisq/core/payment/payload/PayloadWithHolderName.java @@ -0,0 +1,22 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.payment.payload; + +public interface PayloadWithHolderName { + String getHolderName(); +} diff --git a/core/src/main/java/bisq/core/payment/payload/PopmoneyAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/PopmoneyAccountPayload.java index be018ffb558..3a451bda61c 100644 --- a/core/src/main/java/bisq/core/payment/payload/PopmoneyAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/PopmoneyAccountPayload.java @@ -37,7 +37,7 @@ @Setter @Getter @Slf4j -public final class PopmoneyAccountPayload extends PaymentAccountPayload { +public final class PopmoneyAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String accountId = ""; private String holderName = ""; diff --git a/core/src/main/java/bisq/core/payment/payload/SepaAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/SepaAccountPayload.java index c10dacb8437..59425e1bcfe 100644 --- a/core/src/main/java/bisq/core/payment/payload/SepaAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/SepaAccountPayload.java @@ -43,7 +43,7 @@ @ToString @Getter @Slf4j -public final class SepaAccountPayload extends CountryBasedPaymentAccountPayload { +public final class SepaAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName { @Setter private String holderName = ""; @Setter @@ -158,6 +158,7 @@ public byte[] getAgeWitnessInputData() { // slight changes in holder name (e.g. add or remove middle name) return super.getAgeWitnessInputData(ArrayUtils.addAll(iban.getBytes(StandardCharsets.UTF_8), bic.getBytes(StandardCharsets.UTF_8))); } + @Override public String getOwnerId() { return holderName; diff --git a/core/src/main/java/bisq/core/payment/payload/SepaInstantAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/SepaInstantAccountPayload.java index cb8b69cd6f8..54cffcd78d2 100644 --- a/core/src/main/java/bisq/core/payment/payload/SepaInstantAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/SepaInstantAccountPayload.java @@ -43,7 +43,7 @@ @ToString @Getter @Slf4j -public final class SepaInstantAccountPayload extends CountryBasedPaymentAccountPayload { +public final class SepaInstantAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName { @Setter private String holderName = ""; @Setter diff --git a/core/src/main/java/bisq/core/payment/payload/SwishAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/SwishAccountPayload.java index 6c236ba1304..f14eafb92e4 100644 --- a/core/src/main/java/bisq/core/payment/payload/SwishAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/SwishAccountPayload.java @@ -37,7 +37,7 @@ @Setter @Getter @Slf4j -public final class SwishAccountPayload extends PaymentAccountPayload { +public final class SwishAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String mobileNr = ""; private String holderName = ""; diff --git a/core/src/main/java/bisq/core/payment/payload/USPostalMoneyOrderAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/USPostalMoneyOrderAccountPayload.java index e705e032dc8..96a8dec5203 100644 --- a/core/src/main/java/bisq/core/payment/payload/USPostalMoneyOrderAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/USPostalMoneyOrderAccountPayload.java @@ -39,7 +39,7 @@ @Setter @Getter @Slf4j -public final class USPostalMoneyOrderAccountPayload extends PaymentAccountPayload { +public final class USPostalMoneyOrderAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName { private String postalAddress = ""; private String holderName = ""; diff --git a/core/src/main/java/bisq/core/payment/payload/WesternUnionAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/WesternUnionAccountPayload.java index f99b80e81f0..45f33186cc3 100644 --- a/core/src/main/java/bisq/core/payment/payload/WesternUnionAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/WesternUnionAccountPayload.java @@ -39,7 +39,7 @@ @Setter @Getter @Slf4j -public class WesternUnionAccountPayload extends CountryBasedPaymentAccountPayload { +public class WesternUnionAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName { private String holderName; private String city; private String state = ""; // is optional. we don't use @Nullable because it would makes UI code more complex. diff --git a/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java new file mode 100644 index 00000000000..901a739c347 --- /dev/null +++ b/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java @@ -0,0 +1,215 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.support.dispute.agent; + +import bisq.core.locale.Res; +import bisq.core.payment.payload.PayloadWithHolderName; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.support.dispute.Dispute; +import bisq.core.support.dispute.DisputeList; +import bisq.core.support.dispute.DisputeManager; +import bisq.core.trade.Contract; + +import bisq.common.crypto.Hash; +import bisq.common.crypto.PubKeyRing; +import bisq.common.util.Utilities; + +import javafx.collections.ListChangeListener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class FraudDetection { + public interface Listener { + void onSuspiciousDisputeDetected(); + } + + private final DisputeManager> disputeManager; + private Map> buyerRealNameAccountByAddressMap = new HashMap<>(); + private Map> sellerRealNameAccountByAddressMap = new HashMap<>(); + @Getter + private Map> accountsUsingMultipleNames = new HashMap<>(); + private List listeners = new CopyOnWriteArrayList<>(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public FraudDetection(DisputeManager> disputeManager) { + this.disputeManager = disputeManager; + + disputeManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> { + c.next(); + if (c.wasAdded()) { + checkForMultipleHolderNames(); + } + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void checkForMultipleHolderNames() { + log.error("checkForMultipleHolderNames"); + buildRealNameAccountMaps(); + detectUsageOfDifferentUserNames(); + log.error("hasSuspiciousDisputeDetected() " + hasSuspiciousDisputeDetected()); + } + + public boolean hasSuspiciousDisputeDetected() { + return !accountsUsingMultipleNames.isEmpty(); + } + + public String getAccountsUsingMultipleNamesAsString() { + return accountsUsingMultipleNames.entrySet().stream() + .map(entry -> { + String pubKeyHash = entry.getKey(); + String accountInfo = entry.getValue().stream() + .map(info -> { + String tradeId = info.getDispute().getShortTradeId(); + String holderName = info.getPayloadWithHolderName().getHolderName(); + return " Account owner name: '" + holderName + + "'; Trade ID: '" + tradeId + + "'; Address: '" + info.getAddress() + + "'; Payment method: '" + Res.get(info.getPaymentAccountPayload().getPaymentMethodId()) + + "'; Role: " + (info.isBuyer() ? "'Buyer'" : "'Seller'"); + + }) + .collect(Collectors.joining("\n")); + return "Trader with multiple identities:\n" + + accountInfo; + }) + .collect(Collectors.joining("\n\n")); + } + + public void addListener(Listener listener) { + listeners.add(listener); + } + + public void removeListener(Listener listener) { + listeners.remove(listener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void buildRealNameAccountMaps() { + buyerRealNameAccountByAddressMap.clear(); + sellerRealNameAccountByAddressMap.clear(); + disputeManager.getDisputesAsObservableList() + .forEach(dispute -> { + Contract contract = dispute.getContract(); + PubKeyRing traderPubKeyRing = dispute.getTraderPubKeyRing(); + String traderPubKeyHash = getTraderPuKeyHash(traderPubKeyRing); + String buyerPubKeyHash = getTraderPuKeyHash(contract.getBuyerPubKeyRing()); + boolean isBuyer = contract.isMyRoleBuyer(traderPubKeyRing); + + if (buyerPubKeyHash.equals(traderPubKeyHash)) { + PaymentAccountPayload buyerPaymentAccountPayload = contract.getBuyerPaymentAccountPayload(); + String buyersAddress = contract.getBuyerNodeAddress().getFullAddress(); + addToMap(traderPubKeyHash, buyerRealNameAccountByAddressMap, buyerPaymentAccountPayload, buyersAddress, dispute, isBuyer); + } else { + PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); + String sellerAddress = contract.getSellerNodeAddress().getFullAddress(); + addToMap(traderPubKeyHash, sellerRealNameAccountByAddressMap, sellerPaymentAccountPayload, sellerAddress, dispute, isBuyer); + } + }); + } + + private String getTraderPuKeyHash(PubKeyRing pubKeyRing) { + return Utilities.encodeToHex(Hash.getRipemd160hash(pubKeyRing.toProtoMessage().toByteArray())); + } + + private void addToMap(String pubKeyHash, Map> map, + PaymentAccountPayload paymentAccountPayload, + String address, + Dispute dispute, + boolean isBuyer) { + if (paymentAccountPayload instanceof PayloadWithHolderName) { + map.putIfAbsent(pubKeyHash, new ArrayList<>()); + RealNameAccountInfo info = new RealNameAccountInfo(address, + (PayloadWithHolderName) paymentAccountPayload, + paymentAccountPayload, + dispute, + isBuyer); + map.get(pubKeyHash).add(info); + } + } + + private void detectUsageOfDifferentUserNames() { + detectUsageOfDifferentUserNames(buyerRealNameAccountByAddressMap); + detectUsageOfDifferentUserNames(sellerRealNameAccountByAddressMap); + } + + private void detectUsageOfDifferentUserNames(Map> map) { + String previous = accountsUsingMultipleNames.toString(); + map.forEach((key, value) -> { + Set userNames = value.stream() + .map(info -> info.getPayloadWithHolderName().getHolderName()) + .collect(Collectors.toSet()); + if (userNames.size() > 1) { + accountsUsingMultipleNames.put(key, value); + } + }); + String updated = accountsUsingMultipleNames.toString(); + if (!previous.equals(updated)) { + listeners.forEach(Listener::onSuspiciousDisputeDetected); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Static class + /////////////////////////////////////////////////////////////////////////////////////////// + + @Value + private static class RealNameAccountInfo { + private final String address; + private final PayloadWithHolderName payloadWithHolderName; + private final Dispute dispute; + private final boolean isBuyer; + private final PaymentAccountPayload paymentAccountPayload; + + RealNameAccountInfo(String address, + PayloadWithHolderName payloadWithHolderName, + PaymentAccountPayload paymentAccountPayload, + Dispute dispute, + boolean isBuyer) { + this.address = address; + this.payloadWithHolderName = payloadWithHolderName; + this.paymentAccountPayload = paymentAccountPayload; + this.dispute = dispute; + this.isBuyer = isBuyer; + } + } +} diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index 90e24fc0c47..114f272eda1 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -816,6 +816,11 @@ tree-table-view:focused { -fx-padding: 27 2 0 2; } +.alert-icon { + -fx-fill: -bs-rd-error-red; + -fx-cursor: hand; +} + .close-icon { -fx-fill: -bs-text-color; } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index 6bf28f446cb..901ef5ea476 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -60,6 +60,8 @@ import com.google.common.collect.Lists; +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; @@ -72,6 +74,7 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javafx.scene.text.Text; import javafx.geometry.Insets; @@ -108,6 +111,8 @@ import javax.annotation.Nullable; +import static bisq.desktop.util.FormBuilder.getIconForLabel; + public abstract class DisputeView extends ActivatableView { protected final DisputeManager> disputeManager; @@ -142,6 +147,7 @@ public abstract class DisputeView extends ActivatableView { private Map> disputeChatMessagesListeners = new HashMap<>(); @Nullable private ListChangeListener disputesListener; // Only set in mediation cases + protected Label alertIconLabel; /////////////////////////////////////////////////////////////////////////////////////////// @@ -180,6 +186,14 @@ public void initialize() { filterTextFieldListener = (observable, oldValue, newValue) -> applyFilteredListPredicate(filterTextField.getText()); HBox.setHgrow(filterTextField, Priority.NEVER); + alertIconLabel = new Label(); + Text icon = getIconForLabel(MaterialDesignIcon.ALERT_CIRCLE_OUTLINE, "2em", alertIconLabel); + icon.getStyleClass().add("alert-icon"); + HBox.setMargin(alertIconLabel, new Insets(4, 0, 0, 10)); + alertIconLabel.setMouseTransparent(false); + alertIconLabel.setVisible(false); + alertIconLabel.setManaged(false); + reOpenButton = new AutoTooltipButton(Res.get("support.reOpenButton.label")); reOpenButton.setDisable(true); reOpenButton.setVisible(false); @@ -219,7 +233,14 @@ public void initialize() { filterBox = new HBox(); filterBox.setSpacing(5); - filterBox.getChildren().addAll(label, filterTextField, spacer, reOpenButton, sendPrivateNotificationButton, reportButton, fullReportButton); + filterBox.getChildren().addAll(label, + filterTextField, + alertIconLabel, + spacer, + reOpenButton, + sendPrivateNotificationButton, + reportButton, + fullReportButton); VBox.setVgrow(filterBox, Priority.NEVER); tableView = new TableView<>(); diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index 582c461b087..f7a04d89aef 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -18,6 +18,7 @@ package bisq.desktop.main.support.dispute.agent; import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.ContractWindow; import bisq.desktop.main.overlays.windows.DisputeSummaryWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow; @@ -30,14 +31,19 @@ import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeSession; +import bisq.core.support.dispute.agent.FraudDetection; import bisq.core.trade.TradeManager; import bisq.core.util.coin.CoinFormatter; import bisq.common.crypto.KeyRing; +import bisq.common.util.Utilities; import javafx.scene.control.Button; +import javafx.scene.control.Tooltip; -public abstract class DisputeAgentView extends DisputeView { +public abstract class DisputeAgentView extends DisputeView implements FraudDetection.Listener { + + private final FraudDetection fraudDetection; public DisputeAgentView(DisputeManager> disputeManager, KeyRing keyRing, @@ -59,6 +65,8 @@ public DisputeAgentView(DisputeManager new Popup() + .width(1100) + .warning("You have dispute cases where traders used different account holder names.\n\n" + + "This might be not critical in case of small variations of the same name " + + "(e.g. first name and last name are swapped), " + + "but if the name is different you should request information from the trader why they " + + "used a different name and request proof that the person with the real name is aware " + + "of the trade. " + + "It can be that the trader uses the account of their wife/husband, but it also could " + + "be a case of a stolen bank account or money laundering.\n\n" + + "Please check below the list of the names which got detected. Search with the trade ID for " + + "the dispute case for evaluating if it might be a fraudulent account. If so, please notify the " + + "developers and provide the contract json data to them so they can ban those traders.\n\n" + + accountsUsingMultipleNamesList) + .actionButtonText(Res.get("shared.copyToClipboard")) + .onAction(() -> Utilities.copyToClipboard(accountsUsingMultipleNamesList)) + .show()); + } } From 67e90f908a77a37a5f7f0de78a980815e1e753b1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 07:41:27 -0500 Subject: [PATCH 023/108] Remove unused var --- .../java/bisq/core/support/dispute/agent/FraudDetection.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java index 901a739c347..aed9c54391e 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java @@ -91,7 +91,6 @@ public boolean hasSuspiciousDisputeDetected() { public String getAccountsUsingMultipleNamesAsString() { return accountsUsingMultipleNames.entrySet().stream() .map(entry -> { - String pubKeyHash = entry.getKey(); String accountInfo = entry.getValue().stream() .map(info -> { String tradeId = info.getDispute().getShortTradeId(); From 9cc614a7907b23de919cc39933a902b34166d5b2 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 17:39:22 -0500 Subject: [PATCH 024/108] Add alert icon to list entries Support agent can mark a suspicious dispute as resolved so it does not show the alert icon anymore. In the full report a [ACK] got added to that dispute. --- .../support/dispute/agent/FraudDetection.java | 214 -------------- .../agent/MultipleHolderNameDetection.java | 270 ++++++++++++++++++ .../core/support/messages/ChatMessage.java | 2 +- .../main/support/dispute/DisputeView.java | 13 +- .../dispute/agent/DisputeAgentView.java | 195 ++++++++++--- 5 files changed, 440 insertions(+), 254 deletions(-) delete mode 100644 core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java create mode 100644 core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java diff --git a/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java deleted file mode 100644 index aed9c54391e..00000000000 --- a/core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.support.dispute.agent; - -import bisq.core.locale.Res; -import bisq.core.payment.payload.PayloadWithHolderName; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.support.dispute.Dispute; -import bisq.core.support.dispute.DisputeList; -import bisq.core.support.dispute.DisputeManager; -import bisq.core.trade.Contract; - -import bisq.common.crypto.Hash; -import bisq.common.crypto.PubKeyRing; -import bisq.common.util.Utilities; - -import javafx.collections.ListChangeListener; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.stream.Collectors; - -import lombok.Getter; -import lombok.Value; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class FraudDetection { - public interface Listener { - void onSuspiciousDisputeDetected(); - } - - private final DisputeManager> disputeManager; - private Map> buyerRealNameAccountByAddressMap = new HashMap<>(); - private Map> sellerRealNameAccountByAddressMap = new HashMap<>(); - @Getter - private Map> accountsUsingMultipleNames = new HashMap<>(); - private List listeners = new CopyOnWriteArrayList<>(); - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - public FraudDetection(DisputeManager> disputeManager) { - this.disputeManager = disputeManager; - - disputeManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> { - c.next(); - if (c.wasAdded()) { - checkForMultipleHolderNames(); - } - }); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - public void checkForMultipleHolderNames() { - log.error("checkForMultipleHolderNames"); - buildRealNameAccountMaps(); - detectUsageOfDifferentUserNames(); - log.error("hasSuspiciousDisputeDetected() " + hasSuspiciousDisputeDetected()); - } - - public boolean hasSuspiciousDisputeDetected() { - return !accountsUsingMultipleNames.isEmpty(); - } - - public String getAccountsUsingMultipleNamesAsString() { - return accountsUsingMultipleNames.entrySet().stream() - .map(entry -> { - String accountInfo = entry.getValue().stream() - .map(info -> { - String tradeId = info.getDispute().getShortTradeId(); - String holderName = info.getPayloadWithHolderName().getHolderName(); - return " Account owner name: '" + holderName + - "'; Trade ID: '" + tradeId + - "'; Address: '" + info.getAddress() + - "'; Payment method: '" + Res.get(info.getPaymentAccountPayload().getPaymentMethodId()) + - "'; Role: " + (info.isBuyer() ? "'Buyer'" : "'Seller'"); - - }) - .collect(Collectors.joining("\n")); - return "Trader with multiple identities:\n" + - accountInfo; - }) - .collect(Collectors.joining("\n\n")); - } - - public void addListener(Listener listener) { - listeners.add(listener); - } - - public void removeListener(Listener listener) { - listeners.remove(listener); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private void buildRealNameAccountMaps() { - buyerRealNameAccountByAddressMap.clear(); - sellerRealNameAccountByAddressMap.clear(); - disputeManager.getDisputesAsObservableList() - .forEach(dispute -> { - Contract contract = dispute.getContract(); - PubKeyRing traderPubKeyRing = dispute.getTraderPubKeyRing(); - String traderPubKeyHash = getTraderPuKeyHash(traderPubKeyRing); - String buyerPubKeyHash = getTraderPuKeyHash(contract.getBuyerPubKeyRing()); - boolean isBuyer = contract.isMyRoleBuyer(traderPubKeyRing); - - if (buyerPubKeyHash.equals(traderPubKeyHash)) { - PaymentAccountPayload buyerPaymentAccountPayload = contract.getBuyerPaymentAccountPayload(); - String buyersAddress = contract.getBuyerNodeAddress().getFullAddress(); - addToMap(traderPubKeyHash, buyerRealNameAccountByAddressMap, buyerPaymentAccountPayload, buyersAddress, dispute, isBuyer); - } else { - PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); - String sellerAddress = contract.getSellerNodeAddress().getFullAddress(); - addToMap(traderPubKeyHash, sellerRealNameAccountByAddressMap, sellerPaymentAccountPayload, sellerAddress, dispute, isBuyer); - } - }); - } - - private String getTraderPuKeyHash(PubKeyRing pubKeyRing) { - return Utilities.encodeToHex(Hash.getRipemd160hash(pubKeyRing.toProtoMessage().toByteArray())); - } - - private void addToMap(String pubKeyHash, Map> map, - PaymentAccountPayload paymentAccountPayload, - String address, - Dispute dispute, - boolean isBuyer) { - if (paymentAccountPayload instanceof PayloadWithHolderName) { - map.putIfAbsent(pubKeyHash, new ArrayList<>()); - RealNameAccountInfo info = new RealNameAccountInfo(address, - (PayloadWithHolderName) paymentAccountPayload, - paymentAccountPayload, - dispute, - isBuyer); - map.get(pubKeyHash).add(info); - } - } - - private void detectUsageOfDifferentUserNames() { - detectUsageOfDifferentUserNames(buyerRealNameAccountByAddressMap); - detectUsageOfDifferentUserNames(sellerRealNameAccountByAddressMap); - } - - private void detectUsageOfDifferentUserNames(Map> map) { - String previous = accountsUsingMultipleNames.toString(); - map.forEach((key, value) -> { - Set userNames = value.stream() - .map(info -> info.getPayloadWithHolderName().getHolderName()) - .collect(Collectors.toSet()); - if (userNames.size() > 1) { - accountsUsingMultipleNames.put(key, value); - } - }); - String updated = accountsUsingMultipleNames.toString(); - if (!previous.equals(updated)) { - listeners.forEach(Listener::onSuspiciousDisputeDetected); - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Static class - /////////////////////////////////////////////////////////////////////////////////////////// - - @Value - private static class RealNameAccountInfo { - private final String address; - private final PayloadWithHolderName payloadWithHolderName; - private final Dispute dispute; - private final boolean isBuyer; - private final PaymentAccountPayload paymentAccountPayload; - - RealNameAccountInfo(String address, - PayloadWithHolderName payloadWithHolderName, - PaymentAccountPayload paymentAccountPayload, - Dispute dispute, - boolean isBuyer) { - this.address = address; - this.payloadWithHolderName = payloadWithHolderName; - this.paymentAccountPayload = paymentAccountPayload; - this.dispute = dispute; - this.isBuyer = isBuyer; - } - } -} diff --git a/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java new file mode 100644 index 00000000000..48025ae2669 --- /dev/null +++ b/core/src/main/java/bisq/core/support/dispute/agent/MultipleHolderNameDetection.java @@ -0,0 +1,270 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.support.dispute.agent; + +import bisq.core.locale.Res; +import bisq.core.payment.payload.PayloadWithHolderName; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.support.dispute.Dispute; +import bisq.core.support.dispute.DisputeList; +import bisq.core.support.dispute.DisputeManager; +import bisq.core.support.dispute.DisputeResult; +import bisq.core.trade.Contract; +import bisq.core.user.DontShowAgainLookup; + +import bisq.common.crypto.Hash; +import bisq.common.crypto.PubKeyRing; +import bisq.common.util.Tuple2; +import bisq.common.util.Utilities; + +import javafx.collections.ListChangeListener; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Detects traders who had disputes where they used different account holder names. Only payment methods where a + * real name is required are used for the check. + * Strings are not translated here as it is only visible to dispute agents + */ +@Slf4j +public class MultipleHolderNameDetection { + + /////////////////////////////////////////////////////////////////////////////////////////// + // Listener + /////////////////////////////////////////////////////////////////////////////////////////// + + public interface Listener { + void onSuspiciousDisputeDetected(); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Static + /////////////////////////////////////////////////////////////////////////////////////////// + + private static final String ACK_KEY = "Ack-"; + + private static String getSigPuKeyHashAsHex(PubKeyRing pubKeyRing) { + return Utilities.encodeToHex(Hash.getRipemd160hash(pubKeyRing.getSignaturePubKeyBytes())); + } + + private static String getSigPubKeyHashAsHex(Dispute dispute) { + return getSigPuKeyHashAsHex(dispute.getTraderPubKeyRing()); + } + + private static boolean isBuyer(Dispute dispute) { + String traderSigPubKeyHashAsHex = getSigPubKeyHashAsHex(dispute); + String buyerSigPubKeyHashAsHex = getSigPuKeyHashAsHex(dispute.getContract().getBuyerPubKeyRing()); + return buyerSigPubKeyHashAsHex.equals(traderSigPubKeyHashAsHex); + } + + private static PayloadWithHolderName getPayloadWithHolderName(Dispute dispute) { + return (PayloadWithHolderName) getPaymentAccountPayload(dispute); + } + + public static PaymentAccountPayload getPaymentAccountPayload(Dispute dispute) { + return isBuyer(dispute) ? + dispute.getContract().getBuyerPaymentAccountPayload() : + dispute.getContract().getSellerPaymentAccountPayload(); + } + + public static String getAddress(Dispute dispute) { + return isBuyer(dispute) ? + dispute.getContract().getBuyerNodeAddress().getHostName() : + dispute.getContract().getSellerNodeAddress().getHostName(); + } + + public static String getAckKey(Dispute dispute) { + return ACK_KEY + getSigPubKeyHashAsHex(dispute).substring(0, 4) + "/" + dispute.getShortTradeId(); + } + + private static String getIsBuyerSubString(boolean isBuyer) { + return "'\n Role: " + (isBuyer ? "'Buyer'" : "'Seller'"); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Class fields + /////////////////////////////////////////////////////////////////////////////////////////// + + private final DisputeManager> disputeManager; + + // Key is hex of hash of sig pubKey which we consider a trader identity. We could use onion address as well but + // once we support multiple onion addresses that would not work anymore. + @Getter + private Map> suspiciousDisputesByTraderMap = new HashMap<>(); + private List listeners = new CopyOnWriteArrayList<>(); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public MultipleHolderNameDetection(DisputeManager> disputeManager) { + this.disputeManager = disputeManager; + + disputeManager.getDisputesAsObservableList().addListener((ListChangeListener) c -> { + c.next(); + if (c.wasAdded()) { + detectMultipleHolderNames(); + } + }); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void detectMultipleHolderNames() { + String previous = suspiciousDisputesByTraderMap.toString(); + getAllDisputesByTraderMap().forEach((key, value) -> { + Set userNames = value.stream() + .map(dispute -> getPayloadWithHolderName(dispute).getHolderName()) + .collect(Collectors.toSet()); + if (userNames.size() > 1) { + // As we compare previous results we need to make sorting deterministic + value.sort(Comparator.comparing(Dispute::getId)); + suspiciousDisputesByTraderMap.put(key, value); + } + }); + String updated = suspiciousDisputesByTraderMap.toString(); + if (!previous.equals(updated)) { + listeners.forEach(Listener::onSuspiciousDisputeDetected); + } + } + + public boolean hasSuspiciousDisputesDetected() { + return !suspiciousDisputesByTraderMap.isEmpty(); + } + + // Returns all disputes of a trader who used multiple names + public List getDisputesForTrader(Dispute dispute) { + String traderPubKeyHash = getSigPubKeyHashAsHex(dispute); + if (suspiciousDisputesByTraderMap.containsKey(traderPubKeyHash)) { + return suspiciousDisputesByTraderMap.get(traderPubKeyHash); + } + return new ArrayList<>(); + } + + // Get a report of traders who used multiple names with all their disputes listed + public String getReportForAllDisputes() { + return getReport(suspiciousDisputesByTraderMap.values()); + } + + // Get a report for a trader who used multiple names with all their disputes listed + public String getReportForDisputeOfTrader(List disputes) { + Collection> values = new ArrayList<>(); + values.add(disputes); + return getReport(values); + } + + public void addListener(Listener listener) { + listeners.add(listener); + } + + public void removeListener(Listener listener) { + listeners.remove(listener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private Map> getAllDisputesByTraderMap() { + Map> allDisputesByTraderMap = new HashMap<>(); + disputeManager.getDisputesAsObservableList() + .forEach(dispute -> { + Contract contract = dispute.getContract(); + PaymentAccountPayload paymentAccountPayload = isBuyer(dispute) ? + contract.getBuyerPaymentAccountPayload() : + contract.getSellerPaymentAccountPayload(); + if (paymentAccountPayload instanceof PayloadWithHolderName) { + String traderPubKeyHash = getSigPubKeyHashAsHex(dispute); + allDisputesByTraderMap.putIfAbsent(traderPubKeyHash, new ArrayList<>()); + List disputes = allDisputesByTraderMap.get(traderPubKeyHash); + disputes.add(dispute); + } + }); + return allDisputesByTraderMap; + } + + // Get a text report for a trader who used multiple names and list all the his disputes + private String getReport(Collection> collectionOfDisputesOfTrader) { + return collectionOfDisputesOfTrader.stream() + .map(disputes -> { + Set addresses = new HashSet<>(); + Set isBuyerHashSet = new HashSet<>(); + Set names = new HashSet<>(); + String disputesReport = disputes.stream() + .map(dispute -> { + addresses.add(getAddress(dispute)); + String ackKey = getAckKey(dispute); + String ackSubString = " "; + if (!DontShowAgainLookup.showAgain(ackKey)) { + ackSubString = "[ACK] "; + } + String holderName = getPayloadWithHolderName(dispute).getHolderName(); + names.add(holderName); + boolean isBuyer = isBuyer(dispute); + isBuyerHashSet.add(isBuyer); + String isBuyerSubString = getIsBuyerSubString(isBuyer); + DisputeResult disputeResult = dispute.disputeResultProperty().get(); + String summaryNotes = disputeResult != null ? disputeResult.getSummaryNotesProperty().get().trim() : "Not closed yet"; + return ackSubString + + "Trade ID: '" + dispute.getShortTradeId() + + "'\n Account holder name: '" + holderName + + "'\n Payment method: '" + Res.get(getPaymentAccountPayload(dispute).getPaymentMethodId()) + + isBuyerSubString + + "'\n Summary: '" + summaryNotes; + }) + .collect(Collectors.joining("\n")); + + String addressSubString = addresses.size() > 1 ? + "used multiple addresses " + addresses + " with" : + "with address " + new ArrayList<>(addresses).get(0) + " used"; + + String roleSubString = "Trader "; + if (isBuyerHashSet.size() == 1) { + boolean isBuyer = new ArrayList<>(isBuyerHashSet).get(0); + String isBuyerSubString = getIsBuyerSubString(isBuyer); + disputesReport = disputesReport.replace(isBuyerSubString, ""); + roleSubString = isBuyer ? "Buyer " : "Seller "; + } + + + String traderReport = roleSubString + addressSubString + " multiple names: " + names.toString() + "\n" + disputesReport; + return new Tuple2<>(roleSubString, traderReport); + }) + .sorted(Comparator.comparing(o -> o.first)) // Buyers first, then seller, then mixed (trader was in seller and buyer role) + .map(e -> e.second) + .collect(Collectors.joining("\n\n")); + } +} diff --git a/core/src/main/java/bisq/core/support/messages/ChatMessage.java b/core/src/main/java/bisq/core/support/messages/ChatMessage.java index a4e328ec390..b798a374639 100644 --- a/core/src/main/java/bisq/core/support/messages/ChatMessage.java +++ b/core/src/main/java/bisq/core/support/messages/ChatMessage.java @@ -51,7 +51,7 @@ import javax.annotation.Nullable; /* Message for direct communication between two nodes. Originally built for trader to - * arbitrator communication as no other direct communication was allowed. Aribtrator is + * arbitrator communication as no other direct communication was allowed. Arbitrator is * considered as the server and trader as the client in arbitration chats * * For trader to trader communication the maker is considered to be the server diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index 901ef5ea476..251727e43ed 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -127,7 +127,7 @@ public abstract class DisputeView extends ActivatableView { private final AccountAgeWitnessService accountAgeWitnessService; private final boolean useDevPrivilegeKeys; - private TableView tableView; + protected TableView tableView; private SortedList sortedList; @Getter @@ -142,12 +142,12 @@ public abstract class DisputeView extends ActivatableView { protected FilteredList filteredList; protected InputTextField filterTextField; private ChangeListener filterTextFieldListener; - private HBox filterBox; protected AutoTooltipButton reOpenButton, sendPrivateNotificationButton, reportButton, fullReportButton; private Map> disputeChatMessagesListeners = new HashMap<>(); @Nullable private ListChangeListener disputesListener; // Only set in mediation cases protected Label alertIconLabel; + protected TableColumn stateColumn; /////////////////////////////////////////////////////////////////////////////////////////// @@ -231,7 +231,7 @@ public void initialize() { Pane spacer = new Pane(); HBox.setHgrow(spacer, Priority.ALWAYS); - filterBox = new HBox(); + HBox filterBox = new HBox(); filterBox.setSpacing(5); filterBox.getChildren().addAll(label, filterTextField, @@ -419,7 +419,7 @@ protected void onCloseDispute(Dispute dispute) { } } - protected void handleKeyPressed(KeyEvent event) { + private void handleKeyPressed(KeyEvent event) { } protected void reOpenDispute() { @@ -733,7 +733,7 @@ private void showFullReport() { // Table /////////////////////////////////////////////////////////////////////////////////////////// - private void setupTable() { + protected void setupTable() { tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); Label placeholder = new AutoTooltipLabel(Res.get("support.noTickets")); placeholder.setWrapText(true); @@ -764,7 +764,7 @@ private void setupTable() { TableColumn roleColumn = getRoleColumn(); tableView.getColumns().add(roleColumn); - TableColumn stateColumn = getStateColumn(); + stateColumn = getStateColumn(); tableView.getColumns().add(stateColumn); tradeIdColumn.setComparator(Comparator.comparing(Dispute::getTradeId)); @@ -1120,7 +1120,6 @@ public void updateItem(final Dispute item, boolean empty) { }); return column; } - } diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java index f7a04d89aef..13e52608678 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/agent/DisputeAgentView.java @@ -18,6 +18,7 @@ package bisq.desktop.main.support.dispute.agent; import bisq.desktop.components.AutoTooltipButton; +import bisq.desktop.components.AutoTooltipTableColumn; import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.windows.ContractWindow; import bisq.desktop.main.overlays.windows.DisputeSummaryWindow; @@ -31,19 +32,35 @@ import bisq.core.support.dispute.DisputeList; import bisq.core.support.dispute.DisputeManager; import bisq.core.support.dispute.DisputeSession; -import bisq.core.support.dispute.agent.FraudDetection; +import bisq.core.support.dispute.agent.MultipleHolderNameDetection; import bisq.core.trade.TradeManager; +import bisq.core.user.DontShowAgainLookup; import bisq.core.util.coin.CoinFormatter; import bisq.common.crypto.KeyRing; import bisq.common.util.Utilities; +import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; + import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; + +import javafx.geometry.Insets; + +import javafx.beans.property.ReadOnlyObjectWrapper; + +import java.util.List; + +import static bisq.desktop.util.FormBuilder.getIconForLabel; -public abstract class DisputeAgentView extends DisputeView implements FraudDetection.Listener { +public abstract class DisputeAgentView extends DisputeView implements MultipleHolderNameDetection.Listener { - private final FraudDetection fraudDetection; + private final MultipleHolderNameDetection multipleHolderNameDetection; public DisputeAgentView(DisputeManager> disputeManager, KeyRing keyRing, @@ -66,9 +83,14 @@ public DisputeAgentView(DisputeManager { @@ -130,34 +167,128 @@ protected void handleOnSelectDispute(Dispute dispute) { } @Override - public void onSuspiciousDisputeDetected() { - showAlertIcon(); + protected void setupTable() { + super.setupTable(); + + stateColumn.getStyleClass().remove("last-column"); + tableView.getColumns().add(getAlertColumn()); } - private void showAlertIcon() { - String accountsUsingMultipleNamesList = fraudDetection.getAccountsUsingMultipleNamesAsString(); + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + private void suspiciousDisputeDetected() { alertIconLabel.setVisible(true); alertIconLabel.setManaged(true); - alertIconLabel.setTooltip(new Tooltip("You have disputes where user used different " + - "real life names from the same application. Click for more details.")); + alertIconLabel.setTooltip(new Tooltip("You have suspicious disputes where the same trader used different " + + "account holder names.\nClick for more information.")); // Text below is for arbitrators only so no need to translate it - alertIconLabel.setOnMouseClicked(e -> new Popup() - .width(1100) - .warning("You have dispute cases where traders used different account holder names.\n\n" + - "This might be not critical in case of small variations of the same name " + - "(e.g. first name and last name are swapped), " + - "but if the name is different you should request information from the trader why they " + - "used a different name and request proof that the person with the real name is aware " + - "of the trade. " + - "It can be that the trader uses the account of their wife/husband, but it also could " + - "be a case of a stolen bank account or money laundering.\n\n" + - "Please check below the list of the names which got detected. Search with the trade ID for " + - "the dispute case for evaluating if it might be a fraudulent account. If so, please notify the " + - "developers and provide the contract json data to them so they can ban those traders.\n\n" + - accountsUsingMultipleNamesList) - .actionButtonText(Res.get("shared.copyToClipboard")) - .onAction(() -> Utilities.copyToClipboard(accountsUsingMultipleNamesList)) - .show()); + alertIconLabel.setOnMouseClicked(e -> { + String reportForAllDisputes = multipleHolderNameDetection.getReportForAllDisputes(); + new Popup() + .width(1100) + .warning(getReportMessage(reportForAllDisputes, "traders")) + .actionButtonText(Res.get("shared.copyToClipboard")) + .onAction(() -> Utilities.copyToClipboard(reportForAllDisputes)) + .show(); + }); + } + + + private TableColumn getAlertColumn() { + TableColumn column = new AutoTooltipTableColumn<>("Alert") { + { + setMinWidth(50); + } + }; + column.getStyleClass().add("last-column"); + column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); + column.setCellFactory( + c -> new TableCell<>() { + Label alertIconLabel; + + @Override + public void updateItem(Dispute dispute, boolean empty) { + if (dispute != null && !empty) { + if (!showAlertAtDispute(dispute)) { + setGraphic(null); + if (alertIconLabel != null) { + alertIconLabel.setOnMouseClicked(null); + } + return; + } + + if (alertIconLabel != null) { + alertIconLabel.setOnMouseClicked(null); + } + + alertIconLabel = new Label(); + Text icon = getIconForLabel(MaterialDesignIcon.ALERT_CIRCLE_OUTLINE, "1.5em", alertIconLabel); + icon.getStyleClass().add("alert-icon"); + HBox.setMargin(alertIconLabel, new Insets(4, 0, 0, 10)); + alertIconLabel.setMouseTransparent(false); + setGraphic(alertIconLabel); + + alertIconLabel.setOnMouseClicked(e -> { + List realNameAccountInfoList = multipleHolderNameDetection.getDisputesForTrader(dispute); + String reportForDisputeOfTrader = multipleHolderNameDetection.getReportForDisputeOfTrader(realNameAccountInfoList); + String key = MultipleHolderNameDetection.getAckKey(dispute); + new Popup() + .width(1100) + .warning(getReportMessage(reportForDisputeOfTrader, "this trader")) + .actionButtonText(Res.get("shared.copyToClipboard")) + .onAction(() -> { + Utilities.copyToClipboard(reportForDisputeOfTrader); + if (!DontShowAgainLookup.showAgain(key)) { + setGraphic(null); + } + }) + .dontShowAgainId(key) + .dontShowAgainText("Is not suspicious") + .onClose(() -> { + if (!DontShowAgainLookup.showAgain(key)) { + setGraphic(null); + } + }) + .show(); + }); + } else { + setGraphic(null); + if (alertIconLabel != null) { + alertIconLabel.setOnMouseClicked(null); + } + } + } + }); + + column.setComparator((o1, o2) -> Boolean.compare(showAlertAtDispute(o1), showAlertAtDispute(o2))); + column.setSortable(true); + return column; + } + + private boolean showAlertAtDispute(Dispute dispute) { + return DontShowAgainLookup.showAgain(MultipleHolderNameDetection.getAckKey(dispute)) && + !multipleHolderNameDetection.getDisputesForTrader(dispute).isEmpty(); + } + + private String getReportMessage(String report, String subString) { + return "You have dispute cases where " + subString + " used different account holder names.\n\n" + + "This might be not critical in case of small variations of the same name " + + "(e.g. first name and last name are swapped), " + + "but if the name is completely different you should request information from the trader why they " + + "used a different name and request proof that the person with the real name is aware " + + "of the trade. " + + "It can be that the trader uses the account of their wife/husband, but it also could " + + "be a case of a stolen bank account or money laundering.\n\n" + + "Please check below the list of the names which have been detected. " + + "Search with the trade ID for the dispute case or check out the alert icon at each dispute in " + + "the list (you might need to remove the 'open' filter) and evaluate " + + "if it might be a fraudulent account (buyer role is more likely to be fraudulent). " + + "If you find suspicious disputes, please notify the developers and provide the contract json data " + + "to them so they can ban those traders.\n\n" + + Utilities.toTruncatedString(report, 700, false); } } From e080b06736b6e03aa83dfd63b2812e49ef81e725 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 17:54:32 -0500 Subject: [PATCH 025/108] Remove unused method --- .../main/support/dispute/DisputeView.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java index 251727e43ed..fcba3c3b652 100644 --- a/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java +++ b/desktop/src/main/java/bisq/desktop/main/support/dispute/DisputeView.java @@ -62,14 +62,12 @@ import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon; -import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.Tooltip; -import javafx.scene.input.KeyEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.Priority; @@ -85,8 +83,6 @@ import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.value.ChangeListener; -import javafx.event.EventHandler; - import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; @@ -137,8 +133,6 @@ public abstract class DisputeView extends ActivatableView { private ChangeListener selectedDisputeClosedPropertyListener; private Subscription selectedDisputeSubscription; - private EventHandler keyEventEventHandler; - private Scene scene; protected FilteredList filteredList; protected InputTextField filterTextField; private ChangeListener filterTextFieldListener; @@ -253,8 +247,6 @@ public void initialize() { selectedDisputeClosedPropertyListener = (observable, oldValue, newValue) -> chatView.setInputBoxVisible(!newValue); - keyEventEventHandler = this::handleKeyPressed; - chatView = new ChatView(disputeManager, formatter); chatView.initialize(); } @@ -284,9 +276,6 @@ else if (sortedList.size() > 0) chatView.scrollToBottom(); } - scene = root.getScene(); - if (scene != null) - scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); // If doPrint=true we print out a html page which opens tabs with all deposit txs // (firefox needs about:config change to allow > 20 tabs) @@ -345,9 +334,6 @@ protected void deactivate() { selectedDisputeSubscription.unsubscribe(); removeListenersOnSelectDispute(); - if (scene != null) - scene.removeEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); - if (chatView != null) chatView.deactivate(); } @@ -419,9 +405,6 @@ protected void onCloseDispute(Dispute dispute) { } } - private void handleKeyPressed(KeyEvent event) { - } - protected void reOpenDispute() { if (selectedDispute != null) { selectedDispute.setIsClosed(false); From 05353bb43dc2e28ff1fff78915a141cacf3c5b25 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 4 Sep 2020 14:50:53 -0500 Subject: [PATCH 026/108] Add JsonExclude to userName For backward compatibility we need to exclude the new field for the contract json. We can remove that after a while when risk that users with pre 1.3.8 version trade with updated users is very low. --- .../bisq/core/payment/payload/RevolutAccountPayload.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java index 7c8926ab105..7de3c58203c 100644 --- a/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java @@ -20,6 +20,7 @@ import bisq.core.locale.Res; import bisq.common.proto.ProtoUtil; +import bisq.common.util.JsonExclude; import com.google.protobuf.Message; @@ -48,6 +49,10 @@ public final class RevolutAccountPayload extends PaymentAccountPayload { // left unchanged. Newly created accounts fill accountId with the value of userName. // In the UI we only use userName. @Nullable + // For backward compatibility we need to exclude the new field for the contract json. + // We can remove that after a while when risk that users with pre 1.3.8 version trade with updated + // users is very low. + @JsonExclude private String userName = null; public RevolutAccountPayload(String paymentMethod, String id) { From a4f6d2af2e1b0d66a133b0f9c713f9df40637e79 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 4 Sep 2020 14:51:34 -0500 Subject: [PATCH 027/108] Add check if xmrTxProof is used to avoid null pointer exception --- .../steps/seller/SellerStep3View.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index b0e0fa57c22..afa68484e3a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -77,7 +77,6 @@ public class SellerStep3View extends TradeStepView { - private final ChangeListener proofResultListener; private Button confirmButton; private Label statusLabel; private BusyAnimation busyAnimation; @@ -85,6 +84,8 @@ public class SellerStep3View extends TradeStepView { private Timer timeoutTimer; private InfoTextField assetTxProofResultField; private TxConfidenceIndicator assetTxConfidenceIndicator; + private ChangeListener proofResultListener; + private boolean useXmrTxProof; /////////////////////////////////////////////////////////////////////////////////////////// @@ -93,10 +94,6 @@ public class SellerStep3View extends TradeStepView { public SellerStep3View(PendingTradesViewModel model) { super(model); - - proofResultListener = (observable, oldValue, newValue) -> { - applyAssetTxProofResult(trade.getAssetTxProofResult()); - }; } @Override @@ -157,13 +154,19 @@ public void activate() { } }); - // we listen for updates on the trade autoConfirmResult field - if (assetTxProofResultField != null) { - trade.getAssetTxProofResultUpdateProperty().addListener(proofResultListener); + if (useXmrTxProof) { + proofResultListener = (observable, oldValue, newValue) -> { + applyAssetTxProofResult(trade.getAssetTxProofResult()); + }; + + // we listen for updates on the trade autoConfirmResult field + if (assetTxProofResultField != null) { + trade.getAssetTxProofResultUpdateProperty().addListener(proofResultListener); + applyAssetTxProofResult(trade.getAssetTxProofResult()); + } + applyAssetTxProofResult(trade.getAssetTxProofResult()); } - - applyAssetTxProofResult(trade.getAssetTxProofResult()); } @Override @@ -181,7 +184,7 @@ public void deactivate() { timeoutTimer.stop(); } - if (assetTxProofResultField != null) { + if (useXmrTxProof && assetTxProofResultField != null) { trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener); } } @@ -235,7 +238,8 @@ protected void addContent() { GridPane.setRowSpan(titledGroupBg, 4); } - if (isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR")) { + useXmrTxProof = isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR"); + if (useXmrTxProof) { assetTxProofResultField = new InfoTextField(); Tuple2 topLabelWithVBox = getTopLabelWithVBox(Res.get("portfolio.pending.step3_seller.autoConf.status.label"), assetTxProofResultField); From 1a2b17b1cd01df645e3b102c1790af1e4633bc87 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 4 Sep 2020 18:20:16 -0500 Subject: [PATCH 028/108] Move revolutAccountsUpdateHandler code up to BisqSetup --- core/src/main/java/bisq/core/app/BisqSetup.java | 9 ++++++++- core/src/main/java/bisq/core/user/User.java | 12 ------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 2bafa44b858..164f74a04da 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -114,6 +114,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.stream.Collectors; import ch.qos.logback.classic.Level; @@ -851,7 +852,13 @@ private void initDomainServices() { priceAlert.onAllServicesInitialized(); marketAlerts.onAllServicesInitialized(); - user.onAllServicesInitialized(revolutAccountsUpdateHandler); + if (revolutAccountsUpdateHandler != null) { + revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream() + .filter(paymentAccount -> paymentAccount instanceof RevolutAccount) + .map(paymentAccount -> (RevolutAccount) paymentAccount) + .filter(RevolutAccount::userNameNotSet) + .collect(Collectors.toList())); + } allBasicServicesInitialized = true; } diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 0f6957c83ee..75891395d22 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -24,7 +24,6 @@ import bisq.core.notifications.alerts.market.MarketAlertFilter; import bisq.core.notifications.alerts.price.PriceAlertFilter; import bisq.core.payment.PaymentAccount; -import bisq.core.payment.RevolutAccount; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.support.dispute.refund.refundagent.RefundAgent; @@ -51,7 +50,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.Consumer; import java.util.stream.Collectors; import lombok.AllArgsConstructor; @@ -128,16 +126,6 @@ public void persist() { // API /////////////////////////////////////////////////////////////////////////////////////////// - public void onAllServicesInitialized(@Nullable Consumer> resultHandler) { - if (resultHandler != null) { - resultHandler.accept(paymentAccountsAsObservable.stream() - .filter(paymentAccount -> paymentAccount instanceof RevolutAccount) - .map(paymentAccount -> (RevolutAccount) paymentAccount) - .filter(RevolutAccount::userNameNotSet) - .collect(Collectors.toList())); - } - } - @Nullable public Arbitrator getAcceptedArbitratorByAddress(NodeAddress nodeAddress) { final List acceptedArbitrators = userPayload.getAcceptedArbitrators(); From 9eb7900d51b3ce27ace17a4e58bd2814fd31d5e8 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 4 Sep 2020 18:30:08 -0500 Subject: [PATCH 029/108] Fix handling of username/accountId We apply userName to accountId if it is not set (e.g. new account created with new version). We do not use that for display or for account signing in case both fields are the same but we need to use accountId in case the user trades with a not updated user who expects accountId as only field. I improved a bit the display of account data in the trade screens. In case accountId was set with the phone number (updated account with phone nr used for account signing) we show both userName and phone nr. - Show phone number if accountId was set by old account. Otherwise show only userName - For old users they will see the user name as phone number displayed if they trade with new users if the new user has created a new account. If he has updated an existing account the accountId (phone number) is used, so it displays the phone number. - At step 2 changed display of own account data to show account name - Add 'Recipients' prefix to account data of peer at step 2 Step 3: Buyers account data can be - Phone number if peer is using old version - User name if peer is updated user with new account (we apply userName to accountId) - Phone number if user is on old version and peer is updated user with updated account (we keep accountId as phone number) - User name/Phone number if peer is updated user with updated account --- .../bisq/core/payment/PaymentAccountUtil.java | 9 ++ .../bisq/core/payment/RevolutAccount.java | 8 ++ .../payload/RevolutAccountPayload.java | 83 ++++++++++++++----- .../resources/i18n/displayStrings.properties | 4 +- .../paymentmethods/RevolutForm.java | 22 +++-- .../steps/seller/SellerStep3View.java | 46 +++++++--- 6 files changed, 131 insertions(+), 41 deletions(-) diff --git a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java index d4fe6951515..08a96db151b 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccountUtil.java @@ -20,7 +20,9 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.locale.Country; import bisq.core.offer.Offer; +import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentMethod; +import bisq.core.user.User; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -136,6 +138,13 @@ public static boolean isCryptoCurrencyAccount(PaymentAccount paymentAccount) { paymentAccount != null && paymentAccount.getPaymentMethod().equals(PaymentMethod.BLOCK_CHAINS_INSTANT)); } + public static Optional findPaymentAccount(PaymentAccountPayload paymentAccountPayload, + User user) { + return user.getPaymentAccountsAsObservable().stream(). + filter(e -> e.getPaymentAccountPayload().equals(paymentAccountPayload)) + .findAny(); + } + // TODO no code duplication found in UI code (added for API) // That is optional and set to null if not supported (AltCoins,...) /* public static String getCountryCode(PaymentAccount paymentAccount) { diff --git a/core/src/main/java/bisq/core/payment/RevolutAccount.java b/core/src/main/java/bisq/core/payment/RevolutAccount.java index 07282769f69..9e8d41496f6 100644 --- a/core/src/main/java/bisq/core/payment/RevolutAccount.java +++ b/core/src/main/java/bisq/core/payment/RevolutAccount.java @@ -44,7 +44,15 @@ public String getUserName() { return ((RevolutAccountPayload) paymentAccountPayload).getUserName(); } + public String getAccountId() { + return ((RevolutAccountPayload) paymentAccountPayload).getAccountId(); + } + public boolean userNameNotSet() { return ((RevolutAccountPayload) paymentAccountPayload).userNameNotSet(); } + + public boolean hasOldAccountId() { + return ((RevolutAccountPayload) paymentAccountPayload).hasOldAccountId(); + } } diff --git a/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java index 7de3c58203c..72548a14d48 100644 --- a/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java @@ -19,8 +19,8 @@ import bisq.core.locale.Res; -import bisq.common.proto.ProtoUtil; import bisq.common.util.JsonExclude; +import bisq.common.util.Tuple2; import com.google.protobuf.Message; @@ -28,19 +28,23 @@ import java.util.HashMap; import java.util.Map; -import java.util.Optional; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkArgument; + @EqualsAndHashCode(callSuper = true) @ToString @Slf4j public final class RevolutAccountPayload extends PaymentAccountPayload { - // Not used anymore from outside. Only used as internal Id to not break existing account witness objects + // Only used as internal Id to not break existing account witness objects + // We still show it in case it is different to the userName for additional security + @Getter private String accountId = ""; // Was added in 1.3.8 @@ -48,12 +52,13 @@ public final class RevolutAccountPayload extends PaymentAccountPayload { // Old accounts get a popup to add the new required field userName but accountId is // left unchanged. Newly created accounts fill accountId with the value of userName. // In the UI we only use userName. - @Nullable + // For backward compatibility we need to exclude the new field for the contract json. // We can remove that after a while when risk that users with pre 1.3.8 version trade with updated // users is very low. @JsonExclude - private String userName = null; + @Getter + private String userName = ""; public RevolutAccountPayload(String paymentMethod, String id) { super(paymentMethod, id); @@ -76,14 +81,14 @@ private RevolutAccountPayload(String paymentMethod, excludeFromJsonDataMap); this.accountId = accountId; - this.userName = userName; + setUserName(userName); } @Override public Message toProtoMessage() { protobuf.RevolutAccountPayload.Builder revolutBuilder = protobuf.RevolutAccountPayload.newBuilder() - .setAccountId(accountId); - Optional.ofNullable(userName).ifPresent(revolutBuilder::setUserName); + .setAccountId(accountId) + .setUserName(userName); return getPaymentAccountPayloadBuilder().setRevolutAccountPayload(revolutBuilder).build(); } @@ -93,7 +98,7 @@ public static RevolutAccountPayload fromProto(protobuf.PaymentAccountPayload pro return new RevolutAccountPayload(proto.getPaymentMethodId(), proto.getId(), revolutAccountPayload.getAccountId(), - ProtoUtil.stringOrNullFromProto(revolutAccountPayload.getUserName()), + revolutAccountPayload.getUserName(), proto.getMaxTradePeriod(), new HashMap<>(proto.getExcludeFromJsonDataMap())); } @@ -105,7 +110,34 @@ public static RevolutAccountPayload fromProto(protobuf.PaymentAccountPayload pro @Override public String getPaymentDetails() { - return Res.get(paymentMethodId) + " - " + Res.getWithCol("payment.account.userName") + " " + getUserName(); + Tuple2 tuple = getLabelValueTuple(); + return Res.get(paymentMethodId) + " - " + tuple.first + ": " + tuple.second; + } + + private Tuple2 getLabelValueTuple() { + String label; + String value; + checkArgument(!userName.isEmpty() || hasOldAccountId(), + "Either username must be set or we have an old account with accountId"); + if (!userName.isEmpty()) { + label = Res.get("payment.account.userName"); + value = userName; + + if (hasOldAccountId()) { + label += "/" + Res.get("payment.account.phoneNr"); + value += "/" + accountId; + } + } else { + label = Res.get("payment.account.phoneNr"); + value = accountId; + } + return new Tuple2<>(label, value); + } + + public Tuple2 getRecipientsAccountData() { + Tuple2 tuple = getLabelValueTuple(); + String label = Res.get("portfolio.pending.step2_buyer.recipientsAccountData", tuple.first); + return new Tuple2<>(label, tuple.second); } @Override @@ -116,23 +148,30 @@ public String getPaymentDetailsForTradePopup() { @Override public byte[] getAgeWitnessInputData() { // getAgeWitnessInputData is called at new account creation when accountId is empty string. - return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8)); + if (hasOldAccountId()) { + // If the accountId was already in place (updated user who had used accountId for account age) we keep the + // old accountId to not invalidate the existing account age witness. + return super.getAgeWitnessInputData(accountId.getBytes(StandardCharsets.UTF_8)); + + } else { + // If a new account was registered from version 1.3.8 or later we use the userName. + return super.getAgeWitnessInputData(userName.getBytes(StandardCharsets.UTF_8)); + } } - public void setUserName(@Nullable String userName) { - this.userName = userName; - // We only set accountId to userName for new accounts. Existing accounts have accountId set with email - // or phone nr. and we keep that to not break account signing. - if (accountId.isEmpty()) { - accountId = userName; - } + public boolean userNameNotSet() { + return userName.isEmpty(); } - public String getUserName() { - return userName != null ? userName : accountId; + public boolean hasOldAccountId() { + return !accountId.equals(userName); } - public boolean userNameNotSet() { - return userName == null; + public void setUserName(String userName) { + this.userName = userName; + // We need to set accountId as pre v1.3.8 clients expect the accountId field + if (accountId.isEmpty()) { + accountId = userName; + } } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 94d4495f9f8..18e56a562e5 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -637,6 +637,7 @@ portfolio.pending.step2_buyer.bank=Please go to your online banking web page and # suppress inspection "TrailingSpacesInProperty" portfolio.pending.step2_buyer.f2f=Please contact the BTC seller by the provided contact and arrange a meeting to pay {0}.\n\n portfolio.pending.step2_buyer.startPaymentUsing=Start payment using {0} +portfolio.pending.step2_buyer.recipientsAccountData=Recipients {0} portfolio.pending.step2_buyer.amountToTransfer=Amount to transfer portfolio.pending.step2_buyer.sellersAddress=Seller''s {0} address portfolio.pending.step2_buyer.buyerAccount=Your payment account to be used @@ -755,7 +756,7 @@ portfolio.pending.step3_seller.buyersAddress=Buyers {0} address portfolio.pending.step3_seller.yourAccount=Your trading account portfolio.pending.step3_seller.xmrTxHash=Transaction ID portfolio.pending.step3_seller.xmrTxKey=Transaction key -portfolio.pending.step3_seller.buyersAccount=Buyers trading account +portfolio.pending.step3_seller.buyersAccount=Buyers account data portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1} portfolio.pending.step3_seller.buyerStartedPayment.altcoin=Check for blockchain confirmations at your altcoin wallet or block explorer and confirm the payment when you have sufficient blockchain confirmations. @@ -3044,6 +3045,7 @@ payment.account=Account payment.account.no=Account no. payment.account.name=Account name payment.account.userName=User name +payment.account.phoneNr=Phone number payment.account.owner=Account owner full name payment.account.fullName=Full name (first, middle, last) payment.account.state=State/Province/Region diff --git a/desktop/src/main/java/bisq/desktop/components/paymentmethods/RevolutForm.java b/desktop/src/main/java/bisq/desktop/components/paymentmethods/RevolutForm.java index 6ad54d94333..8ed82f821e0 100644 --- a/desktop/src/main/java/bisq/desktop/components/paymentmethods/RevolutForm.java +++ b/desktop/src/main/java/bisq/desktop/components/paymentmethods/RevolutForm.java @@ -32,15 +32,20 @@ import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.InputValidator; +import bisq.common.util.Tuple2; + import javafx.scene.control.TextField; import javafx.scene.layout.FlowPane; import javafx.scene.layout.GridPane; +import lombok.extern.slf4j.Slf4j; + import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextField; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; import static bisq.desktop.util.FormBuilder.addTopLabelFlowPane; import static bisq.desktop.util.FormBuilder.addTopLabelTextField; +@Slf4j public class RevolutForm extends PaymentMethodForm { private final RevolutAccount account; private RevolutValidator validator; @@ -48,9 +53,8 @@ public class RevolutForm extends PaymentMethodForm { public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountPayload paymentAccountPayload) { - String userName = ((RevolutAccountPayload) paymentAccountPayload).getUserName(); - addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, Res.get("payment.account.userName"), userName); - + Tuple2 tuple = ((RevolutAccountPayload) paymentAccountPayload).getRecipientsAccountData(); + addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, tuple.first, tuple.second); return gridRow; } @@ -104,9 +108,17 @@ public void addFormForDisplayAccount() { account.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("shared.paymentMethod"), Res.get(account.getPaymentMethod().getId())); + String userName = account.getUserName(); - TextField field = addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.userName"), userName).second; - field.setMouseTransparent(false); + TextField userNameTf = addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.userName"), userName).second; + userNameTf.setMouseTransparent(false); + + if (account.hasOldAccountId()) { + String accountId = account.getAccountId(); + TextField accountIdTf = addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("payment.account.phoneNr"), accountId).second; + accountIdTf.setMouseTransparent(false); + } + addLimitations(true); addCurrenciesGrid(false); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index afa68484e3a..cb532ebfcee 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -31,6 +31,8 @@ import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; +import bisq.core.payment.PaymentAccount; +import bisq.core.payment.PaymentAccountUtil; import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.BankAccountPayload; import bisq.core.payment.payload.CashDepositAccountPayload; @@ -74,6 +76,7 @@ import javax.annotation.Nullable; import static bisq.desktop.util.FormBuilder.*; +import static com.google.common.base.Preconditions.checkNotNull; public class SellerStep3View extends TradeStepView { @@ -189,6 +192,7 @@ public void deactivate() { } } + /////////////////////////////////////////////////////////////////////////////////////////// // Content /////////////////////////////////////////////////////////////////////////////////////////// @@ -212,33 +216,44 @@ protected void addContent() { String myTitle = ""; String peersTitle = ""; boolean isBlockChain = false; - String nameByCode = CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()); + String nameByCode = CurrencyUtil.getNameByCode(getCurrencyCode(trade)); Contract contract = trade.getContract(); if (contract != null) { PaymentAccountPayload myPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); PaymentAccountPayload peersPaymentAccountPayload = contract.getBuyerPaymentAccountPayload(); + + myPaymentDetails = PaymentAccountUtil.findPaymentAccount(myPaymentAccountPayload, model.getUser()) + .map(PaymentAccount::getAccountName) + .orElse(""); + if (myPaymentAccountPayload instanceof AssetsAccountPayload) { - myPaymentDetails = ((AssetsAccountPayload) myPaymentAccountPayload).getAddress(); + if (myPaymentDetails.isEmpty()) { + // Not expected + myPaymentDetails = ((AssetsAccountPayload) myPaymentAccountPayload).getAddress(); + } peersPaymentDetails = ((AssetsAccountPayload) peersPaymentAccountPayload).getAddress(); myTitle = Res.get("portfolio.pending.step3_seller.yourAddress", nameByCode); peersTitle = Res.get("portfolio.pending.step3_seller.buyersAddress", nameByCode); isBlockChain = true; } else { - myPaymentDetails = myPaymentAccountPayload.getPaymentDetails(); + if (myPaymentDetails.isEmpty()) { + // Not expected + myPaymentDetails = myPaymentAccountPayload.getPaymentDetails(); + } peersPaymentDetails = peersPaymentAccountPayload.getPaymentDetails(); myTitle = Res.get("portfolio.pending.step3_seller.yourAccount"); peersTitle = Res.get("portfolio.pending.step3_seller.buyersAccount"); } } - if (!isBlockChain && !trade.getOffer().getPaymentMethod().equals(PaymentMethod.F2F)) { + if (!isBlockChain && !checkNotNull(trade.getOffer()).getPaymentMethod().equals(PaymentMethod.F2F)) { addTopLabelTextFieldWithCopyIcon( gridPane, gridRow, 1, Res.get("shared.reasonForPayment"), model.dataModel.getReference(), Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE); GridPane.setRowSpan(titledGroupBg, 4); } - useXmrTxProof = isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR"); + useXmrTxProof = isBlockChain && getCurrencyCode(trade).equals("XMR"); if (useXmrTxProof) { assetTxProofResultField = new InfoTextField(); @@ -299,13 +314,19 @@ protected void addContent() { statusLabel = tuple.third; } + @Override + protected void deactivatePaymentButtons(boolean isDisabled) { + confirmButton.setDisable(isDisabled); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Info /////////////////////////////////////////////////////////////////////////////////////////// @Override protected String getInfoText() { - String currencyCode = model.dataModel.getCurrencyCode(); + String currencyCode = getCurrencyCode(trade); if (model.isBlockChainMethod()) { return Res.get("portfolio.pending.step3_seller.buyerStartedPayment", Res.get("portfolio.pending.step3_seller.buyerStartedPayment.altcoin", currencyCode)); } else { @@ -320,7 +341,7 @@ protected String getInfoText() { @Override protected String getFirstHalfOverWarnText() { String substitute = model.isBlockChainMethod() ? - Res.get("portfolio.pending.step3_seller.warn.part1a", model.dataModel.getCurrencyCode()) : + Res.get("portfolio.pending.step3_seller.warn.part1a", getCurrencyCode(trade)) : Res.get("portfolio.pending.step3_seller.warn.part1b"); return Res.get("portfolio.pending.step3_seller.warn.part2", substitute); @@ -351,7 +372,7 @@ private void onPaymentReceived() { String key = "confirmPaymentReceived"; if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload(); - String message = Res.get("portfolio.pending.step3_seller.onPaymentReceived.part1", CurrencyUtil.getNameByCode(model.dataModel.getCurrencyCode())); + String message = Res.get("portfolio.pending.step3_seller.onPaymentReceived.part1", CurrencyUtil.getNameByCode(getCurrencyCode(trade))); if (!(paymentAccountPayload instanceof AssetsAccountPayload)) { if (!(paymentAccountPayload instanceof WesternUnionAccountPayload) && !(paymentAccountPayload instanceof HalCashAccountPayload) && @@ -387,12 +408,12 @@ private void showPopup() { String key = "confirmPayment" + trade.getId(); String message = ""; String tradeVolumeWithCode = DisplayUtils.formatVolumeWithCode(trade.getTradeVolume()); - String currencyName = CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()); + String currencyName = CurrencyUtil.getNameByCode(getCurrencyCode(trade)); String part1 = Res.get("portfolio.pending.step3_seller.part", currencyName); String id = trade.getShortId(); if (paymentAccountPayload instanceof AssetsAccountPayload) { String address = ((AssetsAccountPayload) paymentAccountPayload).getAddress(); - String explorerOrWalletString = trade.getOffer().getCurrencyCode().equals("XMR") ? + String explorerOrWalletString = getCurrencyCode(trade).equals("XMR") ? Res.get("portfolio.pending.step3_seller.altcoin.wallet", currencyName) : Res.get("portfolio.pending.step3_seller.altcoin.explorer", currencyName); message = Res.get("portfolio.pending.step3_seller.altcoin", part1, explorerOrWalletString, address, tradeVolumeWithCode, currencyName); @@ -511,8 +532,7 @@ private Label createPopoverLabel(String text) { return label; } - @Override - protected void deactivatePaymentButtons(boolean isDisabled) { - confirmButton.setDisable(isDisabled); + private String getCurrencyCode(Trade trade) { + return CurrencyUtil.getNameByCode(checkNotNull(trade.getOffer()).getCurrencyCode()); } } From b1b31369697a3f0728de9f84663842ae4f1056e0 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 6 Sep 2020 12:05:39 -0500 Subject: [PATCH 030/108] Improve handling of nullable assetTxProof fields --- .../steps/seller/SellerStep3View.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index cb532ebfcee..5e45038ada6 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -85,8 +85,11 @@ public class SellerStep3View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; + @Nullable private InfoTextField assetTxProofResultField; + @Nullable private TxConfidenceIndicator assetTxConfidenceIndicator; + @Nullable private ChangeListener proofResultListener; private boolean useXmrTxProof; @@ -157,16 +160,12 @@ public void activate() { } }); + useXmrTxProof = getCurrencyCode(trade).equals("XMR"); if (useXmrTxProof) { proofResultListener = (observable, oldValue, newValue) -> { applyAssetTxProofResult(trade.getAssetTxProofResult()); }; - - // we listen for updates on the trade autoConfirmResult field - if (assetTxProofResultField != null) { - trade.getAssetTxProofResultUpdateProperty().addListener(proofResultListener); - applyAssetTxProofResult(trade.getAssetTxProofResult()); - } + trade.getAssetTxProofResultUpdateProperty().addListener(proofResultListener); applyAssetTxProofResult(trade.getAssetTxProofResult()); } @@ -187,7 +186,7 @@ public void deactivate() { timeoutTimer.stop(); } - if (useXmrTxProof && assetTxProofResultField != null) { + if (useXmrTxProof) { trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener); } } @@ -253,7 +252,6 @@ protected void addContent() { GridPane.setRowSpan(titledGroupBg, 4); } - useXmrTxProof = isBlockChain && getCurrencyCode(trade).equals("XMR"); if (useXmrTxProof) { assetTxProofResultField = new InfoTextField(); @@ -493,8 +491,12 @@ else if (paymentAccountPayload instanceof SepaInstantAccountPayload) } private void applyAssetTxProofResult(@Nullable AssetTxProofResult result) { + checkNotNull(assetTxProofResultField); + checkNotNull(assetTxConfidenceIndicator); + String txt = GUIUtil.getProofResultAsString(result); assetTxProofResultField.setText(txt); + if (result == null) { assetTxConfidenceIndicator.setProgress(0); return; From 05cc29d854c5b4aa99b2e4ed66018fa67653a0de Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Sun, 6 Sep 2020 20:54:31 -0500 Subject: [PATCH 031/108] Minor bug fixes for XMR tx proof feature Fix missing CSS color code xmr-orange, was missing from dark mode. Fix log message spelling/typo errors. Removed 2 fixes from SellerStep3View so that chimp1984 can make changes. Remove address validator from XMR service address settings because it does not support https prefix. --- .../java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java | 2 +- .../desktop/main/settings/preferences/PreferencesView.java | 4 +--- desktop/src/main/java/bisq/desktop/theme-dark.css | 3 +++ .../main/java/bisq/network/p2p/network/TorNetworkNode.java | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index 2be07d291cb..9381897e9ab 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -199,7 +199,7 @@ public void requestFromService(Consumer resultHandler, FaultHandler faul String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); log.info("Response json from {}\n{}", this, prettyJson); } catch (Throwable error) { - log.error("Pretty rint caused a {}}: raw josn={}", error, json); + log.error("Pretty print caused a {}: raw json={}", error, json); } Result result = parser.parse(model, json); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 2a4075cfbcb..e521dc12526 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -667,13 +667,11 @@ private void initializeAutoConfirmOptions() { autoConfTradeLimitTf.setValidator(new BtcValidator(formatter)); autoConfServiceAddressTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmServiceAddresses")); - autoConfServiceAddressTf.setValidator(GUIUtil.addressRegexValidator()); - autoConfServiceAddressTf.setErrorMessage(Res.get("validation.invalidAddressList")); GridPane.setHgrow(autoConfServiceAddressTf, Priority.ALWAYS); displayCurrenciesGridRowIndex += 4; autoConfServiceAddressListener = (observable, oldValue, newValue) -> { - if (!newValue.equals(oldValue) && autoConfServiceAddressTf.getValidator().validate(newValue).isValid) { + if (!newValue.equals(oldValue)) { List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); // revert to default service providers when user empties the list if (serviceAddresses.size() == 1 && serviceAddresses.get(0).isEmpty()) { diff --git a/desktop/src/main/java/bisq/desktop/theme-dark.css b/desktop/src/main/java/bisq/desktop/theme-dark.css index 6ab66cce4de..0bc812fdded 100644 --- a/desktop/src/main/java/bisq/desktop/theme-dark.css +++ b/desktop/src/main/java/bisq/desktop/theme-dark.css @@ -139,6 +139,9 @@ /* dao chart colors */ -bs-chart-dao-line1: -bs-color-green-5; -bs-chart-dao-line2: -bs-color-blue-2; + + /* Monero orange color code */ + -xmr-orange: #f26822; } /* table view */ diff --git a/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java b/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java index d55e7b9862b..cd1ef6534b0 100644 --- a/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java +++ b/p2p/src/main/java/bisq/network/p2p/network/TorNetworkNode.java @@ -268,7 +268,7 @@ private void createTorAndHiddenService(int localPort, int servicePort) { hiddenServiceSocket.addReadyListener(socket -> { try { log.info("\n################################################################\n" + - "Tor hidden service published after {} ms. Socked={}\n" + + "Tor hidden service published after {} ms. Socket={}\n" + "################################################################", (new Date().getTime() - ts2), socket); //takes usually 30-40 sec new Thread() { From e704d02801cc1b85d9b3bb62e5164ad3cf9cac08 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 6 Sep 2020 21:37:25 -0500 Subject: [PATCH 032/108] Bug fixes - useXmrTxProof was set at active but used in addContent which was called before activate. - Fix bug with using currencyName instead of currencyCode - Refactor: Use methods in base class instead of repeated code --- .../pendingtrades/steps/TradeStepView.java | 13 ++++++++ .../steps/buyer/BuyerStep2View.java | 14 +++----- .../steps/buyer/BuyerStep3View.java | 4 +-- .../steps/seller/SellerStep2View.java | 4 +-- .../steps/seller/SellerStep3View.java | 33 ++++++++----------- 5 files changed, 35 insertions(+), 33 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index dc9a93ec49d..dc86d0aaff2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -26,6 +26,7 @@ import bisq.desktop.main.portfolio.pendingtrades.TradeSubView; import bisq.desktop.util.Layout; +import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; @@ -607,6 +608,18 @@ private void openMediationResultPopup(String headLine) { protected void deactivatePaymentButtons(boolean isDisabled) { } + protected String getCurrencyName(Trade trade) { + return CurrencyUtil.getNameByCode(getCurrencyCode(trade)); + } + + protected String getCurrencyCode(Trade trade) { + return checkNotNull(trade.getOffer()).getCurrencyCode(); + } + + protected boolean isXmrTrade() { + return getCurrencyCode(trade).equals("XMR"); + } + private void updateTradePeriodState(Trade.TradePeriodState tradePeriodState) { if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) { switch (tradePeriodState) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 562c716ebec..c1588140d04 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -55,7 +55,6 @@ import bisq.desktop.util.Layout; import bisq.desktop.util.Transitions; -import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.network.MessageState; import bisq.core.offer.Offer; @@ -313,8 +312,7 @@ protected void addContent() { break; case PaymentMethod.BLOCK_CHAINS_ID: case PaymentMethod.BLOCK_CHAINS_INSTANT_ID: - String labelTitle = Res.get("portfolio.pending.step2_buyer.sellersAddress", - CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode())); + String labelTitle = Res.get("portfolio.pending.step2_buyer.sellersAddress", getCurrencyName(trade)); gridRow = AssetsForm.addFormForBuyer(gridPane, gridRow, paymentAccountPayload, labelTitle); break; case PaymentMethod.PROMPT_PAY_ID: @@ -365,7 +363,7 @@ protected void addContent() { @Override protected String getFirstHalfOverWarnText() { return Res.get("portfolio.pending.step2_buyer.warn", - model.dataModel.getCurrencyCode(), + getCurrencyCode(trade), model.getDateForOpenDispute()); } @@ -456,8 +454,7 @@ private void onPaymentStarted() { } else { showConfirmPaymentStartedPopup(); } - } else if (sellersPaymentAccountPayload instanceof AssetsAccountPayload && - checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) { + } else if (sellersPaymentAccountPayload instanceof AssetsAccountPayload && isXmrTrade()) { SetXmrTxKeyWindow setXmrTxKeyWindow = new SetXmrTxKeyWindow(); setXmrTxKeyWindow .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) @@ -498,8 +495,7 @@ private void showConfirmPaymentStartedPopup() { if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { Popup popup = new Popup(); popup.headLine(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) - .confirmation(Res.get("portfolio.pending.step2_buyer.confirmStart.msg", - CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()))) + .confirmation(Res.get("portfolio.pending.step2_buyer.confirmStart.msg", getCurrencyName(trade))) .width(700) .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.yes")) .onAction(this::confirmPaymentStarted) @@ -548,7 +544,7 @@ private void showPopup() { String amount = DisplayUtils.formatVolumeWithCode(trade.getTradeVolume()); if (paymentAccountPayload instanceof AssetsAccountPayload) { message += Res.get("portfolio.pending.step2_buyer.altcoin", - CurrencyUtil.getNameByCode(trade.getOffer().getCurrencyCode()), + getCurrencyName(trade), amount) + accountDetails + paymentDetailsForTradePopup + "\n\n" + diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java index 372a7d44801..f04305aefe9 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep3View.java @@ -87,7 +87,7 @@ protected String getInfoBlockTitle() { @Override protected String getInfoText() { - return Res.get("portfolio.pending.step3_buyer.wait.info", model.dataModel.getCurrencyCode()); + return Res.get("portfolio.pending.step3_buyer.wait.info", getCurrencyCode(trade)); } private void updateMessageStateInfo() { @@ -130,7 +130,7 @@ private void updateMessageStateInfo() { @Override protected String getFirstHalfOverWarnText() { String substitute = model.isBlockChainMethod() ? - Res.get("portfolio.pending.step3_buyer.warn.part1a", model.dataModel.getCurrencyCode()) : + Res.get("portfolio.pending.step3_buyer.warn.part1a", getCurrencyCode(trade)) : Res.get("portfolio.pending.step3_buyer.warn.part1b"); return Res.get("portfolio.pending.step3_buyer.warn.part2", substitute); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java index 5fb71302677..425634d2fb3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep2View.java @@ -134,7 +134,7 @@ protected String getInfoBlockTitle() { @Override protected String getInfoText() { - return Res.get("portfolio.pending.step2_seller.waitPayment.msg", model.dataModel.getCurrencyCode()); + return Res.get("portfolio.pending.step2_seller.waitPayment.msg", getCurrencyCode(trade)); } @@ -145,7 +145,7 @@ protected String getInfoText() { @Override protected String getFirstHalfOverWarnText() { return Res.get("portfolio.pending.step2_seller.warn", - model.dataModel.getCurrencyCode(), + getCurrencyCode(trade), model.getDateForOpenDispute()); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 5e45038ada6..938470c3cfb 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -29,7 +29,6 @@ import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; -import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccountUtil; @@ -91,7 +90,6 @@ public class SellerStep3View extends TradeStepView { private TxConfidenceIndicator assetTxConfidenceIndicator; @Nullable private ChangeListener proofResultListener; - private boolean useXmrTxProof; /////////////////////////////////////////////////////////////////////////////////////////// @@ -160,8 +158,7 @@ public void activate() { } }); - useXmrTxProof = getCurrencyCode(trade).equals("XMR"); - if (useXmrTxProof) { + if (isXmrTrade()) { proofResultListener = (observable, oldValue, newValue) -> { applyAssetTxProofResult(trade.getAssetTxProofResult()); }; @@ -186,7 +183,7 @@ public void deactivate() { timeoutTimer.stop(); } - if (useXmrTxProof) { + if (isXmrTrade()) { trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener); } } @@ -215,7 +212,7 @@ protected void addContent() { String myTitle = ""; String peersTitle = ""; boolean isBlockChain = false; - String nameByCode = CurrencyUtil.getNameByCode(getCurrencyCode(trade)); + String currencyName = getCurrencyName(trade); Contract contract = trade.getContract(); if (contract != null) { PaymentAccountPayload myPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); @@ -231,8 +228,8 @@ protected void addContent() { myPaymentDetails = ((AssetsAccountPayload) myPaymentAccountPayload).getAddress(); } peersPaymentDetails = ((AssetsAccountPayload) peersPaymentAccountPayload).getAddress(); - myTitle = Res.get("portfolio.pending.step3_seller.yourAddress", nameByCode); - peersTitle = Res.get("portfolio.pending.step3_seller.buyersAddress", nameByCode); + myTitle = Res.get("portfolio.pending.step3_seller.yourAddress", currencyName); + peersTitle = Res.get("portfolio.pending.step3_seller.buyersAddress", currencyName); isBlockChain = true; } else { if (myPaymentDetails.isEmpty()) { @@ -252,7 +249,7 @@ protected void addContent() { GridPane.setRowSpan(titledGroupBg, 4); } - if (useXmrTxProof) { + if (isXmrTrade()) { assetTxProofResultField = new InfoTextField(); Tuple2 topLabelWithVBox = getTopLabelWithVBox(Res.get("portfolio.pending.step3_seller.autoConf.status.label"), assetTxProofResultField); @@ -324,11 +321,11 @@ protected void deactivatePaymentButtons(boolean isDisabled) { @Override protected String getInfoText() { - String currencyCode = getCurrencyCode(trade); + String currencyName = getCurrencyName(trade); if (model.isBlockChainMethod()) { - return Res.get("portfolio.pending.step3_seller.buyerStartedPayment", Res.get("portfolio.pending.step3_seller.buyerStartedPayment.altcoin", currencyCode)); + return Res.get("portfolio.pending.step3_seller.buyerStartedPayment", Res.get("portfolio.pending.step3_seller.buyerStartedPayment.altcoin", currencyName)); } else { - return Res.get("portfolio.pending.step3_seller.buyerStartedPayment", Res.get("portfolio.pending.step3_seller.buyerStartedPayment.fiat", currencyCode)); + return Res.get("portfolio.pending.step3_seller.buyerStartedPayment", Res.get("portfolio.pending.step3_seller.buyerStartedPayment.fiat", currencyName)); } } @@ -339,7 +336,7 @@ protected String getInfoText() { @Override protected String getFirstHalfOverWarnText() { String substitute = model.isBlockChainMethod() ? - Res.get("portfolio.pending.step3_seller.warn.part1a", getCurrencyCode(trade)) : + Res.get("portfolio.pending.step3_seller.warn.part1a", getCurrencyName(trade)) : Res.get("portfolio.pending.step3_seller.warn.part1b"); return Res.get("portfolio.pending.step3_seller.warn.part2", substitute); @@ -370,7 +367,7 @@ private void onPaymentReceived() { String key = "confirmPaymentReceived"; if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { PaymentAccountPayload paymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload(); - String message = Res.get("portfolio.pending.step3_seller.onPaymentReceived.part1", CurrencyUtil.getNameByCode(getCurrencyCode(trade))); + String message = Res.get("portfolio.pending.step3_seller.onPaymentReceived.part1", getCurrencyName(trade)); if (!(paymentAccountPayload instanceof AssetsAccountPayload)) { if (!(paymentAccountPayload instanceof WesternUnionAccountPayload) && !(paymentAccountPayload instanceof HalCashAccountPayload) && @@ -406,12 +403,12 @@ private void showPopup() { String key = "confirmPayment" + trade.getId(); String message = ""; String tradeVolumeWithCode = DisplayUtils.formatVolumeWithCode(trade.getTradeVolume()); - String currencyName = CurrencyUtil.getNameByCode(getCurrencyCode(trade)); + String currencyName = getCurrencyName(trade); String part1 = Res.get("portfolio.pending.step3_seller.part", currencyName); String id = trade.getShortId(); if (paymentAccountPayload instanceof AssetsAccountPayload) { String address = ((AssetsAccountPayload) paymentAccountPayload).getAddress(); - String explorerOrWalletString = getCurrencyCode(trade).equals("XMR") ? + String explorerOrWalletString = isXmrTrade() ? Res.get("portfolio.pending.step3_seller.altcoin.wallet", currencyName) : Res.get("portfolio.pending.step3_seller.altcoin.explorer", currencyName); message = Res.get("portfolio.pending.step3_seller.altcoin", part1, explorerOrWalletString, address, tradeVolumeWithCode, currencyName); @@ -533,8 +530,4 @@ private Label createPopoverLabel(String text) { label.setPadding(new Insets(10)); return label; } - - private String getCurrencyCode(Trade trade) { - return CurrencyUtil.getNameByCode(checkNotNull(trade.getOffer()).getCurrencyCode()); - } } From 1c2a6a824550afbc31ccf2df8d5e57745a2ee318 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 7 Sep 2020 01:04:23 -0500 Subject: [PATCH 033/108] Create new HttpClient for each request. --- core/src/main/java/bisq/core/trade/TradeModule.java | 4 ---- .../txproof/xmr/DevTestXmrTxProofHttpClient.java | 3 --- .../core/trade/txproof/xmr/XmrTxProofHttpClient.java | 7 ++----- .../core/trade/txproof/xmr/XmrTxProofRequest.java | 8 +++++--- .../trade/txproof/xmr/XmrTxProofRequestsPerTrade.java | 11 ++++++----- .../core/trade/txproof/xmr/XmrTxProofService.java | 10 +++++----- 6 files changed, 18 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/TradeModule.java b/core/src/main/java/bisq/core/trade/TradeModule.java index 422ec13a83c..779cf6e7600 100644 --- a/core/src/main/java/bisq/core/trade/TradeModule.java +++ b/core/src/main/java/bisq/core/trade/TradeModule.java @@ -26,8 +26,6 @@ import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatistics2StorageService; import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.core.trade.txproof.AssetTxProofHttpClient; -import bisq.core.trade.txproof.xmr.XmrTxProofHttpClient; import bisq.common.app.AppModule; import bisq.common.config.Config; @@ -58,8 +56,6 @@ protected void configure() { bind(SignedWitnessStorageService.class).in(Singleton.class); bind(ReferralIdService.class).in(Singleton.class); - bind(AssetTxProofHttpClient.class).to(XmrTxProofHttpClient.class); - bindConstant().annotatedWith(named(DUMP_STATISTICS)).to(config.dumpStatistics); bindConstant().annotatedWith(named(DUMP_DELAYED_PAYOUT_TXS)).to(config.dumpDelayedPayoutTxs); bindConstant().annotatedWith(named(ALLOW_FAULTY_DELAYED_TXS)).to(config.allowFaultyDelayedTxs); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java index b415d354c44..98461356763 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java @@ -24,8 +24,6 @@ import bisq.common.app.DevEnv; -import javax.inject.Inject; - import java.util.Date; import lombok.extern.slf4j.Slf4j; @@ -58,7 +56,6 @@ enum ApiInvalidDetails { EXCEPTION } - @Inject public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java index dce87473837..ac0a4148de0 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java @@ -22,14 +22,11 @@ import bisq.network.Socks5ProxyProvider; import bisq.network.http.HttpClientImpl; -import javax.inject.Inject; - import lombok.extern.slf4j.Slf4j; @Slf4j -public class XmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { - @Inject - public XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { +class XmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { + XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); } } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index 9381897e9ab..325538acb67 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -21,6 +21,8 @@ import bisq.core.trade.txproof.AssetTxProofParser; import bisq.core.trade.txproof.AssetTxProofRequest; +import bisq.network.Socks5ProxyProvider; + import bisq.common.UserThread; import bisq.common.app.Version; import bisq.common.handlers.FaultHandler; @@ -140,9 +142,9 @@ public String toString() { private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofRequester", 3, 5, 10 * 60); - private final AssetTxProofHttpClient httpClient; private final AssetTxProofParser parser; private final XmrTxProofModel model; + private final AssetTxProofHttpClient httpClient; private final long firstRequest; private boolean terminated; @@ -155,12 +157,12 @@ public String toString() { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTxProofRequest(AssetTxProofHttpClient httpClient, + XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, XmrTxProofModel model) { - this.httpClient = httpClient; this.parser = new XmrTxProofParser(); this.model = model; + httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); httpClient.setBaseUrl("http://" + model.getServiceAddress()); if (model.getServiceAddress().matches("^192.*|^localhost.*")) { log.info("Ignoring Socks5 proxy for local net address: {}", model.getServiceAddress()); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 634f6b8ae79..73690c6bd57 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -22,11 +22,12 @@ import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.refund.RefundManager; import bisq.core.trade.Trade; -import bisq.core.trade.txproof.AssetTxProofHttpClient; import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.AutoConfirmSettings; +import bisq.network.Socks5ProxyProvider; + import bisq.common.handlers.FaultHandler; import org.bitcoinj.core.Coin; @@ -54,7 +55,7 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { private final AutoConfirmSettings autoConfirmSettings; private final MediationManager mediationManager; private final RefundManager refundManager; - private final AssetTxProofHttpClient httpClient; + private final Socks5ProxyProvider socks5ProxyProvider; private int numRequiredSuccessResults; private final Set requests = new HashSet<>(); @@ -69,12 +70,12 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTxProofRequestsPerTrade(AssetTxProofHttpClient httpClient, + XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, Trade trade, AutoConfirmSettings autoConfirmSettings, MediationManager mediationManager, RefundManager refundManager) { - this.httpClient = httpClient; + this.socks5ProxyProvider = socks5ProxyProvider; this.trade = trade; this.autoConfirmSettings = autoConfirmSettings; this.mediationManager = mediationManager; @@ -140,7 +141,7 @@ public void requestFromAllServices(Consumer resultHandler, F for (String serviceAddress : serviceAddresses) { XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings); - XmrTxProofRequest request = new XmrTxProofRequest(httpClient, model); + XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); log.info("{} created", request); requests.add(request); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 99d5f670d36..7dc77c1651e 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -27,12 +27,12 @@ import bisq.core.trade.TradeManager; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; -import bisq.core.trade.txproof.AssetTxProofHttpClient; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.trade.txproof.AssetTxProofService; import bisq.core.user.AutoConfirmSettings; import bisq.core.user.Preferences; +import bisq.network.Socks5ProxyProvider; import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.P2PService; @@ -76,7 +76,7 @@ public class XmrTxProofService implements AssetTxProofService { private final RefundManager refundManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; - private final AssetTxProofHttpClient httpClient; + private final Socks5ProxyProvider socks5ProxyProvider; private final Map servicesByTradeId = new HashMap<>(); private AutoConfirmSettings autoConfirmSettings; private Map> tradeStateListenerMap = new HashMap<>(); @@ -99,7 +99,7 @@ public XmrTxProofService(FilterManager filterManager, RefundManager refundManager, P2PService p2PService, WalletsSetup walletsSetup, - AssetTxProofHttpClient httpClient) { + Socks5ProxyProvider socks5ProxyProvider) { this.filterManager = filterManager; this.preferences = preferences; this.tradeManager = tradeManager; @@ -109,7 +109,7 @@ public XmrTxProofService(FilterManager filterManager, this.refundManager = refundManager; this.p2PService = p2PService; this.walletsSetup = walletsSetup; - this.httpClient = httpClient; + this.socks5ProxyProvider = socks5ProxyProvider; } @@ -231,7 +231,7 @@ private void startRequestsIfValid(SellerTrade trade) { } private void startRequests(SellerTrade trade) { - XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(httpClient, + XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(socks5ProxyProvider, trade, autoConfirmSettings, mediationManager, From 39680ec7ff2e3f33492a679c57c90b3a42560b26 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 7 Sep 2020 01:05:07 -0500 Subject: [PATCH 034/108] Remove DevTestXmrTxProofHttpClient --- .../xmr/DevTestXmrTxProofHttpClient.java | 203 ------------------ 1 file changed, 203 deletions(-) delete mode 100644 core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java deleted file mode 100644 index 98461356763..00000000000 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.txproof.xmr; - -import bisq.core.trade.txproof.AssetTxProofHttpClient; - -import bisq.network.Socks5ProxyProvider; -import bisq.network.http.HttpClientImpl; - -import bisq.common.app.DevEnv; - -import java.util.Date; - -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Nullable; - -import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; - -/** - * This should help to test error scenarios in dev testing the app. This is additional to unit test which test the - * correct data but do not test the context of the results and how it behaves in the UI. - * - * You have to change the binding in TradeModule to - * bind(AssetTxProofHttpClient.class).to(DevTestXmrTxProofHttpClient.class); to use that class. - * - * This class can be removed once done testing, but as multiple devs are testing its useful to share it for now. - */ -@Slf4j -public class DevTestXmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { - enum ApiInvalidDetails { - EMPTY_JSON, - MISSING_DATA, - MISSING_STATUS, - UNHANDLED_STATUS, - MISSING_ADDRESS, - MISSING_TX_ID, - MISSING_VIEW_KEY, - MISSING_TS, - MISSING_CONF, - EXCEPTION - } - - public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { - super(socks5ProxyProvider); - } - - private static int counter; - - @Override - public String requestWithGET(String param, - @Nullable String headerKey, - @Nullable String headerValue) { - - XmrTxProofRequest.Result result = XmrTxProofRequest.Result.PENDING; - XmrTxProofRequest.Detail detail = XmrTxProofRequest.Detail.TX_NOT_FOUND; - ApiInvalidDetails apiInvalidDetails = ApiInvalidDetails.EXCEPTION; - - int delay = counter == 0 ? 2000 : 100; - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (counter >= 2) { - detail = XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(counter - 2); - } - counter++; - switch (result) { - case PENDING: - switch (detail) { - case TX_NOT_FOUND: - return validJson().replace("success", - "fail"); - case PENDING_CONFIRMATIONS: - return validJson().replace("201287", - String.valueOf(detail.getNumConfirmations())); - default: - return null; - } - case SUCCESS: - return validJson(); - case FAILED: - switch (detail) { - case TX_HASH_INVALID: - return validJson().replace("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802", - "-"); - case TX_KEY_INVALID: - return validJson().replace("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906", - "-"); - case ADDRESS_INVALID: - return validJson().replace("590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571", - "-"); - case NO_MATCH_FOUND: - return validJson().replace("match\": true", - "match\": false"); - case AMOUNT_NOT_MATCHING: - return validJson().replace("8902597360000", - "18902597360000"); - case TRADE_DATE_NOT_MATCHING: - DevEnv.setDevMode(false); - long date = (new Date(1574922644 * 1000L).getTime() - (MAX_DATE_TOLERANCE * 1000L + 1)) / 1000; - return validJson().replace("1574922644", - String.valueOf(date)); - default: - return null; - } - case ERROR: - switch (detail) { - case CONNECTION_FAILURE: - // Not part of parser level testing - return null; - case API_INVALID: - switch (apiInvalidDetails) { - case EMPTY_JSON: - return null; - case MISSING_DATA: - return validJson().replace("data", - "missing"); - case MISSING_STATUS: - return validJson().replace("status", - "missing"); - case UNHANDLED_STATUS: - return validJson().replace("success", - "missing"); - case MISSING_ADDRESS: - return validJson().replace("address", - "missing"); - case MISSING_TX_ID: - return validJson().replace("tx_hash", - "missing"); - case MISSING_VIEW_KEY: - return validJson().replace("viewkey", - "missing"); - case MISSING_TS: - return validJson().replace("tx_timestamp", - "missing"); - case MISSING_CONF: - return validJson().replace("tx_confirmations", - "missing"); - case EXCEPTION: - return validJson().replace("} ", - ""); - default: - return null; - } - - case NO_RESULTS_TIMEOUT: - // Not part of parser level testing - return null; - default: - return null; - } - default: - return null; - } - } - - private String validJson() { - return "{\n" + - " \"data\": {\n" + - " \"address\": \"590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571\",\n" + - " \"outputs\": [\n" + - " {\n" + - " \"amount\": 8902597360000,\n" + - " \"match\": true,\n" + - " \"output_idx\": 0,\n" + - " \"output_pubkey\": \"2b6d2296f2591c198cd1aa47de9a5d74270963412ed30bbcc63b8eff29f0d43e\"\n" + - " },\n" + - " {\n" + - " \"amount\": 0,\n" + - " \"match\": false,\n" + - " \"output_idx\": 1,\n" + - " \"output_pubkey\": \"f53271624847507d80b746e91e689e88bc41678d55246275f5ad3c0f7e8a9ced\"\n" + - " }\n" + - " ],\n" + - " \"tx_confirmations\": 201287,\n" + - " \"tx_hash\": \"5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802\",\n" + - " \"tx_prove\": true,\n" + - " \"tx_timestamp\": 1574922644,\n" + - " \"viewkey\": \"f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906\"\n" + - " },\n" + - " \"status\": \"success\"\n" + - "} "; - } - -} From b937537926210d278b000cb5df63e2e031789682 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 7 Sep 2020 01:04:23 -0500 Subject: [PATCH 035/108] Resolve conflict in 6a061d2c5 CONFLICT (modify/delete): core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java --- .../xmr/DevTestXmrTxProofHttpClient.java | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java new file mode 100644 index 00000000000..98461356763 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java @@ -0,0 +1,203 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof.xmr; + +import bisq.core.trade.txproof.AssetTxProofHttpClient; + +import bisq.network.Socks5ProxyProvider; +import bisq.network.http.HttpClientImpl; + +import bisq.common.app.DevEnv; + +import java.util.Date; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; + +/** + * This should help to test error scenarios in dev testing the app. This is additional to unit test which test the + * correct data but do not test the context of the results and how it behaves in the UI. + * + * You have to change the binding in TradeModule to + * bind(AssetTxProofHttpClient.class).to(DevTestXmrTxProofHttpClient.class); to use that class. + * + * This class can be removed once done testing, but as multiple devs are testing its useful to share it for now. + */ +@Slf4j +public class DevTestXmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { + enum ApiInvalidDetails { + EMPTY_JSON, + MISSING_DATA, + MISSING_STATUS, + UNHANDLED_STATUS, + MISSING_ADDRESS, + MISSING_TX_ID, + MISSING_VIEW_KEY, + MISSING_TS, + MISSING_CONF, + EXCEPTION + } + + public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { + super(socks5ProxyProvider); + } + + private static int counter; + + @Override + public String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) { + + XmrTxProofRequest.Result result = XmrTxProofRequest.Result.PENDING; + XmrTxProofRequest.Detail detail = XmrTxProofRequest.Detail.TX_NOT_FOUND; + ApiInvalidDetails apiInvalidDetails = ApiInvalidDetails.EXCEPTION; + + int delay = counter == 0 ? 2000 : 100; + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (counter >= 2) { + detail = XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(counter - 2); + } + counter++; + switch (result) { + case PENDING: + switch (detail) { + case TX_NOT_FOUND: + return validJson().replace("success", + "fail"); + case PENDING_CONFIRMATIONS: + return validJson().replace("201287", + String.valueOf(detail.getNumConfirmations())); + default: + return null; + } + case SUCCESS: + return validJson(); + case FAILED: + switch (detail) { + case TX_HASH_INVALID: + return validJson().replace("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802", + "-"); + case TX_KEY_INVALID: + return validJson().replace("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906", + "-"); + case ADDRESS_INVALID: + return validJson().replace("590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571", + "-"); + case NO_MATCH_FOUND: + return validJson().replace("match\": true", + "match\": false"); + case AMOUNT_NOT_MATCHING: + return validJson().replace("8902597360000", + "18902597360000"); + case TRADE_DATE_NOT_MATCHING: + DevEnv.setDevMode(false); + long date = (new Date(1574922644 * 1000L).getTime() - (MAX_DATE_TOLERANCE * 1000L + 1)) / 1000; + return validJson().replace("1574922644", + String.valueOf(date)); + default: + return null; + } + case ERROR: + switch (detail) { + case CONNECTION_FAILURE: + // Not part of parser level testing + return null; + case API_INVALID: + switch (apiInvalidDetails) { + case EMPTY_JSON: + return null; + case MISSING_DATA: + return validJson().replace("data", + "missing"); + case MISSING_STATUS: + return validJson().replace("status", + "missing"); + case UNHANDLED_STATUS: + return validJson().replace("success", + "missing"); + case MISSING_ADDRESS: + return validJson().replace("address", + "missing"); + case MISSING_TX_ID: + return validJson().replace("tx_hash", + "missing"); + case MISSING_VIEW_KEY: + return validJson().replace("viewkey", + "missing"); + case MISSING_TS: + return validJson().replace("tx_timestamp", + "missing"); + case MISSING_CONF: + return validJson().replace("tx_confirmations", + "missing"); + case EXCEPTION: + return validJson().replace("} ", + ""); + default: + return null; + } + + case NO_RESULTS_TIMEOUT: + // Not part of parser level testing + return null; + default: + return null; + } + default: + return null; + } + } + + private String validJson() { + return "{\n" + + " \"data\": {\n" + + " \"address\": \"590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571\",\n" + + " \"outputs\": [\n" + + " {\n" + + " \"amount\": 8902597360000,\n" + + " \"match\": true,\n" + + " \"output_idx\": 0,\n" + + " \"output_pubkey\": \"2b6d2296f2591c198cd1aa47de9a5d74270963412ed30bbcc63b8eff29f0d43e\"\n" + + " },\n" + + " {\n" + + " \"amount\": 0,\n" + + " \"match\": false,\n" + + " \"output_idx\": 1,\n" + + " \"output_pubkey\": \"f53271624847507d80b746e91e689e88bc41678d55246275f5ad3c0f7e8a9ced\"\n" + + " }\n" + + " ],\n" + + " \"tx_confirmations\": 201287,\n" + + " \"tx_hash\": \"5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802\",\n" + + " \"tx_prove\": true,\n" + + " \"tx_timestamp\": 1574922644,\n" + + " \"viewkey\": \"f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906\"\n" + + " },\n" + + " \"status\": \"success\"\n" + + "} "; + } + +} From 82bc445a055f54b2b2fcd974de8f86fe5f8f0abb Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 7 Sep 2020 01:05:07 -0500 Subject: [PATCH 036/108] Remove DevTestXmrTxProofHttpClient --- .../xmr/DevTestXmrTxProofHttpClient.java | 203 ------------------ 1 file changed, 203 deletions(-) delete mode 100644 core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java deleted file mode 100644 index 98461356763..00000000000 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.txproof.xmr; - -import bisq.core.trade.txproof.AssetTxProofHttpClient; - -import bisq.network.Socks5ProxyProvider; -import bisq.network.http.HttpClientImpl; - -import bisq.common.app.DevEnv; - -import java.util.Date; - -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Nullable; - -import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; - -/** - * This should help to test error scenarios in dev testing the app. This is additional to unit test which test the - * correct data but do not test the context of the results and how it behaves in the UI. - * - * You have to change the binding in TradeModule to - * bind(AssetTxProofHttpClient.class).to(DevTestXmrTxProofHttpClient.class); to use that class. - * - * This class can be removed once done testing, but as multiple devs are testing its useful to share it for now. - */ -@Slf4j -public class DevTestXmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { - enum ApiInvalidDetails { - EMPTY_JSON, - MISSING_DATA, - MISSING_STATUS, - UNHANDLED_STATUS, - MISSING_ADDRESS, - MISSING_TX_ID, - MISSING_VIEW_KEY, - MISSING_TS, - MISSING_CONF, - EXCEPTION - } - - public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { - super(socks5ProxyProvider); - } - - private static int counter; - - @Override - public String requestWithGET(String param, - @Nullable String headerKey, - @Nullable String headerValue) { - - XmrTxProofRequest.Result result = XmrTxProofRequest.Result.PENDING; - XmrTxProofRequest.Detail detail = XmrTxProofRequest.Detail.TX_NOT_FOUND; - ApiInvalidDetails apiInvalidDetails = ApiInvalidDetails.EXCEPTION; - - int delay = counter == 0 ? 2000 : 100; - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (counter >= 2) { - detail = XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(counter - 2); - } - counter++; - switch (result) { - case PENDING: - switch (detail) { - case TX_NOT_FOUND: - return validJson().replace("success", - "fail"); - case PENDING_CONFIRMATIONS: - return validJson().replace("201287", - String.valueOf(detail.getNumConfirmations())); - default: - return null; - } - case SUCCESS: - return validJson(); - case FAILED: - switch (detail) { - case TX_HASH_INVALID: - return validJson().replace("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802", - "-"); - case TX_KEY_INVALID: - return validJson().replace("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906", - "-"); - case ADDRESS_INVALID: - return validJson().replace("590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571", - "-"); - case NO_MATCH_FOUND: - return validJson().replace("match\": true", - "match\": false"); - case AMOUNT_NOT_MATCHING: - return validJson().replace("8902597360000", - "18902597360000"); - case TRADE_DATE_NOT_MATCHING: - DevEnv.setDevMode(false); - long date = (new Date(1574922644 * 1000L).getTime() - (MAX_DATE_TOLERANCE * 1000L + 1)) / 1000; - return validJson().replace("1574922644", - String.valueOf(date)); - default: - return null; - } - case ERROR: - switch (detail) { - case CONNECTION_FAILURE: - // Not part of parser level testing - return null; - case API_INVALID: - switch (apiInvalidDetails) { - case EMPTY_JSON: - return null; - case MISSING_DATA: - return validJson().replace("data", - "missing"); - case MISSING_STATUS: - return validJson().replace("status", - "missing"); - case UNHANDLED_STATUS: - return validJson().replace("success", - "missing"); - case MISSING_ADDRESS: - return validJson().replace("address", - "missing"); - case MISSING_TX_ID: - return validJson().replace("tx_hash", - "missing"); - case MISSING_VIEW_KEY: - return validJson().replace("viewkey", - "missing"); - case MISSING_TS: - return validJson().replace("tx_timestamp", - "missing"); - case MISSING_CONF: - return validJson().replace("tx_confirmations", - "missing"); - case EXCEPTION: - return validJson().replace("} ", - ""); - default: - return null; - } - - case NO_RESULTS_TIMEOUT: - // Not part of parser level testing - return null; - default: - return null; - } - default: - return null; - } - } - - private String validJson() { - return "{\n" + - " \"data\": {\n" + - " \"address\": \"590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571\",\n" + - " \"outputs\": [\n" + - " {\n" + - " \"amount\": 8902597360000,\n" + - " \"match\": true,\n" + - " \"output_idx\": 0,\n" + - " \"output_pubkey\": \"2b6d2296f2591c198cd1aa47de9a5d74270963412ed30bbcc63b8eff29f0d43e\"\n" + - " },\n" + - " {\n" + - " \"amount\": 0,\n" + - " \"match\": false,\n" + - " \"output_idx\": 1,\n" + - " \"output_pubkey\": \"f53271624847507d80b746e91e689e88bc41678d55246275f5ad3c0f7e8a9ced\"\n" + - " }\n" + - " ],\n" + - " \"tx_confirmations\": 201287,\n" + - " \"tx_hash\": \"5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802\",\n" + - " \"tx_prove\": true,\n" + - " \"tx_timestamp\": 1574922644,\n" + - " \"viewkey\": \"f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906\"\n" + - " },\n" + - " \"status\": \"success\"\n" + - "} "; - } - -} From 67fc398f9b533176a423012abb53e758758264d9 Mon Sep 17 00:00:00 2001 From: m52go Date: Mon, 7 Sep 2020 11:35:34 -0400 Subject: [PATCH 037/108] Add note to SetXmrTxKeyWindow To convey what it's about, and that it's optional. --- core/src/main/resources/i18n/displayStrings.properties | 5 +++-- .../desktop/main/overlays/windows/SetXmrTxKeyWindow.java | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 18e56a562e5..a05da2ccbaf 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -2551,8 +2551,9 @@ showWalletDataWindow.walletData=Wallet data showWalletDataWindow.includePrivKeys=Include private keys setXMRTxKeyWindow.headline=Prove sending of XMR -setXMRTxKeyWindow.txHash=Transaction ID -setXMRTxKeyWindow.txKey=Transaction key +setXMRTxKeyWindow.note=Adding tx info below enables auto-confirm for quicker trades. See more: https://bisq.wiki/Trading_Monero +setXMRTxKeyWindow.txHash=Transaction ID (optional) +setXMRTxKeyWindow.txKey=Transaction key (optional) # We do not translate the tac because of the legal nature. We would need translations checked by lawyers # in each language which is too expensive atm. diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index 8862664104f..517f57be089 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -36,6 +36,7 @@ import javax.annotation.Nullable; import static bisq.common.app.DevEnv.isDevMode; +import static bisq.desktop.util.FormBuilder.addMultilineLabel; import static bisq.desktop.util.FormBuilder.addInputTextField; import static javafx.beans.binding.Bindings.createBooleanBinding; @@ -114,6 +115,7 @@ public String getTxKey() { } private void addContent() { + addMultilineLabel(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.note"), 0); txHashInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txHash"), 10); txKeyInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txKey")); } From f21019d44c2dba6453714a6edf3d11a195301bee Mon Sep 17 00:00:00 2001 From: Stephan Oeste Date: Mon, 7 Sep 2020 12:38:50 +0200 Subject: [PATCH 038/108] Change from http port 80 clearnet to https 443 To be compatible to https://github.com/bisq-network/bisq/pull/4492 --- core/src/main/java/bisq/core/user/Preferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 4ae7eba6409..694554b2f74 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -124,7 +124,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid )); private static final ArrayList XMR_TX_PROOF_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList( - "78.47.61.90:8081", // @emzy + "xmrblocks.monero.emzy.de", // @emzy "node77.monero.wiz.biz" // @wiz )); private static final ArrayList XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList( From a7b5cdbd0311b9f2ca698c61212766a8aa359c46 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 19:18:33 -0500 Subject: [PATCH 039/108] Resolve merge conflict in Merge conflict in desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java --- .../pendingtrades/steps/TradeStepView.java | 14 +++++++++----- .../pendingtrades/steps/buyer/BuyerStep2View.java | 6 ++++++ .../steps/seller/SellerStep3View.java | 4 ++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index dc86d0aaff2..27219a847ef 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -391,7 +391,6 @@ protected void applyOnDisputeOpened() { } private void updateDisputeState(Trade.DisputeState disputeState) { - deactivatePaymentButtons(false); Optional ownDispute; switch (disputeState) { case NO_DISPUTE: @@ -407,7 +406,6 @@ private void updateDisputeState(Trade.DisputeState disputeState) { if (tradeStepInfo != null) tradeStepInfo.setState(TradeStepInfo.State.IN_MEDIATION_SELF_REQUESTED); }); - break; case MEDIATION_STARTED_BY_PEER: if (tradeStepInfo != null) { @@ -436,7 +434,6 @@ private void updateDisputeState(Trade.DisputeState disputeState) { updateMediationResultState(true); break; case REFUND_REQUESTED: - deactivatePaymentButtons(true); if (tradeStepInfo != null) { tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); } @@ -450,7 +447,6 @@ private void updateDisputeState(Trade.DisputeState disputeState) { break; case REFUND_REQUEST_STARTED_BY_PEER: - deactivatePaymentButtons(true); if (tradeStepInfo != null) { tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText); } @@ -463,9 +459,12 @@ private void updateDisputeState(Trade.DisputeState disputeState) { }); break; case REFUND_REQUEST_CLOSED: - deactivatePaymentButtons(true); + break; + default: break; } + + deactivatePaymentButtons(isDisputed()); } private void updateMediationResultState(boolean blockOpeningOfResultAcceptedPopup) { @@ -652,6 +651,11 @@ private void updateTradePeriodState(Trade.TradePeriodState tradePeriodState) { } } + protected boolean isDisputed() { + return trade.getDisputeState() != Trade.DisputeState.NO_DISPUTE; + } + + /////////////////////////////////////////////////////////////////////////////////////////// // TradeDurationLimitInfo /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index c1588140d04..18378629c1f 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -192,6 +192,8 @@ public void activate() { } }); } + + confirmButton.setDisable(isDisputed()); } @Override @@ -385,6 +387,10 @@ protected void applyOnDisputeOpened() { /////////////////////////////////////////////////////////////////////////////////////////// private void onPaymentStarted() { + if (isDisputed()) { + return; + } + if (!model.dataModel.isBootstrappedOrShowPopup()) { return; } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 938470c3cfb..6985d097df0 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -361,6 +361,10 @@ protected void applyOnDisputeOpened() { /////////////////////////////////////////////////////////////////////////////////////////// private void onPaymentReceived() { + if (isDisputed()) { + return; + } + // 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()) { From 5ecbbf601fb2699238008f1927461f9d5aacfc50 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sat, 5 Sep 2020 19:19:49 -0500 Subject: [PATCH 040/108] Resolve merge conflict in Merge conflict in desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java --- .../portfolio/pendingtrades/steps/TradeStepView.java | 4 ++-- .../pendingtrades/steps/buyer/BuyerStep2View.java | 2 +- .../pendingtrades/steps/seller/SellerStep3View.java | 11 +++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 27219a847ef..5a3b1e9656a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -464,7 +464,7 @@ private void updateDisputeState(Trade.DisputeState disputeState) { break; } - deactivatePaymentButtons(isDisputed()); + updateConfirmButtonDisableState(isDisputed()); } private void updateMediationResultState(boolean blockOpeningOfResultAcceptedPopup) { @@ -604,7 +604,7 @@ private void openMediationResultPopup(String headLine) { acceptMediationResultPopup.show(); } - protected void deactivatePaymentButtons(boolean isDisabled) { + protected void updateConfirmButtonDisableState(boolean isDisabled) { } protected String getCurrencyName(Trade trade) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 18378629c1f..d6d2aeabe33 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -633,7 +633,7 @@ private void showPopup() { } @Override - protected void deactivatePaymentButtons(boolean isDisabled) { + protected void updateConfirmButtonDisableState(boolean isDisabled) { confirmButton.setDisable(isDisabled); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 6985d097df0..8652a5c747a 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -309,12 +309,6 @@ protected void addContent() { statusLabel = tuple.third; } - @Override - protected void deactivatePaymentButtons(boolean isDisabled) { - confirmButton.setDisable(isDisabled); - } - - /////////////////////////////////////////////////////////////////////////////////////////// // Info /////////////////////////////////////////////////////////////////////////////////////////// @@ -534,4 +528,9 @@ private Label createPopoverLabel(String text) { label.setPadding(new Insets(10)); return label; } + + @Override + protected void updateConfirmButtonDisableState(boolean isDisabled) { + confirmButton.setDisable(isDisabled); + } } From cdf2698e29aa9a4fafc959a708016cb21fa39364 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 6 Sep 2020 23:06:27 -0500 Subject: [PATCH 041/108] Add comment to empty method --- .../main/portfolio/pendingtrades/steps/TradeStepView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java index 5a3b1e9656a..cfc35e89681 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/TradeStepView.java @@ -605,6 +605,7 @@ private void openMediationResultPopup(String headLine) { } protected void updateConfirmButtonDisableState(boolean isDisabled) { + // By default do nothing. Only overwritten in certain trade steps } protected String getCurrencyName(Trade trade) { From 35cd7ac56b625871a9cacc8d8f2fdb9763b541e4 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 6 Sep 2020 23:09:45 -0500 Subject: [PATCH 042/108] Add check if trade have been dispute to protocol classes --- .../bisq/core/trade/protocol/BuyerAsMakerProtocol.java | 4 ++++ .../bisq/core/trade/protocol/BuyerAsTakerProtocol.java | 4 ++++ .../core/trade/protocol/SellerAsMakerProtocol.java | 4 ++++ .../core/trade/protocol/SellerAsTakerProtocol.java | 4 ++++ .../java/bisq/core/trade/protocol/TradeProtocol.java | 10 ++++++++++ .../pendingtrades/PendingTradesDataModel.java | 2 +- 6 files changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index ec5d5547937..19dde182b7b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -212,6 +212,10 @@ private void handle() { // User clicked the "bank transfer started" button @Override public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + if (wasDisputed(errorMessageHandler)) { + return; + } + if (trade.isDepositConfirmed() && !trade.isFiatSent()) { buyerAsMakerTrade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED); TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade, diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 6665c001ec6..5e4c17d2563 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -237,6 +237,10 @@ private void handle() { // User clicked the "bank transfer started" button @Override public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + if (wasDisputed(errorMessageHandler)) { + return; + } + if (!trade.isFiatSent()) { buyerAsTakerTrade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED); diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index 3a5ae4df9b8..c7620fccf8c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -204,6 +204,10 @@ private void handle(CounterCurrencyTransferStartedMessage tradeMessage, NodeAddr // User clicked the "bank transfer received" button, so we release the funds for payout @Override public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + if (wasDisputed(errorMessageHandler)) { + return; + } + if (trade.getPayoutTx() == null) { sellerAsMakerTrade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsMakerTrade, diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index fd42d66b7c0..6f2f9815c1e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -196,6 +196,10 @@ private void handle(CounterCurrencyTransferStartedMessage tradeMessage, NodeAddr // User clicked the "bank transfer received" button, so we release the funds for payout @Override public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + if (wasDisputed(errorMessageHandler)) { + return; + } + if (trade.getPayoutTx() == null) { sellerAsTakerTrade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); TradeTaskRunner taskRunner = new TradeTaskRunner(sellerAsTakerTrade, diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 4cf78045db2..6c3d29b5d8f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -345,6 +345,16 @@ protected void handleTaskRunnerFault(@Nullable TradeMessage tradeMessage, String cleanup(); } + protected boolean wasDisputed(ErrorMessageHandler errorMessageHandler) { + if (trade.getDisputeState() != Trade.DisputeState.NO_DISPUTE) { + String msg = "Dispute have been opened once. We do not allow anymore to confirm payment by button click."; + log.error(msg); + errorMessageHandler.handleErrorMessage(msg); + return true; + } + return false; + } + private void sendAckMessage(@Nullable TradeMessage tradeMessage, boolean result, @Nullable String errorMessage) { // We complete at initial protocol setup with the setup listener tasks. // Other cases are if we start from an UI event the task runner (payment started, confirmed). diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index fb03b413f8a..6d1a288f118 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -185,7 +185,7 @@ void onSelectItem(PendingTradesListItem item) { } public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - final Trade trade = getTrade(); + Trade trade = getTrade(); checkNotNull(trade, "trade must not be null"); checkArgument(trade instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade"); ((BuyerTrade) trade).onFiatPaymentStarted(resultHandler, errorMessageHandler); From f566e0975e807599db6ea6561eabfe76f51283fc Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 6 Sep 2020 23:22:31 -0500 Subject: [PATCH 043/108] Use a checkArgument to ensure that the methods are not called once a dispute has been opened. This will cause a Runtime exception but that is justified as the caller need to ensure to do the check and do not allow to get to that point. --- .../bisq/core/trade/protocol/BuyerAsMakerProtocol.java | 7 ++++--- .../bisq/core/trade/protocol/BuyerAsTakerProtocol.java | 6 +++--- .../core/trade/protocol/SellerAsMakerProtocol.java | 7 ++++--- .../core/trade/protocol/SellerAsTakerProtocol.java | 6 +++--- .../java/bisq/core/trade/protocol/TradeProtocol.java | 10 ++-------- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index 19dde182b7b..6f475f6734e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -55,6 +55,8 @@ import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; + @Slf4j public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol, MakerProtocol { private final BuyerAsMakerTrade buyerAsMakerTrade; @@ -212,9 +214,8 @@ private void handle() { // User clicked the "bank transfer started" button @Override public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - if (wasDisputed(errorMessageHandler)) { - return; - } + checkArgument(!wasDisputed(), "A call to onFiatPaymentStarted is not permitted once a " + + "dispute has been opened."); if (trade.isDepositConfirmed() && !trade.isFiatSent()) { buyerAsMakerTrade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED); diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 5e4c17d2563..4d17d4c47fa 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -59,6 +59,7 @@ import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -237,9 +238,8 @@ private void handle() { // User clicked the "bank transfer started" button @Override public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - if (wasDisputed(errorMessageHandler)) { - return; - } + checkArgument(!wasDisputed(), "A call to onFiatPaymentStarted is not permitted once a " + + "dispute has been opened."); if (!trade.isFiatSent()) { buyerAsTakerTrade.setState(Trade.State.BUYER_CONFIRMED_IN_UI_FIAT_PAYMENT_INITIATED); diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index c7620fccf8c..29ecde1c005 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -57,6 +57,8 @@ import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; + @Slf4j public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtocol, MakerProtocol { private final SellerAsMakerTrade sellerAsMakerTrade; @@ -204,9 +206,8 @@ private void handle(CounterCurrencyTransferStartedMessage tradeMessage, NodeAddr // User clicked the "bank transfer received" button, so we release the funds for payout @Override public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - if (wasDisputed(errorMessageHandler)) { - return; - } + checkArgument(!wasDisputed(), "A call to onFiatPaymentReceived is not permitted once a " + + "dispute has been opened."); if (trade.getPayoutTx() == null) { sellerAsMakerTrade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index 6f2f9815c1e..8979a61b28c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -56,6 +56,7 @@ import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -196,9 +197,8 @@ private void handle(CounterCurrencyTransferStartedMessage tradeMessage, NodeAddr // User clicked the "bank transfer received" button, so we release the funds for payout @Override public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { - if (wasDisputed(errorMessageHandler)) { - return; - } + checkArgument(!wasDisputed(), "A call to onFiatPaymentReceived is not permitted once a " + + "dispute has been opened."); if (trade.getPayoutTx() == null) { sellerAsTakerTrade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 6c3d29b5d8f..c561b242741 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -345,14 +345,8 @@ protected void handleTaskRunnerFault(@Nullable TradeMessage tradeMessage, String cleanup(); } - protected boolean wasDisputed(ErrorMessageHandler errorMessageHandler) { - if (trade.getDisputeState() != Trade.DisputeState.NO_DISPUTE) { - String msg = "Dispute have been opened once. We do not allow anymore to confirm payment by button click."; - log.error(msg); - errorMessageHandler.handleErrorMessage(msg); - return true; - } - return false; + protected boolean wasDisputed() { + return trade.getDisputeState() != Trade.DisputeState.NO_DISPUTE; } private void sendAckMessage(@Nullable TradeMessage tradeMessage, boolean result, @Nullable String errorMessage) { From 062c52dd5509086281ea07b32a471a2205ce29b1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 6 Sep 2020 23:51:24 -0500 Subject: [PATCH 044/108] Resolve merge conflict in Merge conflict in desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java --- .../pendingtrades/steps/seller/SellerStep3View.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 8652a5c747a..e9136153b1e 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -309,6 +309,12 @@ protected void addContent() { statusLabel = tuple.third; } + @Override + protected void updateConfirmButtonDisableState(boolean isDisabled) { + confirmButton.setDisable(isDisabled); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // Info /////////////////////////////////////////////////////////////////////////////////////////// @@ -528,9 +534,4 @@ private Label createPopoverLabel(String text) { label.setPadding(new Insets(10)); return label; } - - @Override - protected void updateConfirmButtonDisableState(boolean isDisabled) { - confirmButton.setDisable(isDisabled); - } } From b3c31399a77271191e35ecc2fb1c51cbaef630c2 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 8 Sep 2020 16:09:29 -0500 Subject: [PATCH 045/108] Ignore future results in case we had a terminal result already. Avoid that a success result overwrites an earlier failed/error result. --- .../xmr/XmrTxProofRequestsPerTrade.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 73690c6bd57..d7a1e84bc1f 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -147,6 +147,12 @@ public void requestFromAllServices(Consumer resultHandler, F requests.add(request); request.requestFromService(result -> { + // If we ever received an error or failed result we terminate and do not process any + // future result anymore to avoid that we overwrite out state with success. + if (wasTerminated()) { + return; + } + AssetTxProofResult assetTxProofResult; if (trade.isPayoutPublished()) { assetTxProofResult = AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED; @@ -205,7 +211,11 @@ public void requestFromAllServices(Consumer resultHandler, F } } - protected void addSettingsListener(Consumer resultHandler) { + private boolean wasTerminated() { + return requests.isEmpty(); + } + + private void addSettingsListener(Consumer resultHandler) { autoConfirmSettingsListener = () -> { if (!autoConfirmSettings.isEnabled()) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); @@ -214,7 +224,7 @@ protected void addSettingsListener(Consumer resultHandler) { autoConfirmSettings.addListener(autoConfirmSettingsListener); } - protected void setupTradeStateListener(Consumer resultHandler) { + private void setupTradeStateListener(Consumer resultHandler) { tradeStateListener = (observable, oldValue, newValue) -> { if (trade.isPayoutPublished()) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); @@ -223,8 +233,8 @@ protected void setupTradeStateListener(Consumer resultHandle trade.stateProperty().addListener(tradeStateListener); } - protected void setupArbitrationListener(Consumer resultHandler, - ObservableList refundDisputes) { + private void setupArbitrationListener(Consumer resultHandler, + ObservableList refundDisputes) { refundListener = c -> { c.next(); if (c.wasAdded() && isDisputed(c.getAddedSubList())) { @@ -234,8 +244,8 @@ protected void setupArbitrationListener(Consumer resultHandl refundDisputes.addListener(refundListener); } - protected void setupMediationListener(Consumer resultHandler, - ObservableList mediationDisputes) { + private void setupMediationListener(Consumer resultHandler, + ObservableList mediationDisputes) { mediationListener = c -> { c.next(); if (c.wasAdded() && isDisputed(c.getAddedSubList())) { From ca34f8ecad4609a358ebab54116cb8555871ed19 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 8 Sep 2020 16:10:05 -0500 Subject: [PATCH 046/108] Rearrange fields and improve toString method --- .../core/trade/txproof/AssetTxProofResult.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java index 73f22a5ebfd..b49d81b6609 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java @@ -40,6 +40,11 @@ public enum AssetTxProofResult { // Any service failed. Might be that the tx is invalid. FAILED; + // If isTerminal is set it means that we stop the service + @Getter + private final boolean isTerminal; + @Getter + private String details = ""; @Getter private int numSuccessResults; @Getter @@ -48,11 +53,7 @@ public enum AssetTxProofResult { private int numConfirmations; @Getter private int numRequiredConfirmations; - @Getter - private String details = ""; - // If isTerminal is set it means that we stop the service - @Getter - private final boolean isTerminal; + AssetTxProofResult() { this(true); @@ -91,11 +92,12 @@ public AssetTxProofResult details(String details) { @Override public String toString() { return "AssetTxProofResult{" + - "\n numSuccessResults=" + numSuccessResults + - ",\n requiredSuccessResults=" + numRequiredSuccessResults + + "\n details='" + details + '\'' + + ",\n isTerminal=" + isTerminal + + ",\n numSuccessResults=" + numSuccessResults + + ",\n numRequiredSuccessResults=" + numRequiredSuccessResults + ",\n numConfirmations=" + numConfirmations + ",\n numRequiredConfirmations=" + numRequiredConfirmations + - ",\n details='" + details + '\'' + "\n} " + super.toString(); } } From 0ee86b3fcb302635ef7384b7ff4f3363c9af08a9 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 8 Sep 2020 16:22:28 -0500 Subject: [PATCH 047/108] Improve text in UI in case of failure or error --- core/src/main/resources/i18n/displayStrings.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a05da2ccbaf..a3defe069fc 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -596,9 +596,9 @@ portfolio.pending.autoConf.state.PENDING=Success results: {0}/{1}; {2} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. +portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. No auto-confirm possible. # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.FAILED=A service returned with a failure. +portfolio.pending.autoConf.state.FAILED=A service returned with a failure. No auto-confirm possible. portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for at least one blockchain confirmation before starting the payment. portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low. From edad9bd2feb472ab20693e8ef16243c6521f9f6b Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 8 Sep 2020 12:38:10 +0900 Subject: [PATCH 048/108] Replace old Bisq Explorer URLs with new Mempool URLs for the 3 we have --- core/src/main/java/bisq/core/user/Preferences.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 694554b2f74..1eb35580506 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -115,11 +115,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid )); public static final ArrayList BSQ_MAIN_NET_EXPLORERS = new ArrayList<>(Arrays.asList( - new BlockChainExplorer("bsq.ninja (@wiz)", "https://bsq.ninja/tx.html?tx=", "https://bsq.ninja/Address.html?addr="), - new BlockChainExplorer("bsq.sqrrm.net (@sqrrm)", "https://bsq.sqrrm.net/tx.html?tx=", "https://bsq.sqrrm.net/Address.html?addr="), - new BlockChainExplorer("bsq.bisq.services (@devinbileck)", "https://bsq.bisq.services/tx.html?tx=", "https://bsq.bisq.services/Address.html?addr="), + new BlockChainExplorer("mempool.space (@wiz)", "https://mempool.space/bisq/tx/", "https://mempool.space/bisq/address/"), + new BlockChainExplorer("mempool.emzy.de (@emzy)", "https://mempool.emzy.de/bisq/tx/", "https://mempool.emzy.de/bisq/address/"), + new BlockChainExplorer("mempool.bisq.services (@devinbileck)", "https://mempool.bisq.services/bisq/tx/", "https://mempool.bisq.services/bisq/address/"), new BlockChainExplorer("bsq.vante.me (@mrosseel)", "https://bsq.vante.me/tx.html?tx=", "https://bsq.vante.me/Address.html?addr="), - new BlockChainExplorer("bsq.emzy.de (@emzy)", "https://bsq.emzy.de/tx.html?tx=", "https://bsq.emzy.de/Address.html?addr="), + new BlockChainExplorer("bsq.sqrrm.net (@sqrrm)", "https://bsq.sqrrm.net/tx.html?tx=", "https://bsq.sqrrm.net/Address.html?addr="), new BlockChainExplorer("bsq.bisq.cc (@m52go)", "https://bsq.bisq.cc/tx.html?tx=", "https://bsq.bisq.cc/Address.html?addr=") )); From 8949eaa7e22507ebea01f8bdfcab30bf7cc763d8 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 8 Sep 2020 12:44:33 +0900 Subject: [PATCH 049/108] Tweak strings of "BSQ Explorer" -> "Bisq Explorer" and "Bitcoin Explorer" --- core/src/main/resources/i18n/displayStrings.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index a3defe069fc..8be8446f80d 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1090,8 +1090,8 @@ settings.tab.network=Network info settings.tab.about=About setting.preferences.general=General preferences -setting.preferences.explorer=Bitcoin block explorer -setting.preferences.explorer.bsq=BSQ block explorer +setting.preferences.explorer=Bitcoin Explorer +setting.preferences.explorer.bsq=Bisq Explorer setting.preferences.deviation=Max. deviation from market price setting.preferences.avoidStandbyMode=Avoid standby mode setting.preferences.autoConfirmXMR=XMR auto-confirm From dbefddf940733b48aa1a76d7edcab97b0afe435d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 4 Sep 2020 13:46:15 -0500 Subject: [PATCH 050/108] Improve handling of p2pNetworkAndWalletReady The p2pNetworkAndWalletReady MonadicBinding might be removed from GC if its a local variable. I observed that in BisqSetup with a similar setup. It might be an implementation weakness in MonadicBinding (usage of weak references?). A tester reported that he does not see any result, which might be cause that the service never gets the onP2pNetworkAndWalletReady triggered if the MonadicBinding is not there anymore. By removing the listener we need at shutdown we need it anyway as class field (so codacy does not complain anymore). As well added a check if all is already complete to skip the MonadicBinding at all (not expected case in onAllServicesInitialized). --- .../trade/txproof/xmr/XmrTxProofService.java | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 7dc77c1651e..a172649c236 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -82,6 +82,8 @@ public class XmrTxProofService implements AssetTxProofService { private Map> tradeStateListenerMap = new HashMap<>(); private ChangeListener btcPeersListener, btcBlockListener; private BootstrapListener bootstrapListener; + private MonadicBinding p2pNetworkAndWalletReady; + private ChangeListener p2pNetworkAndWalletReadyListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -122,22 +124,34 @@ public void onAllServicesInitialized() { // As we might trigger the payout tx we want to be sure that we are well connected to the Bitcoin network. // onAllServicesInitialized is called once we have received the initial data but we want to have our // hidden service published and upDatedDataResponse received before we start. - MonadicBinding p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped(), hasSufficientBtcPeers(), isBtcBlockDownloadComplete(), - (isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete) -> { - log.info("isP2pBootstrapped={}, hasSufficientBtcPeers={} isBtcBlockDownloadComplete={}", - isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete); - return isP2pBootstrapped && hasSufficientBtcPeers && isBtcBlockDownloadComplete; - }); - - p2pNetworkAndWalletReady.subscribe((observable, oldValue, newValue) -> { - if (newValue) { - onP2pNetworkAndWalletReady(); - } - }); + BooleanProperty isP2pBootstrapped = isP2pBootstrapped(); + BooleanProperty hasSufficientBtcPeers = hasSufficientBtcPeers(); + BooleanProperty isBtcBlockDownloadComplete = isBtcBlockDownloadComplete(); + if (isP2pBootstrapped.get() && hasSufficientBtcPeers.get() && isBtcBlockDownloadComplete.get()) { + onP2pNetworkAndWalletReady(); + } else { + p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete, + (bootstrapped, sufficientPeers, downloadComplete) -> { + log.info("isP2pBootstrapped={}, hasSufficientBtcPeers={} isBtcBlockDownloadComplete={}", + bootstrapped, sufficientPeers, downloadComplete); + return bootstrapped && sufficientPeers && downloadComplete; + }); + + p2pNetworkAndWalletReadyListener = (observable, oldValue, newValue) -> { + if (newValue) { + onP2pNetworkAndWalletReady(); + } + }; + p2pNetworkAndWalletReady.subscribe(p2pNetworkAndWalletReadyListener); + } } @Override public void shutDown() { + if (p2pNetworkAndWalletReady != null) { + p2pNetworkAndWalletReady.removeListener(p2pNetworkAndWalletReadyListener); + } + servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); servicesByTradeId.clear(); } From 5a53cc6a737aadb6d11823aad5fe54a74dcbfc8c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Fri, 4 Sep 2020 13:59:35 -0500 Subject: [PATCH 051/108] Move remove code from shutDown to onP2pNetworkAndWalletReady --- .../bisq/core/trade/txproof/xmr/XmrTxProofService.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index a172649c236..c3ec7de2843 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -148,10 +148,6 @@ public void onAllServicesInitialized() { @Override public void shutDown() { - if (p2pNetworkAndWalletReady != null) { - p2pNetworkAndWalletReady.removeListener(p2pNetworkAndWalletReadyListener); - } - servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); servicesByTradeId.clear(); } @@ -162,6 +158,12 @@ public void shutDown() { /////////////////////////////////////////////////////////////////////////////////////////// private void onP2pNetworkAndWalletReady() { + if (p2pNetworkAndWalletReady != null) { + p2pNetworkAndWalletReady.removeListener(p2pNetworkAndWalletReadyListener); + p2pNetworkAndWalletReady = null; + p2pNetworkAndWalletReadyListener = null; + } + if (!preferences.findAutoConfirmSettings("XMR").isPresent()) { log.error("AutoConfirmSettings is not present"); } From cc7e6559ff4fe15c1275f287a0181480a8932692 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 7 Sep 2020 18:52:33 +0900 Subject: [PATCH 052/108] Use lots of regex to add http or https for XMR explorer API endpoint * If Tor *.onion hostname, use HTTP with Tor proxy * If 127.0.0.1 or localhost, use HTTP without Tor proxy * If LAN address or *.local FQDN, use HTTP without Tor proxy * If any other FQDN hostname, use HTTPS with Tor proxy --- .../trade/txproof/xmr/XmrTxProofRequest.java | 15 +- .../settings/preferences/PreferencesView.java | 38 +++- .../main/java/bisq/desktop/util/GUIUtil.java | 121 ++++++++++ .../java/bisq/desktop/util/GUIUtilTest.java | 214 ++++++++++++++++++ 4 files changed, 381 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index 325538acb67..eb62b09468f 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -163,10 +163,19 @@ public String toString() { this.model = model; httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); - httpClient.setBaseUrl("http://" + model.getServiceAddress()); - if (model.getServiceAddress().matches("^192.*|^localhost.*")) { - log.info("Ignoring Socks5 proxy for local net address: {}", model.getServiceAddress()); + + // localhost, LAN address, or *.local FQDN starts with http://, don't use Tor + if (model.getServiceAddress().regionMatches(0, "http:", 0, 5)) { + httpClient.setBaseUrl(model.getServiceAddress()); httpClient.setIgnoreSocks5Proxy(true); + // any non-onion FQDN starts with https://, use Tor + } else if (model.getServiceAddress().regionMatches(0, "https:", 0, 6)) { + httpClient.setBaseUrl(model.getServiceAddress()); + httpClient.setIgnoreSocks5Proxy(false); + // it's a raw onion so add http:// and use Tor proxy + } else { + httpClient.setBaseUrl("http://" + model.getServiceAddress()); + httpClient.setIgnoreSocks5Proxy(false); } terminated = false; diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index e521dc12526..f9c803be1e9 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -95,6 +95,7 @@ import java.io.File; import java.util.Arrays; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -672,12 +673,41 @@ private void initializeAutoConfirmOptions() { autoConfServiceAddressListener = (observable, oldValue, newValue) -> { if (!newValue.equals(oldValue)) { - List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); + + RegexValidator onionRegex = GUIUtil.onionAddressRegexValidator(); + RegexValidator localhostRegex = GUIUtil.localhostAddressRegexValidator(); + RegexValidator localnetRegex = GUIUtil.localnetAddressRegexValidator(); + + List serviceAddressesRaw = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); + // revert to default service providers when user empties the list - if (serviceAddresses.size() == 1 && serviceAddresses.get(0).isEmpty()) { - serviceAddresses = preferences.getDefaultXmrTxProofServices(); + if (serviceAddressesRaw.size() == 1 && serviceAddressesRaw.get(0).isEmpty()) { + serviceAddressesRaw = preferences.getDefaultXmrTxProofServices(); } - preferences.setAutoConfServiceAddresses("XMR", serviceAddresses); + + // we must always communicate with XMR explorer API securely + // if *.onion hostname, we use Tor normally + // if localhost, LAN address, or *.local FQDN we use HTTP without Tor + // otherwise we enforce https:// for any clearnet FQDN hostname + List serviceAddressesParsed = new ArrayList(); + serviceAddressesRaw.forEach((addr) -> { + addr = addr.replaceAll("http://", "").replaceAll("https://", ""); + if (onionRegex.validate(addr).isValid) { + log.info("Using Tor for onion hostname: {}", addr); + serviceAddressesParsed.add(addr); + } else if (localhostRegex.validate(addr).isValid) { + log.info("Using HTTP without Tor for Loopback address: {}", addr); + serviceAddressesParsed.add("http://" + addr); + } else if (localnetRegex.validate(addr).isValid) { + log.info("Using HTTP without Tor for LAN address: {}", addr); + serviceAddressesParsed.add("http://" + addr); + } else { + log.info("Using HTTPS with Tor for Clearnet address: {}", addr); + serviceAddressesParsed.add("https://" + addr); + } + }); + + preferences.setAutoConfServiceAddresses("XMR", serviceAddressesParsed); } }; diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 375347939a2..5d4d4406ae4 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -1161,6 +1161,127 @@ public static RegexValidator addressRegexValidator() { return regexValidator; } + // checks if valid tor onion hostname with optional port at the end + public static RegexValidator onionAddressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + String onionV2RegexPattern = String.format("[a-zA-Z2-7]{16}\\.onion(?:\\:%1$s)?", portRegexPattern); + String onionV3RegexPattern = String.format("[a-zA-Z2-7]{56}\\.onion(?:\\:%1$s)?", portRegexPattern); + regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)),\\s*)*(?:(?:%1$s)|(?:%2$s))*$", + onionV2RegexPattern, onionV3RegexPattern)); + return regexValidator; + } + + // checks if localhost address, with optional port at the end + public static RegexValidator localhostAddressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + + // match 0 ~ 65535 + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + + // match 127/8 (127.0.0.0 ~ 127.255.255.255) + String localhostIpv4RegexPattern = String.format( + "(?:127\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){2}" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match ::/64 with optional port at the end, i.e. ::1 or [::1]:8081 + String localhostIpv6RegexPattern = "(:((:[0-9a-fA-F]{1,4}){1,4}|:)|)"; + localhostIpv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", localhostIpv6RegexPattern, portRegexPattern); + + // match *.local + String localhostFqdnRegexPattern = String.format("(localhost(?:\\:%1$s)?)", portRegexPattern); + + regexValidator.setPattern(String.format("^(?:(?:(?:%1$s)|(?:%2$s)|(?:%3$s)),\\s*)*(?:(?:%1$s)|(?:%2$s)|(?:%3$s))*$", + localhostIpv4RegexPattern, localhostIpv6RegexPattern, localhostFqdnRegexPattern)); + + return regexValidator; + } + + // checks if local area network address, with optional port at the end + public static RegexValidator localnetAddressRegexValidator() { + RegexValidator regexValidator = new RegexValidator(); + + // match 0 ~ 65535 + String portRegexPattern = "(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])"; + + // match 10/8 (10.0.0.0 ~ 10.255.255.255) + String localnetIpv4RegexPatternA = String.format( + "(?:10\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){2}" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match 172.16/12 (172.16.0.0 ~ 172.31.255.255) + String localnetIpv4RegexPatternB = String.format( + "(?:172\\.)" + + "(?:(?:1[6-9]|2[0-9]|[3][0-1])\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match 192.168/16 (192.168.0.0 ~ 192.168.255.255) + String localnetIpv4RegexPatternC = String.format( + "(?:192\\.)" + + "(?:168\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match 169.254/15 (169.254.0.0 ~ 169.255.255.255) + String autolocalIpv4RegexPattern = String.format( + "(?:169\\.)" + + "(?:(?:254|255)\\.)" + + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.)" + + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "(?:\\:%1$s)?", + portRegexPattern); + + // match fc00::/7 (fc00:: ~ fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff) + String localnetIpv6RegexPattern = "(" + + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){6}[0-9a-fA-F]{1,4}|" + // fd00:2:3:4:5:6:7:8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,7}:|" + // fd00:: fd00:2:3:4:5:6:7:: + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,6}:[0-9a-fA-F]{1,4}|" + // fd00::8 fd00:2:3:4:5:6::8 fd00:2:3:4:5:6::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,5}(:[0-9a-fA-F]{1,4}){1,1}|" + // fd00::7:8 fd00:2:3:4:5::7:8 fd00:2:3:4:5::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,4}(:[0-9a-fA-F]{1,4}){1,2}|" + // fd00::7:8 fd00:2:3:4:5::7:8 fd00:2:3:4:5::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,3}(:[0-9a-fA-F]{1,4}){1,3}|" + // fd00::6:7:8 fd00:2:3:4::6:7:8 fd00:2:3:4::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,2}(:[0-9a-fA-F]{1,4}){1,4}|" + // fd00::5:6:7:8 fd00:2:3::5:6:7:8 fd00:2:3::8 + "([fF][cCdD][0-9a-fA-F]{2}:)([0-9a-fA-F]{1,4}:){0,1}(:[0-9a-fA-F]{1,4}){1,5}|" + // fd00::4:5:6:7:8 fd00:2::4:5:6:7:8 fd00:2::8 + "([fF][cCdD][0-9a-fA-F]{2}:)(:[0-9a-fA-F]{1,4}){1,6}" + // fd00::3:4:5:6:7:8 fd00::3:4:5:6:7:8 fd00::8 + ")"; + + // match fe80::/10 (fe80:: ~ febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff) + String autolocalIpv6RegexPattern = "("+ + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){6}[0-9a-fA-F]{1,4}|" + // fe80:2:3:4:5:6:7:8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,7}:|" + // fe80:: fe80:2:3:4:5:6:7:: + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,6}:[0-9a-fA-F]{1,4}|" + // fe80::8 fe80:2:3:4:5:6::8 fe80:2:3:4:5:6::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,5}(:[0-9a-fA-F]{1,4}){1,1}|" + // fe80::7:8 fe80:2:3:4:5::7:8 fe80:2:3:4:5::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,4}(:[0-9a-fA-F]{1,4}){1,2}|" + // fe80::7:8 fe80:2:3:4:5::7:8 fe80:2:3:4:5::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,3}(:[0-9a-fA-F]{1,4}){1,3}|" + // fe80::6:7:8 fe80:2:3:4::6:7:8 fe80:2:3:4::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,2}(:[0-9a-fA-F]{1,4}){1,4}|" + // fe80::5:6:7:8 fe80:2:3::5:6:7:8 fe80:2:3::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,1}(:[0-9a-fA-F]{1,4}){1,5}|" + // fe80::4:5:6:7:8 fe80:2::4:5:6:7:8 fe80:2::8 + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)(:[0-9a-fA-F]{1,4}){1,6}" + // fe80::3:4:5:6:7:8 fe80::3:4:5:6:7:8 fe80::8 + ")"; + + // allow for brackets with optional port at the end + localnetIpv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", localnetIpv6RegexPattern, portRegexPattern); + + // allow for brackets with optional port at the end + autolocalIpv6RegexPattern = String.format("(?:%1$s)|(?:\\[%1$s\\]\\:%2$s)", autolocalIpv6RegexPattern, portRegexPattern); + + // match *.local + String localFqdnRegexPattern = String.format("(((?!-)[a-zA-Z0-9-]{1,63}(? Date: Tue, 8 Sep 2020 12:08:44 +0900 Subject: [PATCH 053/108] Tweak string for "Service Addresses" to "Monero Explorer URLs" --- core/src/main/resources/i18n/displayStrings.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 8be8446f80d..b44aa54d5ca 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1098,7 +1098,7 @@ setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmRequiredConfirmations=Required confirmations setting.preferences.autoConfirmMaxTradeSize=Max. trade amount (BTC) -setting.preferences.autoConfirmServiceAddresses=Service addresses +setting.preferences.autoConfirmServiceAddresses=Monero Explorer URLs (uses Tor, except for localhost, LAN IP addresses, and *.local hostnames) setting.preferences.deviationToLarge=Values higher than {0}% are not allowed. setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte) setting.preferences.useCustomValue=Use custom value From 8cde8ce16abf9cd3c2811b69d67241e74a48b71b Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 8 Sep 2020 20:20:30 +0900 Subject: [PATCH 054/108] Fix codacy complaining about missing space character --- desktop/src/main/java/bisq/desktop/util/GUIUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 5d4d4406ae4..5fabd2c94a9 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -1256,7 +1256,7 @@ public static RegexValidator localnetAddressRegexValidator() { ")"; // match fe80::/10 (fe80:: ~ febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff) - String autolocalIpv6RegexPattern = "("+ + String autolocalIpv6RegexPattern = "(" + "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){6}[0-9a-fA-F]{1,4}|" + // fe80:2:3:4:5:6:7:8 "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,7}:|" + // fe80:: fe80:2:3:4:5:6:7:: "([fF][eE][8-9a-bA-B][0-9a-fA-F]:)([0-9a-fA-F]{1,4}:){0,6}:[0-9a-fA-F]{1,4}|" + // fe80::8 fe80:2:3:4:5:6::8 fe80:2:3:4:5:6::8 From e7016a3ede3675530f263619888161f4b3fba092 Mon Sep 17 00:00:00 2001 From: wiz Date: Tue, 8 Sep 2020 20:26:23 +0900 Subject: [PATCH 055/108] Rename Monero Explorer to friendlier hostname explorer.monero.wiz.biz --- core/src/main/java/bisq/core/user/Preferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 1eb35580506..26cd9f1e76d 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -125,7 +125,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid private static final ArrayList XMR_TX_PROOF_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList( "xmrblocks.monero.emzy.de", // @emzy - "node77.monero.wiz.biz" // @wiz + "explorer.monero.wiz.biz" // @wiz )); private static final ArrayList XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList( "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion", // @emzy From 38299a9498c5c4a4bc7f00df10da78b2851196e3 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 9 Sep 2020 11:21:43 -0500 Subject: [PATCH 056/108] Remove side effect in setUserName method and add extra handling for the moment we save the account. Only at that moment we check if we need to set the accountId with the value of the userName. We do that in the domain layer to avoid more domain logic code in the UI layer. Fixes bug found at: https://github.com/bisq-network/bisq/pull/4481#pullrequestreview-485066342 --- .../bisq/core/payment/PaymentAccount.java | 5 +++++ .../bisq/core/payment/RevolutAccount.java | 22 ++++++++++++++----- .../payload/RevolutAccountPayload.java | 8 +++++-- core/src/main/java/bisq/core/user/User.java | 2 ++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/payment/PaymentAccount.java b/core/src/main/java/bisq/core/payment/PaymentAccount.java index f63ad2ca8ff..12a9565b710 100644 --- a/core/src/main/java/bisq/core/payment/PaymentAccount.java +++ b/core/src/main/java/bisq/core/payment/PaymentAccount.java @@ -173,4 +173,9 @@ public String getSaltAsHex() { public String getOwnerId() { return paymentAccountPayload.getOwnerId(); } + + public void onAddToUser() { + // We are in the process to get added to the user. This is called just before saving the account and the + // last moment we could apply some special handling if needed (e.g. as it happens for Revolut) + } } diff --git a/core/src/main/java/bisq/core/payment/RevolutAccount.java b/core/src/main/java/bisq/core/payment/RevolutAccount.java index 9e8d41496f6..93191bbac01 100644 --- a/core/src/main/java/bisq/core/payment/RevolutAccount.java +++ b/core/src/main/java/bisq/core/payment/RevolutAccount.java @@ -37,22 +37,34 @@ protected PaymentAccountPayload createPayload() { } public void setUserName(String userName) { - ((RevolutAccountPayload) paymentAccountPayload).setUserName(userName); + revolutAccountPayload().setUserName(userName); } public String getUserName() { - return ((RevolutAccountPayload) paymentAccountPayload).getUserName(); + return (revolutAccountPayload()).getUserName(); } public String getAccountId() { - return ((RevolutAccountPayload) paymentAccountPayload).getAccountId(); + return (revolutAccountPayload()).getAccountId(); } public boolean userNameNotSet() { - return ((RevolutAccountPayload) paymentAccountPayload).userNameNotSet(); + return (revolutAccountPayload()).userNameNotSet(); } public boolean hasOldAccountId() { - return ((RevolutAccountPayload) paymentAccountPayload).hasOldAccountId(); + return (revolutAccountPayload()).hasOldAccountId(); + } + + private RevolutAccountPayload revolutAccountPayload() { + return (RevolutAccountPayload) paymentAccountPayload; + } + + @Override + public void onAddToUser() { + super.onAddToUser(); + + // At save we apply the userName to accountId in case it is empty for backward compatibility + revolutAccountPayload().maybeApplyUserNameToAccountId(); } } diff --git a/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java b/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java index 72548a14d48..19050325cfa 100644 --- a/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java +++ b/core/src/main/java/bisq/core/payment/payload/RevolutAccountPayload.java @@ -81,7 +81,7 @@ private RevolutAccountPayload(String paymentMethod, excludeFromJsonDataMap); this.accountId = accountId; - setUserName(userName); + this.userName = userName; } @Override @@ -169,7 +169,11 @@ public boolean hasOldAccountId() { public void setUserName(String userName) { this.userName = userName; - // We need to set accountId as pre v1.3.8 clients expect the accountId field + } + + // In case it is a new account we need to fill the accountId field to support not-updated traders who are not + // aware of the new userName field + public void maybeApplyUserNameToAccountId() { if (accountId.isEmpty()) { accountId = userName; } diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 75891395d22..d9adac9a09c 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -190,6 +190,8 @@ public boolean hasPaymentAccountForCurrency(TradeCurrency tradeCurrency) { /////////////////////////////////////////////////////////////////////////////////////////// public void addPaymentAccount(PaymentAccount paymentAccount) { + paymentAccount.onAddToUser(); + boolean changed = paymentAccountsAsObservable.add(paymentAccount); setCurrentPaymentAccount(paymentAccount); if (changed) From b7eef8cb7950f04a7d53ea1102608f68637dd4d6 Mon Sep 17 00:00:00 2001 From: Christoph Atteneder Date: Wed, 9 Sep 2020 22:08:58 +0200 Subject: [PATCH 057/108] Update data stores for v1.3.8 --- p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET | 4 ++-- p2p/src/main/resources/DaoStateStore_BTC_MAINNET | 4 ++-- p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET | 4 ++-- p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET b/p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET index 9fd395ea388..5f30589222b 100644 --- a/p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET +++ b/p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54e87ad4ca9e79fff6da1dbdc7f3b1af069e1b43cdcbac388a043bbafd155e64 -size 1825752 +oid sha256:4dc5e4ad24e6c1457d448022a21da47bda3728dc400ad52eeb874889e1b44d8f +size 1914319 diff --git a/p2p/src/main/resources/DaoStateStore_BTC_MAINNET b/p2p/src/main/resources/DaoStateStore_BTC_MAINNET index ab160e8b201..557fc57c2b0 100644 --- a/p2p/src/main/resources/DaoStateStore_BTC_MAINNET +++ b/p2p/src/main/resources/DaoStateStore_BTC_MAINNET @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da8eb98c10af8c9a99a54cac6304fd68d6ff5f90604347f5a5825cf56c4e7533 -size 76161888 +oid sha256:023df03645498b9603bcab4394a4a0361b63a3a39583e4644f16de6625c9390a +size 83140772 diff --git a/p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET b/p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET index 6760f74d3ef..2f22f569462 100644 --- a/p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET +++ b/p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c951d95fb1d9fc6ba7e0d26bc94b12533c44ad12782705779df00d617b00d1f -size 3191819 +oid sha256:72a089f2076a11caa1f3368c5bfc373e0b85ea5e7c828d2ceee7aab39c2a44b5 +size 3630451 diff --git a/p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET b/p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET index 7207f792e35..3e5b730c838 100644 --- a/p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET +++ b/p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5688976d2b682c32e7404138312cc86472050e6c1c77fdfa92d99410efea3468 -size 14981277 +oid sha256:49dac2909810c42144f4a62979ecd588659a9e7a874bb1a6c4867a6602e3b3eb +size 15862823 From 8dcb5dadd5d9390c99550fdef8f92391e79309b8 Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Wed, 9 Sep 2020 21:17:15 -0700 Subject: [PATCH 058/108] Add new Monero explorer xmrblocks.bisq.services with onion As per https://github.com/bisq-network/proposals/issues/257 --- core/src/main/java/bisq/core/user/Preferences.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 26cd9f1e76d..f04b4c6a5f3 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -125,11 +125,13 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid private static final ArrayList XMR_TX_PROOF_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList( "xmrblocks.monero.emzy.de", // @emzy - "explorer.monero.wiz.biz" // @wiz + "explorer.monero.wiz.biz", // @wiz + "xmrblocks.bisq.services" // @devinbileck )); private static final ArrayList XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList( "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion", // @emzy - "wizxmr4hbdxdszqm5rfyqvceyca5jq62ppvtuznasnk66wvhhvgm3uyd.onion" // @wiz + "wizxmr4hbdxdszqm5rfyqvceyca5jq62ppvtuznasnk66wvhhvgm3uyd.onion", // @wiz + "devinxmrwu4jrfq2zmq5kqjpxb44hx7i7didebkwrtvmvygj4uuop2ad.onion" // @devinbileck )); public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; From ab5aee5aab1da780e7a94f56a03b2f948dae5aa5 Mon Sep 17 00:00:00 2001 From: wiz Date: Fri, 11 Sep 2020 00:45:17 +0900 Subject: [PATCH 059/108] Revert Tether USD for now due to various issues --- .../bisq/asset/coins/TetherUSDLiquid.java | 12 --------- .../java/bisq/asset/coins/TetherUSDOmni.java | 12 --------- .../bisq/asset/tokens/TetherUSDERC20.java | 11 -------- .../META-INF/services/bisq.asset.Asset | 3 --- .../core/provider/price/PriceProvider.java | 26 +------------------ 5 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 assets/src/main/java/bisq/asset/coins/TetherUSDLiquid.java delete mode 100644 assets/src/main/java/bisq/asset/coins/TetherUSDOmni.java delete mode 100644 assets/src/main/java/bisq/asset/tokens/TetherUSDERC20.java diff --git a/assets/src/main/java/bisq/asset/coins/TetherUSDLiquid.java b/assets/src/main/java/bisq/asset/coins/TetherUSDLiquid.java deleted file mode 100644 index b5b50a8ad14..00000000000 --- a/assets/src/main/java/bisq/asset/coins/TetherUSDLiquid.java +++ /dev/null @@ -1,12 +0,0 @@ -package bisq.asset.coins; - -import bisq.asset.Coin; -import bisq.asset.LiquidBitcoinAddressValidator; - -public class TetherUSDLiquid extends Coin { - public TetherUSDLiquid() { - // If you add a new USDT variant or want to change this ticker symbol you should also look here: - // core/src/main/java/bisq/core/provider/price/PriceProvider.java:getAll() - super("Tether USD (Liquid Bitcoin)", "L-USDT", new LiquidBitcoinAddressValidator()); - } -} diff --git a/assets/src/main/java/bisq/asset/coins/TetherUSDOmni.java b/assets/src/main/java/bisq/asset/coins/TetherUSDOmni.java deleted file mode 100644 index b0ab624d713..00000000000 --- a/assets/src/main/java/bisq/asset/coins/TetherUSDOmni.java +++ /dev/null @@ -1,12 +0,0 @@ -package bisq.asset.coins; - -import bisq.asset.Base58BitcoinAddressValidator; -import bisq.asset.Coin; - -public class TetherUSDOmni extends Coin { - public TetherUSDOmni() { - // If you add a new USDT variant or want to change this ticker symbol you should also look here: - // core/src/main/java/bisq/core/provider/price/PriceProvider.java:getAll() - super("Tether USD (Omni)", "USDT-O", new Base58BitcoinAddressValidator()); - } -} diff --git a/assets/src/main/java/bisq/asset/tokens/TetherUSDERC20.java b/assets/src/main/java/bisq/asset/tokens/TetherUSDERC20.java deleted file mode 100644 index cb57361a1f4..00000000000 --- a/assets/src/main/java/bisq/asset/tokens/TetherUSDERC20.java +++ /dev/null @@ -1,11 +0,0 @@ -package bisq.asset.tokens; - -import bisq.asset.Erc20Token; - -public class TetherUSDERC20 extends Erc20Token { - public TetherUSDERC20() { - // If you add a new USDT variant or want to change this ticker symbol you should also look here: - // core/src/main/java/bisq/core/provider/price/PriceProvider.java:getAll() - super("Tether USD (ERC20)", "USDT-E"); - } -} diff --git a/assets/src/main/resources/META-INF/services/bisq.asset.Asset b/assets/src/main/resources/META-INF/services/bisq.asset.Asset index a2f77816d66..80a6168463b 100644 --- a/assets/src/main/resources/META-INF/services/bisq.asset.Asset +++ b/assets/src/main/resources/META-INF/services/bisq.asset.Asset @@ -104,8 +104,6 @@ bisq.asset.coins.Spectrecoin bisq.asset.coins.Starwels bisq.asset.coins.SUB1X bisq.asset.coins.TEO -bisq.asset.coins.TetherUSDLiquid -bisq.asset.coins.TetherUSDOmni bisq.asset.coins.TurtleCoin bisq.asset.coins.UnitedCommunityCoin bisq.asset.coins.Unobtanium @@ -125,7 +123,6 @@ bisq.asset.coins.ZeroClassic bisq.asset.tokens.AugmintEuro bisq.asset.tokens.DaiStablecoin bisq.asset.tokens.EtherStone -bisq.asset.tokens.TetherUSDERC20 bisq.asset.tokens.TrueUSD bisq.asset.tokens.USDCoin bisq.asset.tokens.VectorspaceAI diff --git a/core/src/main/java/bisq/core/provider/price/PriceProvider.java b/core/src/main/java/bisq/core/provider/price/PriceProvider.java index bc29ecc3828..d0ac27c2d72 100644 --- a/core/src/main/java/bisq/core/provider/price/PriceProvider.java +++ b/core/src/main/java/bisq/core/provider/price/PriceProvider.java @@ -70,24 +70,7 @@ public Tuple2, Map> getAll() throws IOExc final double price = (Double) treeMap.get("price"); // json uses double for our timestampSec long value... final long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec")); - - // We do not have support for the case of multiChain assets where a common price ticker used for - // different flavours of the asset. It would be quite a bit of effort to add generic support to the - // asset and tradeCurrency classes and to handle it correctly from their many client classes. - // So we decided to hack in the sub-assets as copies of the price and accept the annoyance to see - // 3 different prices for the same master asset. But who knows, maybe prices will differ over time for - // the sub assets so then we are better prepared that way... - if (currencyCode.equals("USDT")) { - addPrice(marketPriceMap, "USDT-O", price, timestampSec); - addPrice(marketPriceMap, "USDT-E", price, timestampSec); - addPrice(marketPriceMap, "L-USDT", price, timestampSec); - } else { - // NON_EXISTING_SYMBOL is returned from service for nto found items - // Sometimes it has post fixes as well so we use a 'contains' check. - if (!currencyCode.contains("NON_EXISTING_SYMBOL")) { - addPrice(marketPriceMap, currencyCode, price, timestampSec); - } - } + marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true)); } catch (Throwable t) { log.error(t.toString()); t.printStackTrace(); @@ -97,13 +80,6 @@ public Tuple2, Map> getAll() throws IOExc return new Tuple2<>(tsMap, marketPriceMap); } - private void addPrice(Map marketPriceMap, - String currencyCode, - double price, - long timestampSec) { - marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true)); - } - public String getBaseUrl() { return httpClient.getBaseUrl(); } From cb9f0f0e448b770b6debd75347918cc023169343 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 9 Apr 2020 17:32:10 +0200 Subject: [PATCH 060/108] Initial tests --- .../core/network/p2p/FileDatabaseTest.java | 334 ++++++++++++++++++ .../AppendOnlyDataStoreService.java | 11 + 2 files changed, 345 insertions(+) create mode 100644 core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java new file mode 100644 index 00000000000..bab439082f4 --- /dev/null +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -0,0 +1,334 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.network.p2p; + +import bisq.core.account.witness.AccountAgeWitness; +import bisq.core.account.witness.AccountAgeWitnessStorageService; +import bisq.core.account.witness.AccountAgeWitnessStore; +import bisq.core.proto.persistable.CorePersistenceProtoResolver; + +import bisq.network.p2p.storage.P2PDataStorage; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; +import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; + +import bisq.common.storage.Storage; + +import java.nio.file.Files; +import java.nio.file.Path; + +import java.io.File; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for migrating to and operating a multi-file file-based data store system + * + * TODO these tests are bound to be changed once Bisq migrates to a real database backend + */ +public class FileDatabaseTest { + // Test fixtures + static final AccountAgeWitness object1 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 1); + static final AccountAgeWitness object2 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, 2); + static final AccountAgeWitness object3 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, 3); + + /** + * TEST CASE: test migration scenario from old database file model to new one + * + * USE CASE: + * We migrate from just having one working-dir database file to having multiple. In + * detail, the user starts with having one database file in her working directory and + * one in her resources. After the update, there is still one database in her resources + * but it is labelled differently. + * + * RESULT + * There are 2 data stores in her working directory, one holding the live database, + * the other one being the exact and readonly copy of the database in resources. Plus, + * the 2 data stores do not share any set of objects. + */ + @Test + public void migrationScenario() throws IOException, InterruptedException { + // setup scenario + // - create one data store in working directory + createDatabase(createFile("AccountAgeWitnessStore"), object1, object2); + // - create one data store in resources with new naming scheme + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object1); + + // simulate bisq startup + final AppendOnlyDataStoreService DUT = loadDatabase(); + + // check result + // - check total number of elements + Assert.assertEquals(2, DUT.getMap().size()); + // - are there 2 data stores in working-dir + Assert.assertEquals(2, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); + // - do the 2 data stores share objects + Assert.assertEquals(1, DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); + } + + /** + * TEST CASE: test Bisq software update scenario + * + * USE CASE: + * Given a newly released Bisq version x, a new differential database is added to + * the resources. The new database has to be copied to the working directory and the + * live database has to be stripped of the entries found in the new database. + * + * RESULT + * There are n + 1 data stores in the users working directory, one holding the live + * database, the other 2 being the exact and readonly copy of the data stores in + * resources. Plus, the data stores in the working dir do not share any set of objects. + */ + @Test + public void updateScenario() throws IOException, InterruptedException { + // setup scenario + // - create two data stores in resources + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); + // - create two data stores in work dir + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile("AccountAgeWitnessStore"), object2, object3); + + // simulate bisq startup + AppendOnlyDataStoreService DUT = loadDatabase(); + + // check result + // - check total number of elements + Assert.assertEquals(3, DUT.getMap().size()); + // - are there 2 data stores in working-dir + Assert.assertEquals(3, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); + // - do the 2 data stores share objects + Assert.assertEquals(2, DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); + } + + /** + * TEST CASE: test clean install of Bisq software app + * + * USE CASE: + * A user has a fresh install of Bisq. Ie. there are two database files in resources + * and none in the working directory. + * + * RESULT + * After startup, there should be 3 data stores in the working directory. + */ + @Test + public void freshInstallScenario() throws IOException, InterruptedException { + // setup scenario + // - create two data stores in resources + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); + + // simulate bisq startup + AppendOnlyDataStoreService DUT = loadDatabase(); + + // check result + // - check total number of elements + Assert.assertEquals(2, DUT.getMap().size()); + // - are there 2 data stores in working-dir + Assert.assertEquals(2, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); + // - do the 3 data stores share objects + Assert.assertEquals(0, DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(-1)).size()); + } + + /** + * TEST CASE: test if getMap still return all elements + * + * USE CASE: + * The app needs all data for . Currently, this is the most + * encountered use case. Future plans, however, aim to reduce the need for this use + * case. + * + * RESULT + * getMap returns all elements stored in the various database files. + */ + @Test + public void getMap() throws IOException, InterruptedException { + // setup scenario + // - create 2 data stores containing historical data and a live database + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0)), object2); + createDatabase(createFile("AccountAgeWitnessStore"), object3); + + // simulate Bisq startup + AppendOnlyDataStoreService DUT = loadDatabase(); + + // check result + // - getMap still gets all the objects? + Assert.assertEquals(3, DUT.getMap().size()); + Assert.assertTrue(DUT.getMap().containsValue(object1)); + Assert.assertTrue(DUT.getMap().containsValue(object2)); + Assert.assertTrue(DUT.getMap().containsValue(object3)); + } + + /** + * TEST CASE: test if getMap filtering works + * + * USE CASE: + * After introducing the database snapshot functionality, the app only requests objects + * which it got since the last database snapshot. + * + * RESULT + * getMap(since x) returns all elements added after snapshot x + */ + @Test + public void getMapSinceFilter() throws IOException, InterruptedException { + // setup scenario + // - create 2 data stores containing historical data and a live database + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0)), object2); + createDatabase(createFile("AccountAgeWitnessStore"), object3); + + // simulate bisq startup + AppendOnlyDataStoreService DUT = loadDatabase(); + + // check result + // - check "live" filter + Collection result = DUT.getMap("since " + getVersion(0)).values(); + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.contains(object3)); + // - check "since version" filter + result = DUT.getMap("since " + getVersion(-1)).values(); + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.contains(object2)); + Assert.assertTrue(result.contains(object3)); + } + + /** + * TEST CASE: test if adding new data only adds to live database + * + * USE CASE: + * Whenever new objects come in, they are going to be persisted to disk so that + * the local database is kept in sync with the distributed database. + * + * RESULT + * map.put should only add data to the live database. Other data stores are read-only! + */ + @Test + public void put() throws IOException, InterruptedException { + // setup scenario + // - create one database containing historical data and a live database + createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0)), object1); + createDatabase(createFile("AccountAgeWitnessStore"), object2); + + // simulate Bisq startup + AppendOnlyDataStoreService DUT = loadDatabase(); + // add data + DUT.put(new P2PDataStorage.ByteArray(object3.getHash()), object3); + + // check result + // - did the live database grow? + Collection result = DUT.getMap("since" + getVersion(0)).values(); + Assert.assertEquals(2, result.size()); + Assert.assertTrue(result.contains(object2)); + Assert.assertTrue(result.contains(object3)); + // - did the historical data data store grow? + Assert.assertEquals(1, DUT.getMap().size() - result.size()); + } + + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////// Utils ///////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + + List files = new ArrayList<>(); + static final File storageDir = new File("src/test/resources"); + + @After + public void cleanup() { + for (File file : files) { + file.delete(); + } + try { + Files.walk(new File(storageDir + "/setup").toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + + File backupDir = new File(storageDir + "/backup"); + if (backupDir.exists()) + Files.walk(backupDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public File createFile(String name) { + File tmp = new File(storageDir + "/" + name); + files.add(tmp); + return tmp; + } + + public void flush() { + try { + boolean done = false; + while (!done) { + Thread.sleep(100); + Set threads = Thread.getAllStackTraces().keySet(); + done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private AppendOnlyDataStoreService doDatabase(File storageDir, String suffix) { + Storage storage = new Storage<>(storageDir, new CorePersistenceProtoResolver(null, null, null, null), null); + AccountAgeWitnessStorageService storageService = new AccountAgeWitnessStorageService(storageDir, storage); + final AppendOnlyDataStoreService protectedDataStoreService = new AppendOnlyDataStoreService(); + protectedDataStoreService.addService(storageService); + protectedDataStoreService.readFromResources(suffix); + return protectedDataStoreService; + } + + private AppendOnlyDataStoreService loadDatabase() { + return doDatabase(storageDir, "_TEST"); + } + + private void createDatabase(File target, + AccountAgeWitness... objects) throws IOException, InterruptedException { + File tmpDirectory = new File("src/test/resources/setup"); + + if (!tmpDirectory.exists()) + tmpDirectory.mkdir(); + + final AppendOnlyDataStoreService protectedDataStoreService = doDatabase(tmpDirectory, "_NOTHING"); + + if (null != objects) + for (AccountAgeWitness object : objects) + protectedDataStoreService.put(new P2PDataStorage.ByteArray(object.getHash()), object); + + final File source = createFile("setup/AccountAgeWitnessStore"); + flush(); + while (!source.exists()) + Thread.sleep(100); + Files.copy(source.toPath(), target.toPath()); + } + + public String getVersion(int offset) { + int start = 5; + return String.valueOf(start + offset); + } +} diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java index c7ea20c9438..3f5d184f825 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -55,6 +56,16 @@ public void readFromResources(String postFix) { services.forEach(service -> service.readFromResources(postFix)); } + /** + * TODO to be implemented + * + * @param filter + * @return + */ + public Map getMap(String filter) { + return new HashMap<>(); + } + public Map getMap() { return services.stream() .flatMap(service -> service.getMap().entrySet().stream()) From 3296db30d22bc1028e460b803c6cf56835f466ee Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Wed, 15 Apr 2020 12:21:42 +0200 Subject: [PATCH 061/108] Version helper uses real Bisq version --- .../java/bisq/core/network/p2p/FileDatabaseTest.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index bab439082f4..a4641e3c521 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -26,6 +26,7 @@ import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; +import bisq.common.app.Version; import bisq.common.storage.Storage; import java.nio.file.Files; @@ -327,8 +328,12 @@ private void createDatabase(File target, Files.copy(source.toPath(), target.toPath()); } + /** + * note that this function assumes a Bisq version format of x.y.z. It will not work with formats other than that eg. x.yy.z + * @param offset + * @return relative version string to the Version.VERSION constant + */ public String getVersion(int offset) { - int start = 5; - return String.valueOf(start + offset); + return new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); } } From e7831d67a505030d4d3e60454480ba865577f4db Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 13:29:49 +0200 Subject: [PATCH 062/108] Tests use static fixtures --- .../core/network/p2p/FileDatabaseTest.java | 128 +++++++++++------- core/src/test/resources/o1 | Bin 0 -> 29 bytes core/src/test/resources/o1o2 | Bin 0 -> 55 bytes core/src/test/resources/o2 | Bin 0 -> 29 bytes core/src/test/resources/o2o3 | Bin 0 -> 55 bytes core/src/test/resources/o3 | Bin 0 -> 29 bytes 6 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 core/src/test/resources/o1 create mode 100644 core/src/test/resources/o1o2 create mode 100644 core/src/test/resources/o2 create mode 100644 core/src/test/resources/o2o3 create mode 100644 core/src/test/resources/o3 diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index a4641e3c521..e19264dae9b 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -36,6 +36,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -56,6 +57,29 @@ public class FileDatabaseTest { static final AccountAgeWitness object2 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, 2); static final AccountAgeWitness object3 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, 3); + /** + * TEST CASE: check if test fixture databases are in place and correct + * + * This does not test any business logic, just makes sure the test setup is correct. + */ + @Test + public void checkTestFixtures() throws IOException, InterruptedException { + checkTestFixturesHelper(object1); + checkTestFixturesHelper(object1, object2); + checkTestFixturesHelper(object2); + checkTestFixturesHelper(object2, object3); + checkTestFixturesHelper(object3); + } + + private void checkTestFixturesHelper(AccountAgeWitness... objects) throws IOException, InterruptedException { + createDatabase(createFile(false, "AccountAgeWitnessStore"), objects); + AppendOnlyDataStoreService DUT = loadDatabase(); + Assert.assertEquals(objects.length, DUT.getMap().size()); + Arrays.stream(objects).forEach(object -> { + Assert.assertTrue(DUT.getMap().containsValue(object)); + }); + } + /** * TEST CASE: test migration scenario from old database file model to new one * @@ -74,9 +98,9 @@ public class FileDatabaseTest { public void migrationScenario() throws IOException, InterruptedException { // setup scenario // - create one data store in working directory - createDatabase(createFile("AccountAgeWitnessStore"), object1, object2); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object1, object2); // - create one data store in resources with new naming scheme - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object1); + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object1); // simulate bisq startup final AppendOnlyDataStoreService DUT = loadDatabase(); @@ -108,11 +132,11 @@ public void migrationScenario() throws IOException, InterruptedException { public void updateScenario() throws IOException, InterruptedException { // setup scenario // - create two data stores in resources - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); // - create two data stores in work dir - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1)), object1); - createDatabase(createFile("AccountAgeWitnessStore"), object2, object3); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object2, object3); // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -141,8 +165,8 @@ public void updateScenario() throws IOException, InterruptedException { public void freshInstallScenario() throws IOException, InterruptedException { // setup scenario // - create two data stores in resources - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -173,9 +197,9 @@ public void freshInstallScenario() throws IOException, InterruptedException { public void getMap() throws IOException, InterruptedException { // setup scenario // - create 2 data stores containing historical data and a live database - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1)), object1); - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0)), object2); - createDatabase(createFile("AccountAgeWitnessStore"), object3); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); // simulate Bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -202,9 +226,9 @@ public void getMap() throws IOException, InterruptedException { public void getMapSinceFilter() throws IOException, InterruptedException { // setup scenario // - create 2 data stores containing historical data and a live database - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(-1)), object1); - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0)), object2); - createDatabase(createFile("AccountAgeWitnessStore"), object3); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -235,8 +259,8 @@ public void getMapSinceFilter() throws IOException, InterruptedException { public void put() throws IOException, InterruptedException { // setup scenario // - create one database containing historical data and a live database - createDatabase(createFile("AccountAgeWitnessStore_" + getVersion(0)), object1); - createDatabase(createFile("AccountAgeWitnessStore"), object2); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object2); // simulate Bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -262,69 +286,71 @@ public void put() throws IOException, InterruptedException { @After public void cleanup() { + try { + boolean done = false; + while (!done) { + Thread.sleep(100); + Set threads = Thread.getAllStackTraces().keySet(); + done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + for (File file : files) { file.delete(); } - try { - Files.walk(new File(storageDir + "/setup").toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + try { File backupDir = new File(storageDir + "/backup"); if (backupDir.exists()) Files.walk(backupDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); } catch (IOException e) { e.printStackTrace(); } + + Arrays.stream(storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore"))).forEach(s -> { + new File(storageDir + File.separator + s).delete(); + }); } - public File createFile(String name) { - File tmp = new File(storageDir + "/" + name); + public File createFile(boolean isResourceFile, String name) { + File tmp; + if (isResourceFile) + tmp = new File(ClassLoader.getSystemClassLoader().getResource("").getFile() + File.separator + name); + else + tmp = new File(storageDir + File.separator + name); + files.add(tmp); return tmp; } - public void flush() { - try { - boolean done = false; - while (!done) { - Thread.sleep(100); - Set threads = Thread.getAllStackTraces().keySet(); - done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - private AppendOnlyDataStoreService doDatabase(File storageDir, String suffix) { + private AppendOnlyDataStoreService loadDatabase() { Storage storage = new Storage<>(storageDir, new CorePersistenceProtoResolver(null, null, null, null), null); AccountAgeWitnessStorageService storageService = new AccountAgeWitnessStorageService(storageDir, storage); final AppendOnlyDataStoreService protectedDataStoreService = new AppendOnlyDataStoreService(); protectedDataStoreService.addService(storageService); - protectedDataStoreService.readFromResources(suffix); + protectedDataStoreService.readFromResources("_TEST"); return protectedDataStoreService; } - private AppendOnlyDataStoreService loadDatabase() { - return doDatabase(storageDir, "_TEST"); - } - private void createDatabase(File target, - AccountAgeWitness... objects) throws IOException, InterruptedException { - File tmpDirectory = new File("src/test/resources/setup"); + AccountAgeWitness... objects) throws IOException { + + if (null == objects) { + return; + } - if (!tmpDirectory.exists()) - tmpDirectory.mkdir(); + String filename = ""; + filename += Arrays.asList(objects).contains(object1) ? "o1" : ""; + filename += Arrays.asList(objects).contains(object2) ? "o2" : ""; + filename += Arrays.asList(objects).contains(object3) ? "o3" : ""; - final AppendOnlyDataStoreService protectedDataStoreService = doDatabase(tmpDirectory, "_NOTHING"); + File source = new File(storageDir + File.separator + filename); - if (null != objects) - for (AccountAgeWitness object : objects) - protectedDataStoreService.put(new P2PDataStorage.ByteArray(object.getHash()), object); + if (target.exists()) + target.delete(); - final File source = createFile("setup/AccountAgeWitnessStore"); - flush(); - while (!source.exists()) - Thread.sleep(100); Files.copy(source.toPath(), target.toPath()); } diff --git a/core/src/test/resources/o1 b/core/src/test/resources/o1 new file mode 100644 index 0000000000000000000000000000000000000000..0ae8fd6a236386077f7c4a44c99680bec623e828 GIT binary patch literal 29 Tcmb2ilH!u!5@Emui~@`R7ux{y literal 0 HcmV?d00001 diff --git a/core/src/test/resources/o1o2 b/core/src/test/resources/o1o2 new file mode 100644 index 0000000000000000000000000000000000000000..e53dace8c194366a01c43cd625d233491875cf33 GIT binary patch literal 55 YcmXrZGU1Zo5@Emui~@`}WS9h)071b4djJ3c literal 0 HcmV?d00001 diff --git a/core/src/test/resources/o2 b/core/src/test/resources/o2 new file mode 100644 index 0000000000000000000000000000000000000000..f816dfe4faa2d3b41dd6dc9b91c1450e38f51427 GIT binary patch literal 29 Tcmb2ilH!u!5@EmuOae>*7vBK& literal 0 HcmV?d00001 diff --git a/core/src/test/resources/o2o3 b/core/src/test/resources/o2o3 new file mode 100644 index 0000000000000000000000000000000000000000..5696c60944774a359cf7a040aab73a58878bdedd GIT binary patch literal 55 YcmXrZGU1Zo5@EmuOae?eWS9k*0YUu%e*gdg literal 0 HcmV?d00001 diff --git a/core/src/test/resources/o3 b/core/src/test/resources/o3 new file mode 100644 index 0000000000000000000000000000000000000000..655c449851c17b3947c805a5398ad05088623e5f GIT binary patch literal 29 Tcmb2ilH!u!5@Emu%mT~+7vli; literal 0 HcmV?d00001 From 581286e15dd2c951749d8b8193c3ca28d9011742 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 13:31:12 +0200 Subject: [PATCH 063/108] Migration test succeeds --- .../AccountAgeWitnessStorageService.java | 105 ++++++++++++++++-- .../AppendOnlyDataStoreService.java | 5 +- .../storage/persistence/MapStoreService.java | 11 ++ 3 files changed, 111 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index bc261e7efe5..6700a789b80 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -21,23 +21,31 @@ import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.MapStoreService; +import bisq.common.app.Version; import bisq.common.config.Config; +import bisq.common.storage.FileUtil; +import bisq.common.storage.ResourceNotFoundException; import bisq.common.storage.Storage; import javax.inject.Named; import javax.inject.Inject; +import java.nio.file.Paths; + import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import static com.google.common.base.Preconditions.checkArgument; - @Slf4j public class AccountAgeWitnessStorageService extends MapStoreService { private static final String FILE_NAME = "AccountAgeWitnessStore"; + private HashMap history; /////////////////////////////////////////////////////////////////////////////////////////// @@ -61,7 +69,26 @@ public String getFileName() { @Override public Map getMap() { - return store.getMap(); + HashMap result = new HashMap<>(); + result.putAll(store.getMap()); + history.forEach((s, accountAgeWitnessStore) -> result.putAll(accountAgeWitnessStore.getMap())); + + return result; + } + + @Override + public Map getMap(String filter) { + HashMap result = new HashMap<>(); + result.putAll(store.getMap()); + + // TODO do a proper language, possibly classes + if (filter.startsWith("since ")) { + filter = filter.replace("since ", ""); + if (!filter.equals(Version.VERSION)) + result.putAll(history.get(filter).getMap()); + } + + return result; } @Override @@ -74,16 +101,78 @@ public boolean canHandle(PersistableNetworkPayload payload) { // Protected /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void readFromResources(String postFix) { + // check Version.VERSION and see if we have latest r/o data store file in working directory + if (!new File(absolutePathOfStorageDir + File.separator + getFileName() + "_" + Version.VERSION).exists()) + makeFileFromResourceFile(postFix); // if we have the latest file, we are good, else do stuff // TODO are we? + + // load stores/storage + + } + @Override protected AccountAgeWitnessStore createStore() { return new AccountAgeWitnessStore(); } + private AccountAgeWitnessStore readStore(String name) { + AccountAgeWitnessStore store = storage.initAndGetPersistedWithFileName(name, 100); + if (store != null) { + log.info("{}: size of {}: {} MB", this.getClass().getSimpleName(), + storage.getClass().getSimpleName(), + store.toProtoMessage().toByteArray().length / 1_000_000D); + } else { + store = createStore(); + } + + return store; + } + @Override - protected void readStore() { - super.readStore(); - checkArgument(store instanceof AccountAgeWitnessStore, - "Store is not instance of AccountAgeWitnessStore. That can happen if the ProtoBuffer " + - "file got changed. We cleared the data store and recreated it again."); + protected void makeFileFromResourceFile(String postFix) { + File dbDir = new File(absolutePathOfStorageDir); + if (!dbDir.exists() && !dbDir.mkdir()) + log.warn("make dir failed.\ndbDir=" + dbDir.getAbsolutePath()); + + // check resources for files + File resourceDir = new File(ClassLoader.getSystemClassLoader().getResource("").getFile()); + List resourceFiles = Arrays.asList(resourceDir.list((dir, name) -> name.startsWith(getFileName()))).stream().map(s -> new File(s)).collect(Collectors.toList()); + + // if not, copy and split + resourceFiles.forEach(file -> { + final File destinationFile = new File(Paths.get(absolutePathOfStorageDir, file.getName().replace(postFix, "")).toString()); + final String resourceFileName = file.getName(); + if (!destinationFile.exists()) { + try { + log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); + FileUtil.resourceToFile(resourceFileName, destinationFile); + } catch (ResourceNotFoundException e) { + log.info("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet."); + } catch (Throwable e) { + log.error("Could not copy resourceFile " + resourceFileName + " to " + + destinationFile.getAbsolutePath() + ".\n" + e.getMessage()); + e.printStackTrace(); + } + } else { + log.debug(file.getName() + " file exists already."); + } + }); + + // split + // - get all + history = new HashMap<>(); + store = readStore(getFileName()); + resourceFiles.forEach(file -> { + AccountAgeWitnessStore tmp = readStore(file.getName().replace(postFix, "")); + history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + // - subtract all that is in resource files + store.getMap().keySet().removeAll(tmp.getMap().keySet()); + }); + + // - create new file with leftovers + storage.queueUpForSave(); + } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java index 3f5d184f825..2a4155bccf5 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java @@ -25,7 +25,6 @@ import javax.inject.Inject; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -63,7 +62,9 @@ public void readFromResources(String postFix) { * @return */ public Map getMap(String filter) { - return new HashMap<>(); + return services.stream() + .flatMap(service -> service.getMap(filter).entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } public Map getMap() { diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java index 67088799c4a..036685c6fd0 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java @@ -54,6 +54,17 @@ public MapStoreService(File storageDir, Storage storage) { public abstract Map getMap(); + /** + * reroute the filtered query to a non-filtered by default. + *

+ * only stores that are shipped with a release use it anyways and they have to override + * the method to take advantage of the feature. + *

+ */ + public Map getMap(String filter) { + return getMap(); + } + public abstract boolean canHandle(R payload); void put(P2PDataStorage.ByteArray hash, R payload) { From fc7a43dcac9fbe007e842ea9cf3846fb203c121a Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 14:06:06 +0200 Subject: [PATCH 064/108] GetMap test succeeds --- .../witness/AccountAgeWitnessStorageService.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index 6700a789b80..72f287ef4e6 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -107,9 +107,18 @@ protected void readFromResources(String postFix) { // check Version.VERSION and see if we have latest r/o data store file in working directory if (!new File(absolutePathOfStorageDir + File.separator + getFileName() + "_" + Version.VERSION).exists()) makeFileFromResourceFile(postFix); // if we have the latest file, we are good, else do stuff // TODO are we? - - // load stores/storage - + else { + // load stores/storage + File dbDir = new File(absolutePathOfStorageDir); + List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName()))).stream().map(s -> new File(s)).collect(Collectors.toList()); + + history = new HashMap<>(); + store = readStore(getFileName()); + resourceFiles.forEach(file -> { + AccountAgeWitnessStore tmp = readStore(file.getName().replace(postFix, "")); + history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + }); + } } @Override From d111d8ce0eb443b0f0832cae3c43aa6dcc903d0b Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 15:43:54 +0200 Subject: [PATCH 065/108] GetMapSinceFilter works --- .../account/witness/AccountAgeWitnessStorageService.java | 8 +++++--- .../test/java/bisq/core/network/p2p/FileDatabaseTest.java | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index 72f287ef4e6..36219c9347c 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -84,8 +84,10 @@ public Map getMap(String fi // TODO do a proper language, possibly classes if (filter.startsWith("since ")) { filter = filter.replace("since ", ""); - if (!filter.equals(Version.VERSION)) - result.putAll(history.get(filter).getMap()); + if (!filter.equals(Version.VERSION)) { + String finalFilter = filter; + history.entrySet().stream().filter(entry -> Integer.valueOf(entry.getKey().replace(".", "")) > Integer.valueOf(finalFilter.replace(".", ""))).forEach(entry -> result.putAll(entry.getValue().getMap())); + } } return result; @@ -110,7 +112,7 @@ protected void readFromResources(String postFix) { else { // load stores/storage File dbDir = new File(absolutePathOfStorageDir); - List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName()))).stream().map(s -> new File(s)).collect(Collectors.toList()); + List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName() + "_"))).stream().map(s -> new File(s)).collect(Collectors.toList()); history = new HashMap<>(); store = readStore(getFileName()); diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index e19264dae9b..d37d1036cc5 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -240,7 +240,7 @@ public void getMapSinceFilter() throws IOException, InterruptedException { Assert.assertTrue(result.contains(object3)); // - check "since version" filter result = DUT.getMap("since " + getVersion(-1)).values(); - Assert.assertEquals(1, result.size()); + Assert.assertEquals(2, result.size()); Assert.assertTrue(result.contains(object2)); Assert.assertTrue(result.contains(object3)); } From 6eda9a91162aa6c6ea58d5140ed7d069242c9256 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 15:47:01 +0200 Subject: [PATCH 066/108] updateScenario works --- .../src/test/java/bisq/core/network/p2p/FileDatabaseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index d37d1036cc5..7f14f7c1b71 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -147,8 +147,8 @@ public void updateScenario() throws IOException, InterruptedException { // - are there 2 data stores in working-dir Assert.assertEquals(3, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); // - do the 2 data stores share objects - Assert.assertEquals(2, DUT.getMap("since " + getVersion(0)).size()); - Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(1, DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(2, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); } /** From ff53f516866a448ed7c89b3ec8ea8a9ff55b78d6 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 15:50:06 +0200 Subject: [PATCH 067/108] freshInstall scenario works --- .../src/test/java/bisq/core/network/p2p/FileDatabaseTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 7f14f7c1b71..7f66afb4801 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -178,8 +178,8 @@ public void freshInstallScenario() throws IOException, InterruptedException { Assert.assertEquals(2, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); // - do the 3 data stores share objects Assert.assertEquals(0, DUT.getMap("since " + getVersion(0)).size()); - Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); - Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(-1)).size()); + Assert.assertEquals(1, DUT.getMap("since " + getVersion(-1)).size()); + Assert.assertEquals(2, DUT.getMap("since " + getVersion(-2)).size()); } /** From 49731f9d738bfabba7101a593d5807edbb7ad220 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 16 Apr 2020 16:01:56 +0200 Subject: [PATCH 068/108] put scenario works --- .../witness/AccountAgeWitnessStorageService.java | 14 ++++++++++++++ .../bisq/core/network/p2p/FileDatabaseTest.java | 2 +- .../p2p/storage/persistence/MapStoreService.java | 4 ++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index 36219c9347c..e3e63cfaf77 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -67,6 +67,20 @@ public String getFileName() { return FILE_NAME; } + @Override + protected void put(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) { + store.getMap().put(hash, payload); + persist(); + } + + @Override + protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, + PersistableNetworkPayload payload) { + PersistableNetworkPayload previous = store.getMap().putIfAbsent(hash, payload); + persist(); + return previous; + } + @Override public Map getMap() { HashMap result = new HashMap<>(); diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 7f66afb4801..5f50f7b252a 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -269,7 +269,7 @@ public void put() throws IOException, InterruptedException { // check result // - did the live database grow? - Collection result = DUT.getMap("since" + getVersion(0)).values(); + Collection result = DUT.getMap("since " + getVersion(0)).values(); Assert.assertEquals(2, result.size()); Assert.assertTrue(result.contains(object2)); Assert.assertTrue(result.contains(object3)); diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java index 036685c6fd0..364c17e0a9e 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/MapStoreService.java @@ -67,12 +67,12 @@ public Map getMap(String filter) { public abstract boolean canHandle(R payload); - void put(P2PDataStorage.ByteArray hash, R payload) { + protected void put(P2PDataStorage.ByteArray hash, R payload) { getMap().put(hash, payload); persist(); } - R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) { + protected R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) { R previous = getMap().putIfAbsent(hash, payload); persist(); return previous; From e123a0b824571bc72e3bd8ef3135fa32eed866a2 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 17 Apr 2020 12:53:00 +0200 Subject: [PATCH 069/108] Introduce SplitStore and Service to handle BL --- .../AccountAgeWitnessStorageService.java | 52 ++--------- .../witness/AccountAgeWitnessStore.java | 5 +- .../p2p/storage/persistence/SplitStore.java | 19 ++++ .../persistence/SplitStoreService.java | 90 +++++++++++++++++++ 4 files changed, 116 insertions(+), 50 deletions(-) create mode 100644 p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStore.java create mode 100644 p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index e3e63cfaf77..ad0499d3f2f 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -17,9 +17,9 @@ package bisq.core.account.witness; -import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; -import bisq.network.p2p.storage.persistence.MapStoreService; +import bisq.network.p2p.storage.persistence.SplitStore; +import bisq.network.p2p.storage.persistence.SplitStoreService; import bisq.common.app.Version; import bisq.common.config.Config; @@ -37,15 +37,13 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; @Slf4j -public class AccountAgeWitnessStorageService extends MapStoreService { +public class AccountAgeWitnessStorageService extends SplitStoreService { private static final String FILE_NAME = "AccountAgeWitnessStore"; - private HashMap history; /////////////////////////////////////////////////////////////////////////////////////////// @@ -67,46 +65,6 @@ public String getFileName() { return FILE_NAME; } - @Override - protected void put(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) { - store.getMap().put(hash, payload); - persist(); - } - - @Override - protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, - PersistableNetworkPayload payload) { - PersistableNetworkPayload previous = store.getMap().putIfAbsent(hash, payload); - persist(); - return previous; - } - - @Override - public Map getMap() { - HashMap result = new HashMap<>(); - result.putAll(store.getMap()); - history.forEach((s, accountAgeWitnessStore) -> result.putAll(accountAgeWitnessStore.getMap())); - - return result; - } - - @Override - public Map getMap(String filter) { - HashMap result = new HashMap<>(); - result.putAll(store.getMap()); - - // TODO do a proper language, possibly classes - if (filter.startsWith("since ")) { - filter = filter.replace("since ", ""); - if (!filter.equals(Version.VERSION)) { - String finalFilter = filter; - history.entrySet().stream().filter(entry -> Integer.valueOf(entry.getKey().replace(".", "")) > Integer.valueOf(finalFilter.replace(".", ""))).forEach(entry -> result.putAll(entry.getValue().getMap())); - } - } - - return result; - } - @Override public boolean canHandle(PersistableNetworkPayload payload) { return payload instanceof AccountAgeWitness; @@ -131,7 +89,7 @@ protected void readFromResources(String postFix) { history = new HashMap<>(); store = readStore(getFileName()); resourceFiles.forEach(file -> { - AccountAgeWitnessStore tmp = readStore(file.getName().replace(postFix, "")); + SplitStore tmp = readStore(file.getName().replace(postFix, "")); history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); }); } @@ -190,7 +148,7 @@ protected void makeFileFromResourceFile(String postFix) { history = new HashMap<>(); store = readStore(getFileName()); resourceFiles.forEach(file -> { - AccountAgeWitnessStore tmp = readStore(file.getName().replace(postFix, "")); + SplitStore tmp = readStore(file.getName().replace(postFix, "")); history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); // - subtract all that is in resource files store.getMap().keySet().removeAll(tmp.getMap().keySet()); diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java index 4731cb9e624..dfee5c2fb12 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java @@ -19,8 +19,7 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; - -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.network.p2p.storage.persistence.SplitStore; import com.google.protobuf.Message; @@ -39,7 +38,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class AccountAgeWitnessStore implements ThreadedPersistableEnvelope { +public class AccountAgeWitnessStore extends SplitStore { @Getter private Map map = new ConcurrentHashMap<>(); diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStore.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStore.java new file mode 100644 index 00000000000..dbf0acb9082 --- /dev/null +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStore.java @@ -0,0 +1,19 @@ +package bisq.network.p2p.storage.persistence; + +import bisq.network.p2p.storage.P2PDataStorage; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; + +import bisq.common.proto.persistable.ThreadedPersistableEnvelope; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import lombok.Getter; + +/** + * Goes with {@link SplitStoreService}. + */ +public abstract class SplitStore implements ThreadedPersistableEnvelope { + @Getter + protected Map map = new ConcurrentHashMap<>(); +} diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java new file mode 100644 index 00000000000..e4b4d1b4215 --- /dev/null +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -0,0 +1,90 @@ +package bisq.network.p2p.storage.persistence; + +import bisq.network.p2p.storage.P2PDataStorage; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; + +import bisq.common.app.Version; +import bisq.common.storage.Storage; + +import java.io.File; + +import java.util.HashMap; +import java.util.Map; + +/** + * Has business logic to operate data stores which are spread across multiple files.

+ * + *

Use Case

+ * Startup requires to send all object keys to the network in order to get the new data. + * However, data stores got quite big over time and will grow even faster in the future. + * With multi-file data stores, we can query for new objects since the last snapshot (ie. + * the last release) and subsequently send "I am Bisq vx.y.z and I have these new objects" + * to the network and the network can respond accordingly.



+ * + *

Features

+ * In order to only get a specific part of all the objects available in the complete data + * store, the original single-file data store had to be split up and this class and + * {@link SplitStore} are there to handle the business logic needed for + *
    + *
  • migrating to the new and shiny multi-file data store
  • + *
  • shoveling around data in case Bisq gets updated to a new version
  • + *
  • takes care of setting up a fresh Bisq install
  • + *
  • makes sure that historical data cannot be altered easily
  • + *
  • adding data to the store will only amend the live store
  • + *
  • takes care of keeping the legacy API functional
  • + *
  • adds the feature of filtering object queries by Bisq release
  • + *
+ *

+ * + *

Further reading

+ */ +public abstract class SplitStoreService extends MapStoreService { + protected HashMap history; + + public SplitStoreService(File storageDir, Storage storage) { + super(storageDir, storage); + } + + @Override + protected void put(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) { + store.getMap().put(hash, payload); + persist(); + } + + @Override + protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, + PersistableNetworkPayload payload) { + PersistableNetworkPayload previous = store.getMap().putIfAbsent(hash, payload); + persist(); + return previous; + } + + + @Override + public Map getMap() { + HashMap result = new HashMap<>(); + result.putAll(store.getMap()); + history.forEach((s, store) -> result.putAll(store.getMap())); + + return result; + } + + @Override + public Map getMap(String filter) { + HashMap result = new HashMap<>(); + result.putAll(store.getMap()); + + // TODO do a proper language, possibly classes + if (filter.startsWith("since ")) { + filter = filter.replace("since ", ""); + if (!filter.equals(Version.VERSION)) { + String finalFilter = filter; + history.entrySet().stream().filter(entry -> Integer.valueOf(entry.getKey().replace(".", "")) > Integer.valueOf(finalFilter.replace(".", ""))).forEach(entry -> result.putAll(entry.getValue().getMap())); + } + } + + return result; + } +} From da5d41b55f057b0054727b72081eff8367809008 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 17 Apr 2020 13:38:32 +0200 Subject: [PATCH 070/108] Moved file handling logic to SplitStoreService --- .../AccountAgeWitnessStorageService.java | 90 ------------------- .../witness/AccountAgeWitnessStore.java | 7 -- .../persistence/SplitStoreService.java | 88 ++++++++++++++++++ 3 files changed, 88 insertions(+), 97 deletions(-) diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java index ad0499d3f2f..625778290b5 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStorageService.java @@ -18,27 +18,16 @@ package bisq.core.account.witness; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; -import bisq.network.p2p.storage.persistence.SplitStore; import bisq.network.p2p.storage.persistence.SplitStoreService; -import bisq.common.app.Version; import bisq.common.config.Config; -import bisq.common.storage.FileUtil; -import bisq.common.storage.ResourceNotFoundException; import bisq.common.storage.Storage; import javax.inject.Named; import javax.inject.Inject; -import java.nio.file.Paths; - import java.io.File; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.stream.Collectors; - import lombok.extern.slf4j.Slf4j; @Slf4j @@ -75,87 +64,8 @@ public boolean canHandle(PersistableNetworkPayload payload) { // Protected /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - protected void readFromResources(String postFix) { - // check Version.VERSION and see if we have latest r/o data store file in working directory - if (!new File(absolutePathOfStorageDir + File.separator + getFileName() + "_" + Version.VERSION).exists()) - makeFileFromResourceFile(postFix); // if we have the latest file, we are good, else do stuff // TODO are we? - else { - // load stores/storage - File dbDir = new File(absolutePathOfStorageDir); - List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName() + "_"))).stream().map(s -> new File(s)).collect(Collectors.toList()); - - history = new HashMap<>(); - store = readStore(getFileName()); - resourceFiles.forEach(file -> { - SplitStore tmp = readStore(file.getName().replace(postFix, "")); - history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); - }); - } - } - @Override protected AccountAgeWitnessStore createStore() { return new AccountAgeWitnessStore(); } - - private AccountAgeWitnessStore readStore(String name) { - AccountAgeWitnessStore store = storage.initAndGetPersistedWithFileName(name, 100); - if (store != null) { - log.info("{}: size of {}: {} MB", this.getClass().getSimpleName(), - storage.getClass().getSimpleName(), - store.toProtoMessage().toByteArray().length / 1_000_000D); - } else { - store = createStore(); - } - - return store; - } - - @Override - protected void makeFileFromResourceFile(String postFix) { - File dbDir = new File(absolutePathOfStorageDir); - if (!dbDir.exists() && !dbDir.mkdir()) - log.warn("make dir failed.\ndbDir=" + dbDir.getAbsolutePath()); - - // check resources for files - File resourceDir = new File(ClassLoader.getSystemClassLoader().getResource("").getFile()); - List resourceFiles = Arrays.asList(resourceDir.list((dir, name) -> name.startsWith(getFileName()))).stream().map(s -> new File(s)).collect(Collectors.toList()); - - // if not, copy and split - resourceFiles.forEach(file -> { - final File destinationFile = new File(Paths.get(absolutePathOfStorageDir, file.getName().replace(postFix, "")).toString()); - final String resourceFileName = file.getName(); - if (!destinationFile.exists()) { - try { - log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); - FileUtil.resourceToFile(resourceFileName, destinationFile); - } catch (ResourceNotFoundException e) { - log.info("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet."); - } catch (Throwable e) { - log.error("Could not copy resourceFile " + resourceFileName + " to " + - destinationFile.getAbsolutePath() + ".\n" + e.getMessage()); - e.printStackTrace(); - } - } else { - log.debug(file.getName() + " file exists already."); - } - }); - - // split - // - get all - history = new HashMap<>(); - store = readStore(getFileName()); - resourceFiles.forEach(file -> { - SplitStore tmp = readStore(file.getName().replace(postFix, "")); - history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); - // - subtract all that is in resource files - store.getMap().keySet().removeAll(tmp.getMap().keySet()); - }); - - // - create new file with leftovers - storage.queueUpForSave(); - - } } diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java index dfee5c2fb12..5321ce959b0 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessStore.java @@ -18,17 +18,13 @@ package bisq.core.account.witness; import bisq.network.p2p.storage.P2PDataStorage; -import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.SplitStore; import com.google.protobuf.Message; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -39,9 +35,6 @@ */ @Slf4j public class AccountAgeWitnessStore extends SplitStore { - @Getter - private Map map = new ConcurrentHashMap<>(); - AccountAgeWitnessStore() { } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index e4b4d1b4215..a0544b1716d 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -4,12 +4,21 @@ import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.common.app.Version; +import bisq.common.storage.FileUtil; +import bisq.common.storage.ResourceNotFoundException; import bisq.common.storage.Storage; +import java.nio.file.Paths; + import java.io.File; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; /** * Has business logic to operate data stores which are spread across multiple files.

@@ -40,6 +49,7 @@ *

  • project description
  • *

    */ +@Slf4j public abstract class SplitStoreService extends MapStoreService { protected HashMap history; @@ -87,4 +97,82 @@ public Map getMap(String fi return result; } + + @Override + protected void readFromResources(String postFix) { + // check Version.VERSION and see if we have latest r/o data store file in working directory + if (!new File(absolutePathOfStorageDir + File.separator + getFileName() + "_" + Version.VERSION).exists()) + makeFileFromResourceFile(postFix); // if we have the latest file, we are good, else do stuff // TODO are we? + else { + // load stores/storage + File dbDir = new File(absolutePathOfStorageDir); + List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName() + "_"))).stream().map(s -> new File(s)).collect(Collectors.toList()); + + history = new HashMap<>(); + store = readStore(getFileName()); + resourceFiles.forEach(file -> { + SplitStore tmp = readStore(file.getName().replace(postFix, "")); + history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + }); + } + } + + private T readStore(String name) { + T store = storage.initAndGetPersistedWithFileName(name, 100); + if (store != null) { + log.info("{}: size of {}: {} MB", this.getClass().getSimpleName(), + storage.getClass().getSimpleName(), + store.toProtoMessage().toByteArray().length / 1_000_000D); + } else { + store = createStore(); + } + + return store; + } + + @Override + protected void makeFileFromResourceFile(String postFix) { + File dbDir = new File(absolutePathOfStorageDir); + if (!dbDir.exists() && !dbDir.mkdir()) + log.warn("make dir failed.\ndbDir=" + dbDir.getAbsolutePath()); + + // check resources for files + File resourceDir = new File(ClassLoader.getSystemClassLoader().getResource("").getFile()); + List resourceFiles = Arrays.asList(resourceDir.list((dir, name) -> name.startsWith(getFileName()))).stream().map(s -> new File(s)).collect(Collectors.toList()); + + // if not, copy and split + resourceFiles.forEach(file -> { + final File destinationFile = new File(Paths.get(absolutePathOfStorageDir, file.getName().replace(postFix, "")).toString()); + final String resourceFileName = file.getName(); + if (!destinationFile.exists()) { + try { + log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); + FileUtil.resourceToFile(resourceFileName, destinationFile); + } catch (ResourceNotFoundException e) { + log.info("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet."); + } catch (Throwable e) { + log.error("Could not copy resourceFile " + resourceFileName + " to " + + destinationFile.getAbsolutePath() + ".\n" + e.getMessage()); + e.printStackTrace(); + } + } else { + log.debug(file.getName() + " file exists already."); + } + }); + + // split + // - get all + history = new HashMap<>(); + store = readStore(getFileName()); + resourceFiles.forEach(file -> { + SplitStore tmp = readStore(file.getName().replace(postFix, "")); + history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + // - subtract all that is in resource files + store.getMap().keySet().removeAll(tmp.getMap().keySet()); + }); + + // - create new file with leftovers + storage.queueUpForSave(); + + } } From c7351a058d1957a45c2ff53c2d5bf7212682c055 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 17 Apr 2020 14:35:22 +0200 Subject: [PATCH 071/108] Activate other split data stores --- .../sign/SignedWitnessStorageService.java | 16 ++-------------- .../core/account/sign/SignedWitnessStore.java | 12 ++---------- .../TradeStatistics2StorageService.java | 13 ++----------- .../trade/statistics/TradeStatistics2Store.java | 12 ++---------- 4 files changed, 8 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java index 722522cba78..8476efee836 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessStorageService.java @@ -20,6 +20,7 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.MapStoreService; +import bisq.network.p2p.storage.persistence.SplitStoreService; import bisq.common.config.Config; import bisq.common.storage.Storage; @@ -36,7 +37,7 @@ import static com.google.common.base.Preconditions.checkArgument; @Slf4j -public class SignedWitnessStorageService extends MapStoreService { +public class SignedWitnessStorageService extends SplitStoreService { private static final String FILE_NAME = "SignedWitnessStore"; @@ -59,11 +60,6 @@ public String getFileName() { return FILE_NAME; } - @Override - public Map getMap() { - return store.getMap(); - } - @Override public boolean canHandle(PersistableNetworkPayload payload) { return payload instanceof SignedWitness; @@ -78,12 +74,4 @@ public boolean canHandle(PersistableNetworkPayload payload) { protected SignedWitnessStore createStore() { return new SignedWitnessStore(); } - - @Override - protected void readStore() { - super.readStore(); - checkArgument(store instanceof SignedWitnessStore, - "Store is not instance of SignedWitnessStore. That can happen if the ProtoBuffer " + - "file got changed. We cleared the data store and recreated it again."); - } } diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java index 8b0e340c41f..d8fae0cbc84 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessStore.java @@ -19,18 +19,13 @@ import bisq.network.p2p.storage.P2PDataStorage; -import bisq.network.p2p.storage.payload.PersistableNetworkPayload; - -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.network.p2p.storage.persistence.SplitStore; import com.google.protobuf.Message; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -40,10 +35,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class SignedWitnessStore implements ThreadedPersistableEnvelope { - @Getter - private Map map = new ConcurrentHashMap<>(); - +public class SignedWitnessStore extends SplitStore { SignedWitnessStore() { } diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java index c732a077f2c..e2768a1be9a 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2StorageService.java @@ -20,6 +20,7 @@ import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.MapStoreService; +import bisq.network.p2p.storage.persistence.SplitStoreService; import bisq.common.config.Config; import bisq.common.storage.Storage; @@ -35,7 +36,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class TradeStatistics2StorageService extends MapStoreService { +public class TradeStatistics2StorageService extends SplitStoreService { private static final String FILE_NAME = "TradeStatistics2Store"; @@ -59,11 +60,6 @@ public String getFileName() { return FILE_NAME; } - @Override - public Map getMap() { - return store.getMap(); - } - @Override public boolean canHandle(PersistableNetworkPayload payload) { return payload instanceof TradeStatistics2; @@ -78,9 +74,4 @@ public boolean canHandle(PersistableNetworkPayload payload) { protected TradeStatistics2Store createStore() { return new TradeStatistics2Store(); } - - @Override - protected void readStore() { - super.readStore(); - } } diff --git a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java index d3ea468907c..bb2354a8fce 100644 --- a/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java +++ b/core/src/main/java/bisq/core/trade/statistics/TradeStatistics2Store.java @@ -18,18 +18,13 @@ package bisq.core.trade.statistics; import bisq.network.p2p.storage.P2PDataStorage; -import bisq.network.p2p.storage.payload.PersistableNetworkPayload; - -import bisq.common.proto.persistable.ThreadedPersistableEnvelope; +import bisq.network.p2p.storage.persistence.SplitStore; import com.google.protobuf.Message; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** @@ -38,10 +33,7 @@ * definition and provide a hashMap for the domain access. */ @Slf4j -public class TradeStatistics2Store implements ThreadedPersistableEnvelope { - @Getter - private Map map = new ConcurrentHashMap<>(); - +public class TradeStatistics2Store extends SplitStore { TradeStatistics2Store() { } From e2e80d7ed2b12bea510ba45b1e26f3bd95bafb50 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 20 Apr 2020 12:41:27 +0200 Subject: [PATCH 072/108] Refactored tests --- .../core/network/p2p/FileDatabaseTest.java | 92 +------------- .../network/p2p/FileDatabaseTestUtils.java | 118 ++++++++++++++++++ 2 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 5f50f7b252a..504737c991f 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -51,11 +51,7 @@ * * TODO these tests are bound to be changed once Bisq migrates to a real database backend */ -public class FileDatabaseTest { - // Test fixtures - static final AccountAgeWitness object1 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 1); - static final AccountAgeWitness object2 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, 2); - static final AccountAgeWitness object3 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, 3); +public class FileDatabaseTest extends FileDatabaseTestUtils { /** * TEST CASE: check if test fixture databases are in place and correct @@ -276,90 +272,4 @@ public void put() throws IOException, InterruptedException { // - did the historical data data store grow? Assert.assertEquals(1, DUT.getMap().size() - result.size()); } - - ///////////////////////////////////////////////////////////////////////////////// - ///////////////////////////////////// Utils ///////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////// - - List files = new ArrayList<>(); - static final File storageDir = new File("src/test/resources"); - - @After - public void cleanup() { - try { - boolean done = false; - while (!done) { - Thread.sleep(100); - Set threads = Thread.getAllStackTraces().keySet(); - done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - - for (File file : files) { - file.delete(); - } - - try { - File backupDir = new File(storageDir + "/backup"); - if (backupDir.exists()) - Files.walk(backupDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); - } catch (IOException e) { - e.printStackTrace(); - } - - Arrays.stream(storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore"))).forEach(s -> { - new File(storageDir + File.separator + s).delete(); - }); - } - - public File createFile(boolean isResourceFile, String name) { - File tmp; - if (isResourceFile) - tmp = new File(ClassLoader.getSystemClassLoader().getResource("").getFile() + File.separator + name); - else - tmp = new File(storageDir + File.separator + name); - - files.add(tmp); - return tmp; - } - - private AppendOnlyDataStoreService loadDatabase() { - Storage storage = new Storage<>(storageDir, new CorePersistenceProtoResolver(null, null, null, null), null); - AccountAgeWitnessStorageService storageService = new AccountAgeWitnessStorageService(storageDir, storage); - final AppendOnlyDataStoreService protectedDataStoreService = new AppendOnlyDataStoreService(); - protectedDataStoreService.addService(storageService); - protectedDataStoreService.readFromResources("_TEST"); - return protectedDataStoreService; - } - - private void createDatabase(File target, - AccountAgeWitness... objects) throws IOException { - - if (null == objects) { - return; - } - - String filename = ""; - filename += Arrays.asList(objects).contains(object1) ? "o1" : ""; - filename += Arrays.asList(objects).contains(object2) ? "o2" : ""; - filename += Arrays.asList(objects).contains(object3) ? "o3" : ""; - - File source = new File(storageDir + File.separator + filename); - - if (target.exists()) - target.delete(); - - Files.copy(source.toPath(), target.toPath()); - } - - /** - * note that this function assumes a Bisq version format of x.y.z. It will not work with formats other than that eg. x.yy.z - * @param offset - * @return relative version string to the Version.VERSION constant - */ - public String getVersion(int offset) { - return new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); - } } diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java new file mode 100644 index 00000000000..79263533e21 --- /dev/null +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -0,0 +1,118 @@ +package bisq.core.network.p2p; + +import bisq.core.account.witness.AccountAgeWitness; +import bisq.core.account.witness.AccountAgeWitnessStorageService; +import bisq.core.account.witness.AccountAgeWitnessStore; +import bisq.core.proto.persistable.CorePersistenceProtoResolver; + +import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; + +import bisq.common.app.Version; +import bisq.common.storage.Storage; + +import java.nio.file.Files; +import java.nio.file.Path; + +import java.io.File; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.junit.After; + +/** + * utility class for the {@link FileDatabaseTest} + */ +public class FileDatabaseTestUtils { + // Test fixtures + static final AccountAgeWitness object1 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 1); + static final AccountAgeWitness object2 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, 2); + static final AccountAgeWitness object3 = new AccountAgeWitness(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3}, 3); + + + List files = new ArrayList<>(); + static final File storageDir = new File("src/test/resources"); + + @After + public void cleanup() { + try { + boolean done = false; + while (!done) { + Thread.sleep(100); + Set threads = Thread.getAllStackTraces().keySet(); + done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + + for (File file : files) { + file.delete(); + } + + try { + File backupDir = new File(storageDir + "/backup"); + if (backupDir.exists()) + Files.walk(backupDir.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + e.printStackTrace(); + } + + Arrays.stream(storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore"))).forEach(s -> { + new File(storageDir + File.separator + s).delete(); + }); + } + + public File createFile(boolean isResourceFile, String name) { + File tmp; + if (isResourceFile) + tmp = new File(ClassLoader.getSystemClassLoader().getResource("").getFile() + File.separator + name); + else + tmp = new File(storageDir + File.separator + name); + + files.add(tmp); + return tmp; + } + + protected AppendOnlyDataStoreService loadDatabase() { + Storage storage = new Storage<>(storageDir, new CorePersistenceProtoResolver(null, null, null, null), null); + AccountAgeWitnessStorageService storageService = new AccountAgeWitnessStorageService(storageDir, storage); + final AppendOnlyDataStoreService protectedDataStoreService = new AppendOnlyDataStoreService(); + protectedDataStoreService.addService(storageService); + protectedDataStoreService.readFromResources("_TEST"); + return protectedDataStoreService; + } + + protected void createDatabase(File target, + AccountAgeWitness... objects) throws IOException { + + if (null == objects) { + return; + } + + String filename = ""; + filename += Arrays.asList(objects).contains(object1) ? "o1" : ""; + filename += Arrays.asList(objects).contains(object2) ? "o2" : ""; + filename += Arrays.asList(objects).contains(object3) ? "o3" : ""; + + File source = new File(storageDir + File.separator + filename); + + if (target.exists()) + target.delete(); + + Files.copy(source.toPath(), target.toPath()); + } + + /** + * note that this function assumes a Bisq version format of x.y.z. It will not work with formats other than that eg. x.yy.z + * @param offset + * @return relative version string to the Version.VERSION constant + */ + public String getVersion(int offset) { + return new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); + } +} From 10c1ecc1fe92427c91e758b08f184a73dfecf855 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 20 Apr 2020 12:42:01 +0200 Subject: [PATCH 073/108] Initial tests for requests --- .../network/p2p/FileDatabaseTestUtils.java | 2 +- .../core/network/p2p/RequestDataTest.java | 146 ++++++++++++++++++ 2 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/bisq/core/network/p2p/RequestDataTest.java diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java index 79263533e21..cbdba48cdf9 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -25,7 +25,7 @@ import org.junit.After; /** - * utility class for the {@link FileDatabaseTest} + * utility class for the {@link FileDatabaseTest} and {@link RequestDataTest} */ public class FileDatabaseTestUtils { // Test fixtures diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java new file mode 100644 index 00000000000..145973fb0f3 --- /dev/null +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -0,0 +1,146 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.network.p2p; + +import bisq.core.account.witness.AccountAgeWitness; + +import bisq.network.p2p.network.LocalhostNetworkNode; +import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; +import bisq.network.p2p.storage.P2PDataStorage; +import bisq.network.p2p.storage.payload.PersistableNetworkPayload; +import bisq.network.p2p.storage.persistence.ProtectedDataStoreService; +import bisq.network.p2p.storage.persistence.SequenceNumberMap; + +import bisq.common.storage.Storage; + +import java.io.File; +import java.io.IOException; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for migrating to and operating a multi-file file-based data store system + */ +public class RequestDataTest extends FileDatabaseTestUtils { + + /** + * TEST CASE: test the optimized query creation + * + * USE CASE: + * In order to save on data to be transmitted, we summarize data history by reporting + * our bisq version as a "special key". The queried peers then know which data we + * already have. + * + * RESULT + * Test whether our client creates the correct query. + */ + @Test + public void query() throws IOException { + // setup scenario + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); + + // simulate bisq startup + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + Set result = DUT.buildPreliminaryGetDataRequest(0).getExcludedKeys().stream().map(bytes -> new P2PDataStorage.ByteArray(bytes)).collect(Collectors.toSet()); + + // check result + // - check total number of elements + Assert.assertEquals(2, result.size()); + // - check keys + Assert.assertFalse(result.contains(new P2PDataStorage.ByteArray(object1.getHash()))); + Assert.assertFalse(result.contains(new P2PDataStorage.ByteArray(object2.getHash()))); + Assert.assertTrue(result.contains(new P2PDataStorage.ByteArray(object3.getHash()))); + // - check special key + Assert.assertTrue(result.contains(new P2PDataStorage.ByteArray(getSpecialKey(getVersion(0)).getHash()))); + } + + /** + * TEST CASE: test the optimized query evaluation + * + * USE CASE: + * In order to save on data to be transmitted, we summarize data history by reporting + * our bisq version as a "special key". The queried peers then know which data we + * already have. + * + * RESULT + * Given the new query, see if another peer would respond correctly + */ + @Test + public void response() throws IOException { + // setup scenario + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); + + // simulate bisq startup + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + PreliminaryGetDataRequest query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(getVersion(0)).getHash()))); + Set result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); + + // check result + // - check total number of elements + Assert.assertEquals(1, result.size()); + // - check keys + Assert.assertFalse(result.contains(object1)); + Assert.assertFalse(result.contains(object2)); + Assert.assertTrue(result.contains(object3)); + + // alter query slightly + query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(getVersion(-1)).getHash(), object3.getHash()))); + result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); + + // check result + // - check total number of elements + Assert.assertEquals(1, result.size()); + // - check keys + Assert.assertFalse(result.contains(object1)); + Assert.assertTrue(result.contains(object2)); + Assert.assertFalse(result.contains(object3)); + } + + + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////// Utils ///////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + + public class SequenceNumberStorageFake extends Storage { + + public SequenceNumberStorageFake() { + super(new File("src/test/resources"), null, null); + } + + @Override + public void setNumMaxBackupFiles(int numMaxBackupFiles) { + } + } + + public static AccountAgeWitness getSpecialKey(String version) { + byte[] result = new byte[20]; + Arrays.fill(result, (byte) 0); + System.arraycopy(version.getBytes(), 0, result, 0, version.length()); + return new AccountAgeWitness(result, 0); + } +} From f6cea15b25638c18e5b27ebca8ed82917bc13a63 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 20 Apr 2020 15:44:13 +0200 Subject: [PATCH 074/108] Requests are smaller --- .../network/p2p/storage/P2PDataStorage.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index 2283547a1f7..39ff1fd215c 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -54,6 +54,7 @@ import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Capabilities; +import bisq.common.app.Version; import bisq.common.crypto.CryptoException; import bisq.common.crypto.Hash; import bisq.common.crypto.Sig; @@ -81,6 +82,7 @@ import java.time.Clock; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -89,6 +91,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; @@ -205,6 +208,21 @@ public GetUpdatedDataRequest buildGetUpdatedDataRequest(NodeAddress senderNodeAd return new GetUpdatedDataRequest(senderNodeAddress, nonce, this.getKnownPayloadHashes()); } + private byte[] getSpecialKey() { + byte[] result = new byte[20]; + Arrays.fill(result, (byte) 0); + System.arraycopy(Version.VERSION.getBytes(), 0, result, 0, Version.VERSION.length()); + return result; + } + + private String containsSpecialKey(Set collection) { + Optional result = collection.stream().map(byteArray -> new String(byteArray.bytes).trim()).filter(s -> s.matches("^[0-9]+\\.[0-9]+\\.[0-9]+")).findFirst(); + if (result.isPresent()) + return result.get(); + else + return ""; + } + /** * Returns the set of known payload hashes. This is used in the GetData path to request missing data from peer nodes */ @@ -213,7 +231,7 @@ private Set getKnownPayloadHashes() { // PersistedStoragePayload items don't get removed, so we don't have an issue with the case that // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would // miss that event if we do not load the full set or use some delta handling. - Set excludedKeys = this.appendOnlyDataStoreService.getMap().keySet().stream() + Set excludedKeys = this.appendOnlyDataStoreService.getMap("since " + Version.VERSION).keySet().stream() .map(e -> e.bytes) .collect(Collectors.toSet()); @@ -223,6 +241,7 @@ private Set getKnownPayloadHashes() { .collect(Collectors.toSet()); excludedKeys.addAll(excludedKeysFromPersistedEntryMap); + excludedKeys.add(getSpecialKey()); return excludedKeys; } @@ -268,9 +287,18 @@ public GetDataResponse buildGetDataResponse( Set excludedKeysAsByteArray = P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys()); + String specialKey = containsSpecialKey(excludedKeysAsByteArray); + + Map tmp; + if ("".equals(specialKey)) + tmp = this.appendOnlyDataStoreService.getMap(); + else { + tmp = this.appendOnlyDataStoreService.getMap("since " + specialKey); + } + Set filteredPersistableNetworkPayloads = filterKnownHashes( - this.appendOnlyDataStoreService.getMap(), + tmp, Function.identity(), excludedKeysAsByteArray, peerCapabilities, From 0d80123b55623d0e872913599b4e0bfb891312db Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 24 Apr 2020 11:15:48 +0200 Subject: [PATCH 075/108] Prevent adding duplicate data --- .../core/network/p2p/FileDatabaseTest.java | 34 +++++++++++++++++++ .../persistence/SplitStoreService.java | 8 +++++ 2 files changed, 42 insertions(+) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 504737c991f..88ad81edd36 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -272,4 +272,38 @@ public void put() throws IOException, InterruptedException { // - did the historical data data store grow? Assert.assertEquals(1, DUT.getMap().size() - result.size()); } + + /** + * TEST CASE: test if only new data is added given a set of data we partially already + * know. + * + * USE CASE: + * Given a Bisq client version x asks a seed node version x-1 for data, it might receive + * data it already has. We do not want to add duplicates to our local database. + * + * RESULT + * Check for duplicates + */ + @Test + public void putDuplicates() throws IOException { + // setup scenario + // - create one database containing historical data and a live database + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object2); + + // simulate Bisq startup + AppendOnlyDataStoreService DUT = loadDatabase(); + // add data + // - duplicate data + DUT.put(new P2PDataStorage.ByteArray(object1.getHash()), object1); + // - legit data + DUT.put(new P2PDataStorage.ByteArray(object3.getHash()), object3); + + // check result + // - did the live database grow? + Collection result = DUT.getMap("since " + getVersion(0)).values(); + Assert.assertEquals(2, result.size()); + Assert.assertTrue(result.contains(object2)); + Assert.assertTrue(result.contains(object3)); + } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index a0544b1716d..d90a349059a 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -59,6 +59,10 @@ public SplitStoreService(File storageDir, Storage storage) { @Override protected void put(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) { + // make sure we do not add data that we already have (in a bin of historical data) + if (getMap().containsKey(hash)) + return; + store.getMap().put(hash, payload); persist(); } @@ -66,6 +70,10 @@ protected void put(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payl @Override protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) { + // make sure we do not add data that we already have (in a bin of historical data) + if (getMap().containsKey(hash)) + return null; + PersistableNetworkPayload previous = store.getMap().putIfAbsent(hash, payload); persist(); return previous; From aad57c3c34775801d917abfc64d8b4d01daa0cd9 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 24 Apr 2020 11:58:14 +0200 Subject: [PATCH 076/108] Enforce version format and filter faulty ones Instead of trying to find faulty keys, we stick to the mechanics we have in place. This way, we have less false positives. However, we enforce a certain Bisq version format, as parsing and business logic relies on that now. --- .../core/network/p2p/RequestDataTest.java | 54 +++++++++++++++++++ .../network/p2p/storage/P2PDataStorage.java | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index 145973fb0f3..f336a84d9a4 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -26,6 +26,7 @@ import bisq.network.p2p.storage.persistence.ProtectedDataStoreService; import bisq.network.p2p.storage.persistence.SequenceNumberMap; +import bisq.common.app.Version; import bisq.common.storage.Storage; import java.io.File; @@ -33,6 +34,7 @@ import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -121,6 +123,58 @@ public void response() throws IOException { Assert.assertFalse(result.contains(object3)); } + /** + * TEST CASE: what happens if a faulty key arrives + * + * USE CASE: + * An Attacker tries to hijack the protocol by sending a incorrect "special key". The + * system should be resilient against that and thus, recover and fall back to a known + * state. + * + * RESULT + * Although it would be nice to have some sort of detection, it is hard to do right. + * So we just stick to what happened until now, namely, we ignore the special key + * and let other size limits do the work. + * + * However, we add an additional test to ensure Bisq version follows a strict pattern. + */ + @Test + public void faultySpecialKey() throws IOException { + // setup scenario + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object2); + + // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + + List faultyKeys = Arrays.asList("1.3.13", "1.13.3", "13.3.3", "a.3.3", "13.a.3", "ä.3.3", Version.VERSION.replace(".", "_"), Version.VERSION.replace(".", ",")); + faultyKeys.forEach(s -> faultySpecialKeyHelper(DUT, s)); + } + + private void faultySpecialKeyHelper(P2PDataStorage DUT, String key) { + PreliminaryGetDataRequest query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(key).getHash()))); + + Set result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); + Assert.assertEquals(key + " got accepted", 2, result.size()); + Assert.assertTrue(key + " got accepted", result.contains(object1)); + Assert.assertTrue(key + " got accepted", result.contains(object2)); + } + + /** + * TEST CASE: make sure Bisq follows a strict pattern in release versioning. + * + * USE CASE: + * Bringing in the timely element we need to make sure Bisq's release versioning stays + * true to the pattern it follows now. + * + * RESULT + * If the pattern does not match, we fire! + */ + @Test + public void testBisqVersionFormat() { + Assert.assertTrue("Bisq Version does not match formatting! x.y.z vs. " + Version.VERSION, Version.VERSION.matches("^[0-9]\\.[0-9]\\.[0-9]$")); + } + ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// Utils ///////////////////////////////////// diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index 39ff1fd215c..f6bc5aeb672 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -216,7 +216,7 @@ private byte[] getSpecialKey() { } private String containsSpecialKey(Set collection) { - Optional result = collection.stream().map(byteArray -> new String(byteArray.bytes).trim()).filter(s -> s.matches("^[0-9]+\\.[0-9]+\\.[0-9]+")).findFirst(); + Optional result = collection.stream().map(byteArray -> new String(byteArray.bytes).trim()).filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")).findFirst(); if (result.isPresent()) return result.get(); else From faca8e4f96c47609be7fe24d2571e9feafd2597a Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 24 Apr 2020 12:09:21 +0200 Subject: [PATCH 077/108] Test incoming special keys from the future --- .../core/network/p2p/RequestDataTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index f336a84d9a4..4552ca00cfb 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -175,6 +175,39 @@ public void testBisqVersionFormat() { Assert.assertTrue("Bisq Version does not match formatting! x.y.z vs. " + Version.VERSION, Version.VERSION.matches("^[0-9]\\.[0-9]\\.[0-9]$")); } + /** + * TEST CASE: test what happens if an incoming "special key" is a future bisq version + * + * USE CASE: + * A Bisq client Alice of version x asks a Bisq client Bob of version x-1 for data. + * The "special" key incoming to Bob describes its future. A real-world example is an + * outdated seednode getting asked by an up-to-date client. + * + * RESULT + * send anything new since the seednode's newest data bucket - ie. all live data. This + * will result in duplicates arriving at the client but + *
    • we took care of that already
    • + *
    • is a sane way of reacting to a newer client
    + */ + @Test + public void futureSpecialKey() throws IOException { + // setup scenario + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); + createDatabase(createFile(false, "AccountAgeWitnessStore"), object2, object3); + + // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + PreliminaryGetDataRequest query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(getVersion(0)).getHash()))); + Set result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); + + // check result + // - check total number of elements + Assert.assertEquals(2, result.size()); + // - check keys + Assert.assertFalse(result.contains(object1)); + Assert.assertTrue(result.contains(object2)); + Assert.assertTrue(result.contains(object3)); + } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////// Utils ///////////////////////////////////// From 72600b15d59ae0cd7f9115d8007016abb2f218e1 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 24 Apr 2020 12:15:12 +0200 Subject: [PATCH 078/108] Javadoc cosmetics --- .../core/network/p2p/FileDatabaseTest.java | 44 +++++++++---------- .../core/network/p2p/RequestDataTest.java | 40 +++++++++-------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 88ad81edd36..cef635fbe7c 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -54,7 +54,7 @@ public class FileDatabaseTest extends FileDatabaseTestUtils { /** - * TEST CASE: check if test fixture databases are in place and correct + * TEST CASE: check if test fixture databases are in place and correct

    * * This does not test any business logic, just makes sure the test setup is correct. */ @@ -77,15 +77,15 @@ private void checkTestFixturesHelper(AccountAgeWitness... objects) throws IOExce } /** - * TEST CASE: test migration scenario from old database file model to new one + * TEST CASE: test migration scenario from old database file model to new one

    * * USE CASE: * We migrate from just having one working-dir database file to having multiple. In * detail, the user starts with having one database file in her working directory and * one in her resources. After the update, there is still one database in her resources - * but it is labelled differently. + * but it is labelled differently.

    * - * RESULT + * RESULT: * There are 2 data stores in her working directory, one holding the live database, * the other one being the exact and readonly copy of the database in resources. Plus, * the 2 data stores do not share any set of objects. @@ -112,14 +112,14 @@ public void migrationScenario() throws IOException, InterruptedException { } /** - * TEST CASE: test Bisq software update scenario + * TEST CASE: test Bisq software update scenario

    * * USE CASE: * Given a newly released Bisq version x, a new differential database is added to * the resources. The new database has to be copied to the working directory and the - * live database has to be stripped of the entries found in the new database. + * live database has to be stripped of the entries found in the new database.

    * - * RESULT + * RESULT: * There are n + 1 data stores in the users working directory, one holding the live * database, the other 2 being the exact and readonly copy of the data stores in * resources. Plus, the data stores in the working dir do not share any set of objects. @@ -148,13 +148,13 @@ public void updateScenario() throws IOException, InterruptedException { } /** - * TEST CASE: test clean install of Bisq software app + * TEST CASE: test clean install of Bisq software app

    * * USE CASE: * A user has a fresh install of Bisq. Ie. there are two database files in resources - * and none in the working directory. + * and none in the working directory.

    * - * RESULT + * RESULT: * After startup, there should be 3 data stores in the working directory. */ @Test @@ -179,14 +179,14 @@ public void freshInstallScenario() throws IOException, InterruptedException { } /** - * TEST CASE: test if getMap still return all elements + * TEST CASE: test if getMap still return all elements

    * * USE CASE: * The app needs all data for . Currently, this is the most * encountered use case. Future plans, however, aim to reduce the need for this use - * case. + * case.

    * - * RESULT + * RESULT: * getMap returns all elements stored in the various database files. */ @Test @@ -209,13 +209,13 @@ public void getMap() throws IOException, InterruptedException { } /** - * TEST CASE: test if getMap filtering works + * TEST CASE: test if getMap filtering works

    * * USE CASE: * After introducing the database snapshot functionality, the app only requests objects - * which it got since the last database snapshot. + * which it got since the last database snapshot.

    * - * RESULT + * RESULT: * getMap(since x) returns all elements added after snapshot x */ @Test @@ -242,13 +242,13 @@ public void getMapSinceFilter() throws IOException, InterruptedException { } /** - * TEST CASE: test if adding new data only adds to live database + * TEST CASE: test if adding new data only adds to live database

    * * USE CASE: * Whenever new objects come in, they are going to be persisted to disk so that - * the local database is kept in sync with the distributed database. + * the local database is kept in sync with the distributed database.

    * - * RESULT + * RESULT: * map.put should only add data to the live database. Other data stores are read-only! */ @Test @@ -275,13 +275,13 @@ public void put() throws IOException, InterruptedException { /** * TEST CASE: test if only new data is added given a set of data we partially already - * know. + * know.

    * * USE CASE: * Given a Bisq client version x asks a seed node version x-1 for data, it might receive - * data it already has. We do not want to add duplicates to our local database. + * data it already has. We do not want to add duplicates to our local database.

    * - * RESULT + * RESULT: * Check for duplicates */ @Test diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index 4552ca00cfb..80f6134ada7 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -42,19 +42,20 @@ import org.junit.Test; /** - * Tests for migrating to and operating a multi-file file-based data store system + * Tests for the "reduced" initial query size feature. See + * project description on github. */ public class RequestDataTest extends FileDatabaseTestUtils { /** - * TEST CASE: test the optimized query creation + * TEST CASE: test the optimized query creation

    * * USE CASE: * In order to save on data to be transmitted, we summarize data history by reporting * our bisq version as a "special key". The queried peers then know which data we - * already have. + * already have.

    * - * RESULT + * RESULT: * Test whether our client creates the correct query. */ @Test @@ -64,7 +65,7 @@ public void query() throws IOException { createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); - // simulate bisq startup + // create a PreliminaryGetDataRequest as a Device Under Test P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); Set result = DUT.buildPreliminaryGetDataRequest(0).getExcludedKeys().stream().map(bytes -> new P2PDataStorage.ByteArray(bytes)).collect(Collectors.toSet()); @@ -80,14 +81,14 @@ public void query() throws IOException { } /** - * TEST CASE: test the optimized query evaluation + * TEST CASE: test the optimized query evaluation

    * * USE CASE: * In order to save on data to be transmitted, we summarize data history by reporting * our bisq version as a "special key". The queried peers then know which data we - * already have. + * already have.

    * - * RESULT + * RESULT: * Given the new query, see if another peer would respond correctly */ @Test @@ -97,7 +98,7 @@ public void response() throws IOException { createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); - // simulate bisq startup + // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); PreliminaryGetDataRequest query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(getVersion(0)).getHash()))); Set result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); @@ -124,17 +125,17 @@ public void response() throws IOException { } /** - * TEST CASE: what happens if a faulty key arrives + * TEST CASE: what happens if a faulty key arrives

    * * USE CASE: * An Attacker tries to hijack the protocol by sending a incorrect "special key". The * system should be resilient against that and thus, recover and fall back to a known - * state. + * state.

    * - * RESULT + * RESULT: * Although it would be nice to have some sort of detection, it is hard to do right. * So we just stick to what happened until now, namely, we ignore the special key - * and let other size limits do the work. + * and let other size limits do the work.

    * * However, we add an additional test to ensure Bisq version follows a strict pattern. */ @@ -147,6 +148,7 @@ public void faultySpecialKey() throws IOException { // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + // check results List faultyKeys = Arrays.asList("1.3.13", "1.13.3", "13.3.3", "a.3.3", "13.a.3", "ä.3.3", Version.VERSION.replace(".", "_"), Version.VERSION.replace(".", ",")); faultyKeys.forEach(s -> faultySpecialKeyHelper(DUT, s)); } @@ -161,13 +163,13 @@ private void faultySpecialKeyHelper(P2PDataStorage DUT, String key) { } /** - * TEST CASE: make sure Bisq follows a strict pattern in release versioning. + * TEST CASE: make sure Bisq follows a strict pattern in release versioning.

    * * USE CASE: * Bringing in the timely element we need to make sure Bisq's release versioning stays - * true to the pattern it follows now. + * true to the pattern it follows now.

    * - * RESULT + * RESULT: * If the pattern does not match, we fire! */ @Test @@ -176,14 +178,14 @@ public void testBisqVersionFormat() { } /** - * TEST CASE: test what happens if an incoming "special key" is a future bisq version + * TEST CASE: test what happens if an incoming "special key" is a future bisq version

    * * USE CASE: * A Bisq client Alice of version x asks a Bisq client Bob of version x-1 for data. * The "special" key incoming to Bob describes its future. A real-world example is an - * outdated seednode getting asked by an up-to-date client. + * outdated seednode getting asked by an up-to-date client.

    * - * RESULT + * RESULT: * send anything new since the seednode's newest data bucket - ie. all live data. This * will result in duplicates arriving at the client but *
    • we took care of that already
    • From 3ad395b928e72629c497c5906b2c89878f182f29 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Wed, 29 Apr 2020 16:44:38 +0200 Subject: [PATCH 079/108] Init integration test script --- reduce_initial_request_size_test.sh | 106 ++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100755 reduce_initial_request_size_test.sh diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh new file mode 100755 index 00000000000..0f98e2aa292 --- /dev/null +++ b/reduce_initial_request_size_test.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +install() +{ + mkdir -p installdir/$2 + cp bisq-$1 installdir/$2/ + cp -r lib installdir/$2/ +} + +declare -A cmd +cmd['bitcoind']="bitcoind -regtest -prune=0 -txindex=1 -peerbloomfilters=1 -server -rpcuser=bisqdao -rpcpassword=bsq -datadir=.localnet/bitcoind -blocknotify='.localnet/bitcoind/blocknotify %s'" +cmd['alice']="installdir/alice/bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=5555 --fullDaoNode=true --rpcUser=bisqdao --rpcPassword=bsq --rpcBlockNotificationPort=5122 --genesisBlockHeight=111 --genesisTxId=30af0050040befd8af25068cc697e418e09c2d8ebd8d411d2240591b9ec203cf --appDataDir=.localnet/alice --appName=Alice" +cmd['bob']="installdir/bob/bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=6666 --appDataDir=.localnet/bob --appName=Bob" +cmd['mediator']="installdir/mediator/bisq-desktop --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --nodePort=4444 --appDataDir=.localnet/mediator --appName=Mediator" +cmd['seednode']="installdir/seednode/bisq-seednode --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --fullDaoNode=true --rpcUser=bisqdao --rpcPassword=bsq --rpcBlockNotificationPort=5120 --nodePort=2002 --userDataDir=.localnet --appName=seednode" +cmd['seednode2']="installdir/seednode2/bisq-seednode --baseCurrencyNetwork=BTC_REGTEST --useLocalhostForP2P=true --useDevPrivilegeKeys=true --fullDaoNode=true --rpcUser=bisqdao --rpcPassword=bsq --rpcBlockNotificationPort=5121 --nodePort=3002 --userDataDir=.localnet --appName=seednode2" + +run() +{ + # create a new screen session named 'localnet' + screen -dmS localnet + # deploy each node in its own named screen window + for target in \ + bitcoind \ + seednode \ + seednode2 \ + alice \ + bob \ + mediator; do \ +# echo "$target: ${cmd[$target]}" + screen -S localnet -X screen -t $target; \ + screen -S localnet -p $target -X stuff "${cmd[$target]}\n"; \ + done; + # give bitcoind rpc server time to start + sleep 5 + # generate a block to ensure Bisq nodes get dao-synced + make block +} + +# - shut down anything again +staap() +{ + # kill all Bitcoind and Bisq nodes running in screen windows + screen -S localnet -X at "#" stuff "^C" + # quit all screen windows which results in killing the session + screen -S localnet -X at "#" kill + screen -wipe +} + +# clean everything for a fresh test run +rm -rf .localnet +rm -rf installdir + +# deploy configuration files and start bitcoind +make localnet + +# start with 1.3.2 setup +# - get sources for 1.3.2 +git checkout v1.3.2 -f + +# - build initial binaries and file structure +./gradlew :seednode:build +./gradlew :desktop:build + +# - install binaries +install seednode seednode +install seednode seednode2 +install desktop alice +install desktop bob +install desktop mediator + +# - fire up all of it +run + +# - setup mediator/refund agent +read -n 1 -p "Configure mediator/refund agent! proceed?" mainmenuinput +read -n 1 -p "Create 2 offers and do one trade! proceed?" mainmenuinput + +# - shut down everything +staap + +# upgrade to PR +git checkout -f reduce_initial_request_size +./gradlew :seednode:build + +# install seednode binaries +install seednode seednode + +# fire up all of it +run + +read -n 1 -p "Create 2 offers and do one trade! done. proceed:" mainmenuinput + +# shut down anything again +staap + +## install client binaries +install desktop alice + +## fire up all of it +run + +read -n 1 -p "Create 2 offers and do one trade! done. proceed:" mainmenuinput + +# shut down anything again +staap From 25891371f481519f0e25059b90e4c7ec3aa19c8f Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 1 May 2020 15:19:35 +0200 Subject: [PATCH 080/108] Persist live database after split --- .../bisq/network/p2p/storage/persistence/SplitStoreService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index d90a349059a..28b61e00e3a 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -180,6 +180,7 @@ protected void makeFileFromResourceFile(String postFix) { }); // - create new file with leftovers + storage.initAndGetPersisted(store, 0); storage.queueUpForSave(); } From 34abad09dfd4f2678b9e3edb3f8eb0f7ec27176c Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 1 May 2020 15:32:47 +0200 Subject: [PATCH 081/108] Integration test script tests stock --- reduce_initial_request_size_test.sh | 39 +++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh index 0f98e2aa292..de2a08b7ff7 100755 --- a/reduce_initial_request_size_test.sh +++ b/reduce_initial_request_size_test.sh @@ -30,6 +30,7 @@ run() # echo "$target: ${cmd[$target]}" screen -S localnet -X screen -t $target; \ screen -S localnet -p $target -X stuff "${cmd[$target]}\n"; \ + sleep 3 done; # give bitcoind rpc server time to start sleep 5 @@ -47,12 +48,43 @@ staap() screen -wipe } +check() +{ + # gather data + for target in \ + alice \ + bob \ + mediator; do \ + echo "$target:" >> result.log; \ + grep -o ": .* with [0-9\.]* kB" .localnet/$target/bisq.log >> result.log; \ + rm .localnet/$target/bisq.log; \ + done; +} + # clean everything for a fresh test run rm -rf .localnet rm -rf installdir +staap # deploy configuration files and start bitcoind make localnet +for target in \ + seednode \ + seednode2 \ + alice \ + bob \ + mediator; do \ + mkdir -p .localnet/$target/btc_regtest/db/; \ + cp PreferencesPayload .localnet/$target/btc_regtest/db/; \ +done; + + +# - borrow mainnet data stores to better illustrate stuff +cd p2p/src/main/resources/ +cp TradeStatistics2Store_BTC_MAINNET TradeStatistics2Store_BTC_REGTEST +cp AccountAgeWitnessStore_BTC_MAINNET AccountAgeWitnessStore_BTC_REGTEST +cp SignedWitnessStore_BTC_MAINNET SignedWitnessStore_BTC_REGTEST +cd - # start with 1.3.2 setup # - get sources for 1.3.2 @@ -73,12 +105,15 @@ install desktop mediator run # - setup mediator/refund agent -read -n 1 -p "Configure mediator/refund agent! proceed?" mainmenuinput -read -n 1 -p "Create 2 offers and do one trade! proceed?" mainmenuinput +sleep 5 # - shut down everything staap +echo "##### Sanity check ###########################################" > result.log +check + +exit 1 # upgrade to PR git checkout -f reduce_initial_request_size ./gradlew :seednode:build From 62a77e331dc0a78a087f13145a7764183ad59e96 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 1 May 2020 16:30:47 +0200 Subject: [PATCH 082/108] Use version history array Turns out, scanning resources does not work reliable enough. Thus, an array of Strings denoting historical Bisq versions it is. An other way to put it is it is an array denoting which data stores are there in the resources. --- .../main/java/bisq/common/app/Version.java | 13 +++++++++ .../core/network/p2p/FileDatabaseTest.java | 13 +++++++-- .../network/p2p/FileDatabaseTestUtils.java | 27 +++++++++++++++++++ .../persistence/SplitStoreService.java | 17 +++++++----- reduce_initial_request_size_test.sh | 16 +++++++++-- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index 898228f3def..a0c1cfac926 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -17,6 +17,9 @@ package bisq.common.app; +import java.util.Arrays; +import java.util.List; + import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkArgument; @@ -29,6 +32,16 @@ public class Version { // We use semantic versioning with major, minor and patch public static final String VERSION = "1.3.8"; + /** + * Holds a list of historical versions. + * + * Has been introduced with version 1.3.2. Prior versions did not need the history. + * + * The history of versions is used in the differential data store solution which + * was introduced to get rid of storing all data every time. + */ + public static final List history = Arrays.asList(); + public static int getMajorVersion(String version) { return getSubVersion(version, 0); } diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index cef635fbe7c..57701762f59 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -68,6 +68,7 @@ public void checkTestFixtures() throws IOException, InterruptedException { } private void checkTestFixturesHelper(AccountAgeWitness... objects) throws IOException, InterruptedException { + createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), objects); createDatabase(createFile(false, "AccountAgeWitnessStore"), objects); AppendOnlyDataStoreService DUT = loadDatabase(); Assert.assertEquals(objects.length, DUT.getMap().size()); @@ -125,7 +126,7 @@ public void migrationScenario() throws IOException, InterruptedException { * resources. Plus, the data stores in the working dir do not share any set of objects. */ @Test - public void updateScenario() throws IOException, InterruptedException { + public void updateScenario() throws Exception { // setup scenario // - create two data stores in resources createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); @@ -134,6 +135,10 @@ public void updateScenario() throws IOException, InterruptedException { createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); createDatabase(createFile(false, "AccountAgeWitnessStore"), object2, object3); + // beware of the nasty hack! + // TODO replace as soon as we have at least one version string in history + setFinalStatic(Version.class.getField("history"), Arrays.asList("1.3.1")); + // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -158,12 +163,16 @@ public void updateScenario() throws IOException, InterruptedException { * After startup, there should be 3 data stores in the working directory. */ @Test - public void freshInstallScenario() throws IOException, InterruptedException { + public void freshInstallScenario() throws Exception { // setup scenario // - create two data stores in resources createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); + // beware of the nasty hack! + // TODO replace as soon as we have at least one version string in history + setFinalStatic(Version.class.getField("history"), Arrays.asList("1.3.1")); + // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java index cbdba48cdf9..dc80e17cc26 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -22,6 +22,9 @@ import java.util.List; import java.util.Set; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + import org.junit.After; /** @@ -115,4 +118,28 @@ protected void createDatabase(File target, public String getVersion(int offset) { return new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); } + + /** + * Beware of this nasty hack!

      + * + * TODO get rid of this as soon as we have at least one version in Version.history

      + * + * Because Version.history should be static and final, we cannot create + * proper tests. Hence, we use reflections to do it. This can be removed as soon as + * there is at least one version in Version.history! + * + * @param field + * @param newValue + * @throws Exception + */ + static void setFinalStatic(Field field, Object newValue) throws Exception { + field.setAccessible(true); + + // remove final modifier from field + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + + field.set(null, newValue); + } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 28b61e00e3a..0036c03e2e6 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -12,6 +12,7 @@ import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -145,13 +146,15 @@ protected void makeFileFromResourceFile(String postFix) { log.warn("make dir failed.\ndbDir=" + dbDir.getAbsolutePath()); // check resources for files - File resourceDir = new File(ClassLoader.getSystemClassLoader().getResource("").getFile()); - List resourceFiles = Arrays.asList(resourceDir.list((dir, name) -> name.startsWith(getFileName()))).stream().map(s -> new File(s)).collect(Collectors.toList()); + List versions = new ArrayList<>(); + versions.add(Version.VERSION); + versions.addAll(Version.history); + List resourceFiles = versions.stream().map(s -> getFileName() + "_" + s + postFix).collect(Collectors.toList()); // if not, copy and split resourceFiles.forEach(file -> { - final File destinationFile = new File(Paths.get(absolutePathOfStorageDir, file.getName().replace(postFix, "")).toString()); - final String resourceFileName = file.getName(); + final File destinationFile = new File(Paths.get(absolutePathOfStorageDir, file.replace(postFix, "")).toString()); + final String resourceFileName = file; if (!destinationFile.exists()) { try { log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); @@ -164,7 +167,7 @@ protected void makeFileFromResourceFile(String postFix) { e.printStackTrace(); } } else { - log.debug(file.getName() + " file exists already."); + log.debug(file + " file exists already."); } }); @@ -173,8 +176,8 @@ protected void makeFileFromResourceFile(String postFix) { history = new HashMap<>(); store = readStore(getFileName()); resourceFiles.forEach(file -> { - SplitStore tmp = readStore(file.getName().replace(postFix, "")); - history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + SplitStore tmp = readStore(file.replace(postFix, "")); + history.put(file.replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); // - subtract all that is in resource files store.getMap().keySet().removeAll(tmp.getMap().keySet()); }); diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh index de2a08b7ff7..a279c07e921 100755 --- a/reduce_initial_request_size_test.sh +++ b/reduce_initial_request_size_test.sh @@ -106,6 +106,7 @@ run # - setup mediator/refund agent sleep 5 +read -p "Wrap up first test case?" # - shut down everything staap @@ -113,9 +114,15 @@ staap echo "##### Sanity check ###########################################" > result.log check -exit 1 # upgrade to PR git checkout -f reduce_initial_request_size + +# create release data stores +cd p2p/src/main/resources/ +cp TradeStatistics2Store_BTC_REGTEST TradeStatistics2Store_1.3.2_BTC_REGTEST +cp AccountAgeWitnessStore_BTC_REGTEST AccountAgeWitnessStore_1.3.2_BTC_REGTEST +cp SignedWitnessStore_BTC_REGTEST SignedWitnessStore_1.3.2_BTC_REGTEST +cd - ./gradlew :seednode:build # install seednode binaries @@ -124,11 +131,16 @@ install seednode seednode # fire up all of it run -read -n 1 -p "Create 2 offers and do one trade! done. proceed:" mainmenuinput +sleep 5 +read -p "Wrap up second test case?" # shut down anything again staap +echo "##### After upgrading one seednode ###########################" >> result.log +check + +exit 1 ## install client binaries install desktop alice From 39fcca5a524cda2cb7d7f23c7351f28c811fca0c Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 4 May 2020 12:45:10 +0200 Subject: [PATCH 083/108] Integration test script tests new system --- reduce_initial_request_size_test.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh index a279c07e921..dd264c31c66 100755 --- a/reduce_initial_request_size_test.sh +++ b/reduce_initial_request_size_test.sh @@ -52,11 +52,13 @@ check() { # gather data for target in \ + seednode \ + seednode2 \ alice \ bob \ mediator; do \ echo "$target:" >> result.log; \ - grep -o ": .* with [0-9\.]* kB" .localnet/$target/bisq.log >> result.log; \ + grep -Eo ": (Sending .*Request|Received .*Response) with [0-9\.]* kB" .localnet/$target/bisq.log >> result.log; \ rm .localnet/$target/bisq.log; \ done; } @@ -140,14 +142,16 @@ staap echo "##### After upgrading one seednode ###########################" >> result.log check -exit 1 ## install client binaries install desktop alice ## fire up all of it run -read -n 1 -p "Create 2 offers and do one trade! done. proceed:" mainmenuinput +read -p "Wrap up third test case?" # shut down anything again staap + +echo "##### After upgrading one client ###########################" >> result.log +check From a3628ffffcad41b019770d18e7bade51ceb97c85 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 4 May 2020 12:40:54 +0200 Subject: [PATCH 084/108] Use full sync between seednodes If we use the diff sync between seed nodes we create a race condition where when a seednode gets updated to the new system, it does not sync up properly with the other seed nodes. And that would be fatal. So for the time being, when a seednode asks for data, it uses the "old" big requests with all object keys. Should not be a problem for now since they have enough bandwidth. --- .../core/network/p2p/RequestDataTest.java | 8 ++++---- .../network/p2p/storage/P2PDataStorage.java | 20 +++++++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index 80f6134ada7..81565ebf656 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -66,7 +66,7 @@ public void query() throws IOException { createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); // create a PreliminaryGetDataRequest as a Device Under Test - P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, null, 0); Set result = DUT.buildPreliminaryGetDataRequest(0).getExcludedKeys().stream().map(bytes -> new P2PDataStorage.ByteArray(bytes)).collect(Collectors.toSet()); // check result @@ -99,7 +99,7 @@ public void response() throws IOException { createDatabase(createFile(false, "AccountAgeWitnessStore"), object3); // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse - P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, null, 0); PreliminaryGetDataRequest query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(getVersion(0)).getHash()))); Set result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); @@ -146,7 +146,7 @@ public void faultySpecialKey() throws IOException { createDatabase(createFile(false, "AccountAgeWitnessStore"), object2); // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse - P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, null, 0); // check results List faultyKeys = Arrays.asList("1.3.13", "1.13.3", "13.3.3", "a.3.3", "13.a.3", "ä.3.3", Version.VERSION.replace(".", "_"), Version.VERSION.replace(".", ",")); @@ -198,7 +198,7 @@ public void futureSpecialKey() throws IOException { createDatabase(createFile(false, "AccountAgeWitnessStore"), object2, object3); // craft a PreliminaryGetDataRequest as a query to get a GetDataResponse - P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, 0); + P2PDataStorage DUT = new P2PDataStorage(new LocalhostNetworkNode(9999, null), null, loadDatabase(), new ProtectedDataStoreService(), null, new SequenceNumberStorageFake(), null, null, 0); PreliminaryGetDataRequest query = new PreliminaryGetDataRequest(0, new HashSet<>(Arrays.asList(getSpecialKey(getVersion(0)).getHash()))); Set result = DUT.buildGetDataResponse(query, 100000, null, null, null).getPersistableNetworkPayloadSet(); diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index f6bc5aeb672..1194153f33e 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -29,6 +29,7 @@ import bisq.network.p2p.peers.getdata.messages.GetDataResponse; import bisq.network.p2p.peers.getdata.messages.GetUpdatedDataRequest; import bisq.network.p2p.peers.getdata.messages.PreliminaryGetDataRequest; +import bisq.network.p2p.seed.SeedNodeRepository; import bisq.network.p2p.storage.messages.AddDataMessage; import bisq.network.p2p.storage.messages.AddOncePayload; import bisq.network.p2p.storage.messages.AddPersistableNetworkPayloadMessage; @@ -118,6 +119,8 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers @VisibleForTesting public static final int CHECK_TTL_INTERVAL_SEC = 60; + private final SeedNodeRepository seedNodeRepository; + private final NetworkNode networkNode; private boolean initialRequestApplied = false; @@ -155,15 +158,17 @@ public P2PDataStorage(NetworkNode networkNode, ProtectedDataStoreService protectedDataStoreService, ResourceDataStoreService resourceDataStoreService, Storage sequenceNumberMapStorage, + SeedNodeRepository seedNodeRepository, Clock clock, @Named("MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE") int maxSequenceNumberBeforePurge) { this.broadcaster = broadcaster; this.appendOnlyDataStoreService = appendOnlyDataStoreService; this.protectedDataStoreService = protectedDataStoreService; this.resourceDataStoreService = resourceDataStoreService; + this.seedNodeRepository = seedNodeRepository; this.clock = clock; this.maxSequenceNumberMapSizeBeforePurge = maxSequenceNumberBeforePurge; - + this.networkNode = networkNode; networkNode.addMessageListener(this); networkNode.addConnectionListener(this); @@ -227,13 +232,20 @@ private String containsSpecialKey(Set collection) { * Returns the set of known payload hashes. This is used in the GetData path to request missing data from peer nodes */ private Set getKnownPayloadHashes() { + // We collect the keys of the PersistableNetworkPayload items so we exclude them in our request. // PersistedStoragePayload items don't get removed, so we don't have an issue with the case that // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would // miss that event if we do not load the full set or use some delta handling. - Set excludedKeys = this.appendOnlyDataStoreService.getMap("since " + Version.VERSION).keySet().stream() - .map(e -> e.bytes) - .collect(Collectors.toSet()); + Set excludedKeys; + if (seedNodeRepository != null && seedNodeRepository.isSeedNode(networkNode.getNodeAddress())) + excludedKeys = this.appendOnlyDataStoreService.getMap().keySet().stream() + .map(e -> e.bytes) + .collect(Collectors.toSet()); + else + excludedKeys = this.appendOnlyDataStoreService.getMap("since " + Version.VERSION).keySet().stream() + .map(e -> e.bytes) + .collect(Collectors.toSet()); Set excludedKeysFromPersistedEntryMap = this.map.keySet() .stream() From a37424901838372c87f8636e2aa7d1fcc595fe00 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 4 May 2020 15:13:02 +0200 Subject: [PATCH 085/108] Cleanup integration test script --- PreferencesPayload | Bin 0 -> 988 bytes reduce_initial_request_size_test.sh | 22 +++++++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 PreferencesPayload diff --git a/PreferencesPayload b/PreferencesPayload new file mode 100644 index 0000000000000000000000000000000000000000..97d0f2b1949a0fa92a1576e95f4d625104963429 GIT binary patch literal 988 zcmah{K~K~`6m~khE>2*92?uB}E@+~dup9M8La^PgfZ5%wEe(W|v+Y19wlm9gs{8~! z>dAQ1qbH9ZO}ub4@gVW!*<*pd4REhVS7CpEE_n zhNp<4saZ#c)x%>}LM01GYAzsX`3|18vQ$YDGr}8=h+`rbYp7aa=2ZmO?E#*!B~vVo z8UvB>u!MEpyoO-ga`0rEa1x&F*N8cTV8?fHrISffw&~QIM{u+6;_*#hG`ZP`(h}9> z+|%`KJmF?(nrph*Fg}5G-JCq?jPD6NA1O6TS6Z#%8Kk8S zTjs)rC<#MJ)3kLrI7jrxg>Cw#6sM{XU-~cWx5)}cv%~bD`IK-)n+a{)x}Qjqrj*7@ zxN$1;7egB~BbCH!>TuP(c`m~$Cn#yT)9MCrXVJ{C+;vw3B#uO?`XZ!Lz`cW(IeoSz zNk|7$C=rNwo2keO0)5}gWBZ)({VvIP5DmmA8%=@7AOiB@(>n}bfP2OFb?p_%5A;nD z-xcwF5kCO<34kL2euA-9Bof+6=yL-c8Q_Znjt%g`054%F@_FqwJg&Sw%|1f#3q_6> ALI3~& literal 0 HcmV?d00001 diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh index dd264c31c66..4b7b46ca624 100755 --- a/reduce_initial_request_size_test.sh +++ b/reduce_initial_request_size_test.sh @@ -1,5 +1,8 @@ #!/bin/sh +LAST_RELEASE='1.3.4' +PR='reduce_initial_request_size' + install() { mkdir -p installdir/$2 @@ -45,7 +48,9 @@ staap() screen -S localnet -X at "#" stuff "^C" # quit all screen windows which results in killing the session screen -S localnet -X at "#" kill + set +e screen -wipe + set -e } check() @@ -68,6 +73,8 @@ rm -rf .localnet rm -rf installdir staap +set -e + # deploy configuration files and start bitcoind make localnet for target in \ @@ -88,11 +95,12 @@ cp AccountAgeWitnessStore_BTC_MAINNET AccountAgeWitnessStore_BTC_REGTEST cp SignedWitnessStore_BTC_MAINNET SignedWitnessStore_BTC_REGTEST cd - -# start with 1.3.2 setup -# - get sources for 1.3.2 -git checkout v1.3.2 -f +# start with release setup +# - get sources for release +git checkout release/v$LAST_RELEASE # - build initial binaries and file structure +./gradlew clean ./gradlew :seednode:build ./gradlew :desktop:build @@ -117,13 +125,13 @@ echo "##### Sanity check ###########################################" > result.l check # upgrade to PR -git checkout -f reduce_initial_request_size +git checkout $PR # create release data stores cd p2p/src/main/resources/ -cp TradeStatistics2Store_BTC_REGTEST TradeStatistics2Store_1.3.2_BTC_REGTEST -cp AccountAgeWitnessStore_BTC_REGTEST AccountAgeWitnessStore_1.3.2_BTC_REGTEST -cp SignedWitnessStore_BTC_REGTEST SignedWitnessStore_1.3.2_BTC_REGTEST +cp TradeStatistics2Store_BTC_REGTEST TradeStatistics2Store_${LAST_RELEASE}_BTC_REGTEST +cp AccountAgeWitnessStore_BTC_REGTEST AccountAgeWitnessStore_${LAST_RELEASE}_BTC_REGTEST +cp SignedWitnessStore_BTC_REGTEST SignedWitnessStore_${LAST_RELEASE}_BTC_REGTEST cd - ./gradlew :seednode:build From 8e55f640a4eebcd9a8c0bc83b1270e2072780944 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 11 May 2020 10:38:27 +0200 Subject: [PATCH 086/108] Ignore some tests CI does have troubles with tests which do file operations. Thus, these tests have been disabled. They have been useful during development and are useful for testing locally, though. --- .../java/bisq/core/network/p2p/FileDatabaseTest.java | 11 +++++++++-- .../test/java/bisq/network/p2p/storage/TestState.java | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 57701762f59..fb258463d1f 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -44,6 +44,7 @@ import org.junit.After; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -123,8 +124,11 @@ public void migrationScenario() throws IOException, InterruptedException { * RESULT: * There are n + 1 data stores in the users working directory, one holding the live * database, the other 2 being the exact and readonly copy of the data stores in - * resources. Plus, the data stores in the working dir do not share any set of objects. + * resources. Plus, the data stores in the working dir do not share any set of objects.

      + * + * Ignored because file-based tests do not work well with CI. */ + @Ignore @Test public void updateScenario() throws Exception { // setup scenario @@ -160,8 +164,11 @@ public void updateScenario() throws Exception { * and none in the working directory.

      * * RESULT: - * After startup, there should be 3 data stores in the working directory. + * After startup, there should be 3 data stores in the working directory.

      + * + * Ignored because file-based tests do not work well with CI. */ + @Ignore @Test public void freshInstallScenario() throws Exception { // setup scenario diff --git a/p2p/src/test/java/bisq/network/p2p/storage/TestState.java b/p2p/src/test/java/bisq/network/p2p/storage/TestState.java index e71246a0310..04aba199e03 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/TestState.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/TestState.java @@ -83,7 +83,7 @@ public class TestState { this.mockBroadcaster, new AppendOnlyDataStoreServiceFake(), this.protectedDataStoreService, mock(ResourceDataStoreService.class), - this.mockSeqNrStorage, this.clockFake, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); + this.mockSeqNrStorage, null, this.clockFake, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); this.appendOnlyDataStoreListener = mock(AppendOnlyDataStoreListener.class); this.hashMapChangedListener = mock(HashMapChangedListener.class); @@ -129,7 +129,7 @@ private static P2PDataStorage createP2PDataStorageForTest( broadcaster, new AppendOnlyDataStoreServiceFake(), protectedDataStoreService, mock(ResourceDataStoreService.class), - sequenceNrMapStorage, clock, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); + sequenceNrMapStorage, null, clock, MAX_SEQUENCE_NUMBER_MAP_SIZE_BEFORE_PURGE); // Currently TestState only supports reading ProtectedStorageEntries off disk. p2PDataStorage.readFromResources("unused"); From e927e6d4cf343684d8d12e84bc76f3b5e13f927a Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 11 May 2020 11:26:28 +0200 Subject: [PATCH 087/108] Adjust P2PDataStorage tests --- .../p2p/storage/P2PDataStorageRequestDataTest.java | 12 ++++++++---- .../mocks/AppendOnlyDataStoreServiceFake.java | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java index 27e522daaad..7fedd330c23 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/P2PDataStorageRequestDataTest.java @@ -98,7 +98,8 @@ public void buildPreliminaryGetDataRequest_EmptyP2PDataStore() { Assert.assertEquals(getDataRequest.getNonce(), 1); Assert.assertEquals(getDataRequest.getSupportedCapabilities(), Capabilities.app); - Assert.assertTrue(getDataRequest.getExcludedKeys().isEmpty()); + Assert.assertEquals(1, getDataRequest.getExcludedKeys().size()); + Assert.assertTrue(getDataRequest.getExcludedKeys().stream().map(bytes -> new String(bytes).trim()).filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")).findFirst().isPresent()); } // TESTCASE: P2PDataStorage with no entries returns an empty PreliminaryGetDataRequest @@ -109,7 +110,8 @@ public void buildGetUpdatedDataRequest_EmptyP2PDataStore() { Assert.assertEquals(getDataRequest.getNonce(), 1); Assert.assertEquals(getDataRequest.getSenderNodeAddress(), this.localNodeAddress); - Assert.assertTrue(getDataRequest.getExcludedKeys().isEmpty()); + Assert.assertEquals(1, getDataRequest.getExcludedKeys().size()); + Assert.assertTrue(getDataRequest.getExcludedKeys().stream().map(bytes -> new String(bytes).trim()).filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")).findFirst().isPresent()); } // TESTCASE: P2PDataStorage with PersistableNetworkPayloads and ProtectedStorageEntry generates @@ -131,7 +133,8 @@ public void buildPreliminaryGetDataRequest_FilledP2PDataStore() throws NoSuchAlg Assert.assertEquals(getDataRequest.getNonce(), 1); Assert.assertEquals(getDataRequest.getSupportedCapabilities(), Capabilities.app); - Assert.assertEquals(4, getDataRequest.getExcludedKeys().size()); + Assert.assertEquals(5, getDataRequest.getExcludedKeys().size()); + Assert.assertTrue(getDataRequest.getExcludedKeys().stream().map(bytes -> new String(bytes).trim()).filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")).findFirst().isPresent()); Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd1.getHash())); Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd2.getHash())); Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), @@ -160,7 +163,8 @@ public void requestData_FilledP2PDataStore_GetUpdatedDataRequest() throws NoSuch Assert.assertEquals(getDataRequest.getNonce(), 1); Assert.assertEquals(getDataRequest.getSenderNodeAddress(), this.localNodeAddress); - Assert.assertEquals(4, getDataRequest.getExcludedKeys().size()); + Assert.assertEquals(5, getDataRequest.getExcludedKeys().size()); + Assert.assertTrue(getDataRequest.getExcludedKeys().stream().map(bytes -> new String(bytes).trim()).filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")).findFirst().isPresent()); Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd1.getHash())); Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), toAdd2.getHash())); Assert.assertTrue(byteSetContains(getDataRequest.getExcludedKeys(), diff --git a/p2p/src/test/java/bisq/network/p2p/storage/mocks/AppendOnlyDataStoreServiceFake.java b/p2p/src/test/java/bisq/network/p2p/storage/mocks/AppendOnlyDataStoreServiceFake.java index 155e4708be7..4a693df3772 100644 --- a/p2p/src/test/java/bisq/network/p2p/storage/mocks/AppendOnlyDataStoreServiceFake.java +++ b/p2p/src/test/java/bisq/network/p2p/storage/mocks/AppendOnlyDataStoreServiceFake.java @@ -37,6 +37,10 @@ public AppendOnlyDataStoreServiceFake() { map = new HashMap<>(); } + public Map getMap(String filter) { + return getMap(); + } + public Map getMap() { return map; } From 2fb22321bf5958bdef45ce49fab001c76833624c Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 12 May 2020 09:20:53 +0200 Subject: [PATCH 088/108] Add another migration test This test addresses the migration scenario where a user does not upgrade on the first possible occation (to the first Bisq version that has the new database structure in place) but does so later. --- .../core/network/p2p/FileDatabaseTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index fb258463d1f..22bdd5b0bd1 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -113,6 +113,48 @@ public void migrationScenario() throws IOException, InterruptedException { Assert.assertEquals(1, DUT.getMap().size() - DUT.getMap("since " + getVersion(0)).size()); } + /** + * TEST CASE: test migration scenario from old database file model to new one but the + * user skipped some releases before upgrading

      + * + * USE CASE: + * We migrate from just having one working-dir database file to having multiple. In + * detail, the user starts with having one database file in her working directory and + * multiple data store files in her resources. This can happen if the user does not + * upgrade at the first possible moment. After the update, however, there is all of the + * resource files copied to her working directory plus the live data store.

      + * + * RESULT: + * There are 2 data stores in her working directory, one holding the live database, + * the other one being the exact and readonly copy of the database in resources. Plus, + * the 2 data stores do not share any set of objects. + */ + @Test + public void migrationScenario2() throws Exception { + // setup scenario + // - create one data store in working directory + createDatabase(createFile(false, "AccountAgeWitnessStore"), object1, object2); + // - create one data store in resources with new naming scheme + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); + createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); + + // beware of the nasty hack! + // TODO replace as soon as we have at least one version string in history + setFinalStatic(Version.class.getField("history"), Arrays.asList(getVersion(-1))); + + // simulate bisq startup + final AppendOnlyDataStoreService DUT = loadDatabase(); + + // check result + // - check total number of elements + Assert.assertEquals(2, DUT.getMap().size()); + // - are there 2 data stores in working-dir + Assert.assertEquals(3, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); + // - do the 2 data stores share objects + Assert.assertEquals(0, DUT.getMap("since " + getVersion(0)).size()); + Assert.assertEquals(1, DUT.getMap("since " + getVersion(-1)).size()); + } + /** * TEST CASE: test Bisq software update scenario

      * From 5f8b7cfe8f0c929b351d909b8014f0a272f9d13a Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 12 May 2020 09:22:27 +0200 Subject: [PATCH 089/108] Reactivate ignored tests --- .../bisq/core/network/p2p/FileDatabaseTest.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 22bdd5b0bd1..776a3968294 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -166,11 +166,8 @@ public void migrationScenario2() throws Exception { * RESULT: * There are n + 1 data stores in the users working directory, one holding the live * database, the other 2 being the exact and readonly copy of the data stores in - * resources. Plus, the data stores in the working dir do not share any set of objects.

      - * - * Ignored because file-based tests do not work well with CI. + * resources. Plus, the data stores in the working dir do not share any set of objects. */ - @Ignore @Test public void updateScenario() throws Exception { // setup scenario @@ -183,7 +180,7 @@ public void updateScenario() throws Exception { // beware of the nasty hack! // TODO replace as soon as we have at least one version string in history - setFinalStatic(Version.class.getField("history"), Arrays.asList("1.3.1")); + setFinalStatic(Version.class.getField("history"), Arrays.asList(getVersion(-1))); // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -206,11 +203,8 @@ public void updateScenario() throws Exception { * and none in the working directory.

      * * RESULT: - * After startup, there should be 3 data stores in the working directory.

      - * - * Ignored because file-based tests do not work well with CI. + * After startup, there should be 3 data stores in the working directory. */ - @Ignore @Test public void freshInstallScenario() throws Exception { // setup scenario @@ -220,7 +214,7 @@ public void freshInstallScenario() throws Exception { // beware of the nasty hack! // TODO replace as soon as we have at least one version string in history - setFinalStatic(Version.class.getField("history"), Arrays.asList("1.3.1")); + setFinalStatic(Version.class.getField("history"), Arrays.asList(getVersion(-1))); // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); From f8320fe10a6d493008fe1fc5e601740c10948133 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Wed, 20 May 2020 12:26:32 +0200 Subject: [PATCH 090/108] Refactoring and cleaning up --- .../network/p2p/storage/P2PDataStorage.java | 50 ++++++++---- .../persistence/SplitStoreService.java | 77 +++++++++++++++---- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index 1194153f33e..db24500e98d 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -92,7 +92,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; +import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; @@ -213,6 +213,13 @@ public GetUpdatedDataRequest buildGetUpdatedDataRequest(NodeAddress senderNodeAd return new GetUpdatedDataRequest(senderNodeAddress, nonce, this.getKnownPayloadHashes()); } + /** + * Create the special key.

      + * + * For example: "1.3.4" encoded into a 20 byte array. + * + * @return the special key + */ private byte[] getSpecialKey() { byte[] result = new byte[20]; Arrays.fill(result, (byte) 0); @@ -220,12 +227,19 @@ private byte[] getSpecialKey() { return result; } - private String containsSpecialKey(Set collection) { - Optional result = collection.stream().map(byteArray -> new String(byteArray.bytes).trim()).filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")).findFirst(); - if (result.isPresent()) - return result.get(); - else - return ""; + /** + * See if the request contains a "special key". + * + * @param knownPayloadHashes + * @throws NoSuchElementException if there is no "special key" in the list + * @return the "special key" + */ + private String containsSpecialKey(Set knownPayloadHashes) { + return knownPayloadHashes.stream() + .map(byteArray -> new String(byteArray.bytes).trim()) + .filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")) + .findFirst() + .orElseThrow(); } /** @@ -238,14 +252,15 @@ private Set getKnownPayloadHashes() { // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would // miss that event if we do not load the full set or use some delta handling. Set excludedKeys; - if (seedNodeRepository != null && seedNodeRepository.isSeedNode(networkNode.getNodeAddress())) + if (seedNodeRepository != null && seedNodeRepository.isSeedNode(networkNode.getNodeAddress())) { excludedKeys = this.appendOnlyDataStoreService.getMap().keySet().stream() .map(e -> e.bytes) .collect(Collectors.toSet()); - else + } else { excludedKeys = this.appendOnlyDataStoreService.getMap("since " + Version.VERSION).keySet().stream() .map(e -> e.bytes) .collect(Collectors.toSet()); + } Set excludedKeysFromPersistedEntryMap = this.map.keySet() .stream() @@ -299,18 +314,19 @@ public GetDataResponse buildGetDataResponse( Set excludedKeysAsByteArray = P2PDataStorage.ByteArray.convertBytesSetToByteArraySet(getDataRequest.getExcludedKeys()); - String specialKey = containsSpecialKey(excludedKeysAsByteArray); - - Map tmp; - if ("".equals(specialKey)) - tmp = this.appendOnlyDataStoreService.getMap(); - else { - tmp = this.appendOnlyDataStoreService.getMap("since " + specialKey); + // In case we get a "new" data request, ie. with a "special key" like "1.3.4", we + // pre-filter the data. If there is no "special key", we use all data. + Map prefilteredData; + try { + String specialKey = containsSpecialKey(excludedKeysAsByteArray); + prefilteredData = this.appendOnlyDataStoreService.getMap("since " + specialKey); + } catch (NoSuchElementException e) { + prefilteredData = this.appendOnlyDataStoreService.getMap(); } Set filteredPersistableNetworkPayloads = filterKnownHashes( - tmp, + prefilteredData, Function.identity(), excludedKeysAsByteArray, peerCapabilities, diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 0036c03e2e6..a67ea65a126 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -100,32 +100,72 @@ public Map getMap(String fi filter = filter.replace("since ", ""); if (!filter.equals(Version.VERSION)) { String finalFilter = filter; - history.entrySet().stream().filter(entry -> Integer.valueOf(entry.getKey().replace(".", "")) > Integer.valueOf(finalFilter.replace(".", ""))).forEach(entry -> result.putAll(entry.getValue().getMap())); + history.entrySet().stream() + .filter(entry -> parseSpecialKey(entry.getKey()) > parseSpecialKey(finalFilter)) + .forEach(entry -> result.putAll(entry.getValue().getMap())); } } return result; } + private int parseSpecialKey(String specialKey) { + return Integer.parseInt(specialKey.replace(".", "")); + } + + /** + * For the {@link SplitStoreService}s, we check if we already have all the historical data stores in our working + * directory. If we have, we can proceed loading the stores. If we do not, we have to transfer the fresh data stores + * from resources. + * + * @param postFix + */ @Override protected void readFromResources(String postFix) { // check Version.VERSION and see if we have latest r/o data store file in working directory - if (!new File(absolutePathOfStorageDir + File.separator + getFileName() + "_" + Version.VERSION).exists()) + if (!new File(absolutePathOfStorageDir, getFileName() + "_" + Version.VERSION).exists()) makeFileFromResourceFile(postFix); // if we have the latest file, we are good, else do stuff // TODO are we? else { - // load stores/storage + // if we have the r/o data stores in our working directory already, we can proceed on loading them. File dbDir = new File(absolutePathOfStorageDir); - List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName() + "_"))).stream().map(s -> new File(s)).collect(Collectors.toList()); + List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName() + "_"))) + .stream() + .map(s -> new File(s)) + .collect(Collectors.toList()); history = new HashMap<>(); store = readStore(getFileName()); resourceFiles.forEach(file -> { SplitStore tmp = readStore(file.getName().replace(postFix, "")); - history.put(file.getName().replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + history.put(extractSpecialKey(file.getName(), postFix), tmp); }); } } + /** + * Extracts the special key from a file name.

      + * + * Example: "TradeStatistics2Store_1.3.4_BTC_MAINNET" becomes "1.3.4" + * + * @param name the file name to begin with + * @param postFix the postfix eg. "BTC_MAINNET" + * @return the special key eg. "1.3.4" + */ + private String extractSpecialKey(String name, String postFix) { + return name.replace(postFix, "") // remove the postfix + .replace("_", "") // remove the spacings + .replace(getFileName(), ""); // remove the file name + } + + /** + * Bluntly copy and pasted from {@link StoreService} because: + *
      • The member function there is private
      • + *
      • it does not match our interface
      • + *
      • is a temporary solutions, until https://github.com/bisq-network/projects/issues/29
      + * + * @param name + * @return store + */ private T readStore(String name) { T store = storage.initAndGetPersistedWithFileName(name, 100); if (store != null) { @@ -139,6 +179,15 @@ private T readStore(String name) { return store; } + /** + * We compile a list of files available in resources and:
      + *
      • copy them to our working directory
      • + *
      • load the freshly copied stores
      • + *
      • remove the contents from our live data store
      • + *
      • persist the cleaned-up live store
      + * + * @param postFix + */ @Override protected void makeFileFromResourceFile(String postFix) { File dbDir = new File(absolutePathOfStorageDir); @@ -149,25 +198,25 @@ protected void makeFileFromResourceFile(String postFix) { List versions = new ArrayList<>(); versions.add(Version.VERSION); versions.addAll(Version.history); - List resourceFiles = versions.stream().map(s -> getFileName() + "_" + s + postFix).collect(Collectors.toList()); + List resourceFiles = versions.stream() + .map(s -> getFileName() + "_" + s + postFix) + .collect(Collectors.toList()); // if not, copy and split - resourceFiles.forEach(file -> { - final File destinationFile = new File(Paths.get(absolutePathOfStorageDir, file.replace(postFix, "")).toString()); - final String resourceFileName = file; + resourceFiles.forEach(resourceFileName -> { + final File destinationFile = new File(absolutePathOfStorageDir, resourceFileName.replace(postFix, "")); if (!destinationFile.exists()) { try { log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); FileUtil.resourceToFile(resourceFileName, destinationFile); } catch (ResourceNotFoundException e) { - log.info("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet."); + log.info("Could not find resourceFile {}. That is expected if none is provided yet.", resourceFileName); } catch (Throwable e) { - log.error("Could not copy resourceFile " + resourceFileName + " to " + - destinationFile.getAbsolutePath() + ".\n" + e.getMessage()); + log.error("Could not copy resourceFile {} to {}.\n{}", resourceFileName, destinationFile.getAbsolutePath(), e.getMessage()); e.printStackTrace(); } } else { - log.debug(file + " file exists already."); + log.debug(resourceFileName + " file exists already."); } }); @@ -177,7 +226,7 @@ protected void makeFileFromResourceFile(String postFix) { store = readStore(getFileName()); resourceFiles.forEach(file -> { SplitStore tmp = readStore(file.replace(postFix, "")); - history.put(file.replace(postFix, "").replace(getFileName(), "").replace("_", ""), tmp); + history.put(extractSpecialKey(file, postFix), tmp); // - subtract all that is in resource files store.getMap().keySet().removeAll(tmp.getMap().keySet()); }); From 017bbfd5e3118456d95eafa477e2bc3d1148d279 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Wed, 20 May 2020 15:54:35 +0200 Subject: [PATCH 091/108] Cleanup --- .../test/java/bisq/core/network/p2p/RequestDataTest.java | 1 + .../network/p2p/storage/persistence/SplitStoreService.java | 5 ++--- reduce_initial_request_size_test.sh | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index 81565ebf656..4081f5d0024 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -223,6 +223,7 @@ public SequenceNumberStorageFake() { @Override public void setNumMaxBackupFiles(int numMaxBackupFiles) { + // left empty intentionally } } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index a67ea65a126..11c8dd3ca10 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -97,9 +97,8 @@ public Map getMap(String fi // TODO do a proper language, possibly classes if (filter.startsWith("since ")) { - filter = filter.replace("since ", ""); - if (!filter.equals(Version.VERSION)) { - String finalFilter = filter; + String finalFilter = filter.replace("since ", ""); + if (!finalFilter.equals(Version.VERSION)) { history.entrySet().stream() .filter(entry -> parseSpecialKey(entry.getKey()) > parseSpecialKey(finalFilter)) .forEach(entry -> result.putAll(entry.getValue().getMap())); diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh index 4b7b46ca624..04365f70573 100755 --- a/reduce_initial_request_size_test.sh +++ b/reduce_initial_request_size_test.sh @@ -5,9 +5,9 @@ PR='reduce_initial_request_size' install() { - mkdir -p installdir/$2 - cp bisq-$1 installdir/$2/ - cp -r lib installdir/$2/ + mkdir -p "installdir/$2" + cp "bisq-$1" "installdir/$2/" + cp -r lib "installdir/$2/" } declare -A cmd From d2a665519af569f61383d1b49cf34d398bd34068 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 21 May 2020 10:31:10 +0200 Subject: [PATCH 092/108] Refactored test helpers --- .../core/network/p2p/FileDatabaseTest.java | 26 +++++-------------- .../network/p2p/FileDatabaseTestUtils.java | 12 +++++++-- .../core/network/p2p/RequestDataTest.java | 8 +++--- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 776a3968294..42a0bfbd0a3 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -60,7 +60,7 @@ public class FileDatabaseTest extends FileDatabaseTestUtils { * This does not test any business logic, just makes sure the test setup is correct. */ @Test - public void checkTestFixtures() throws IOException, InterruptedException { + public void checkTestFixtures() throws Exception { checkTestFixturesHelper(object1); checkTestFixturesHelper(object1, object2); checkTestFixturesHelper(object2); @@ -68,7 +68,7 @@ public void checkTestFixtures() throws IOException, InterruptedException { checkTestFixturesHelper(object3); } - private void checkTestFixturesHelper(AccountAgeWitness... objects) throws IOException, InterruptedException { + private void checkTestFixturesHelper(AccountAgeWitness... objects) throws Exception { createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), objects); createDatabase(createFile(false, "AccountAgeWitnessStore"), objects); AppendOnlyDataStoreService DUT = loadDatabase(); @@ -93,7 +93,7 @@ private void checkTestFixturesHelper(AccountAgeWitness... objects) throws IOExce * the 2 data stores do not share any set of objects. */ @Test - public void migrationScenario() throws IOException, InterruptedException { + public void migrationScenario() throws Exception { // setup scenario // - create one data store in working directory createDatabase(createFile(false, "AccountAgeWitnessStore"), object1, object2); @@ -138,10 +138,6 @@ public void migrationScenario2() throws Exception { createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); - // beware of the nasty hack! - // TODO replace as soon as we have at least one version string in history - setFinalStatic(Version.class.getField("history"), Arrays.asList(getVersion(-1))); - // simulate bisq startup final AppendOnlyDataStoreService DUT = loadDatabase(); @@ -178,10 +174,6 @@ public void updateScenario() throws Exception { createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); createDatabase(createFile(false, "AccountAgeWitnessStore"), object2, object3); - // beware of the nasty hack! - // TODO replace as soon as we have at least one version string in history - setFinalStatic(Version.class.getField("history"), Arrays.asList(getVersion(-1))); - // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -212,10 +204,6 @@ public void freshInstallScenario() throws Exception { createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(-1) + "_TEST"), object1); createDatabase(createFile(true, "AccountAgeWitnessStore_" + getVersion(0) + "_TEST"), object2); - // beware of the nasty hack! - // TODO replace as soon as we have at least one version string in history - setFinalStatic(Version.class.getField("history"), Arrays.asList(getVersion(-1))); - // simulate bisq startup AppendOnlyDataStoreService DUT = loadDatabase(); @@ -242,7 +230,7 @@ public void freshInstallScenario() throws Exception { * getMap returns all elements stored in the various database files. */ @Test - public void getMap() throws IOException, InterruptedException { + public void getMap() throws Exception { // setup scenario // - create 2 data stores containing historical data and a live database createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); @@ -271,7 +259,7 @@ public void getMap() throws IOException, InterruptedException { * getMap(since x) returns all elements added after snapshot x */ @Test - public void getMapSinceFilter() throws IOException, InterruptedException { + public void getMapSinceFilter() throws Exception { // setup scenario // - create 2 data stores containing historical data and a live database createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); @@ -304,7 +292,7 @@ public void getMapSinceFilter() throws IOException, InterruptedException { * map.put should only add data to the live database. Other data stores are read-only! */ @Test - public void put() throws IOException, InterruptedException { + public void put() throws Exception { // setup scenario // - create one database containing historical data and a live database createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object1); @@ -337,7 +325,7 @@ public void put() throws IOException, InterruptedException { * Check for duplicates */ @Test - public void putDuplicates() throws IOException { + public void putDuplicates() throws Exception { // setup scenario // - create one database containing historical data and a live database createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object1); diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java index dc80e17cc26..b45a3acc69a 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -115,8 +115,16 @@ protected void createDatabase(File target, * @param offset * @return relative version string to the Version.VERSION constant */ - public String getVersion(int offset) { - return new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); + public String getVersion(int offset) throws Exception { + String result = new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); + + if (offset < 0 && !Version.history.contains(result)) { + // beware of the nasty hack! + // FIXME replace as soon as we have at least one version string in history + setFinalStatic(Version.class.getField("history"), Arrays.asList(result)); + } + + return result; } /** diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index 4081f5d0024..a42ec9d6239 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -59,7 +59,7 @@ public class RequestDataTest extends FileDatabaseTestUtils { * Test whether our client creates the correct query. */ @Test - public void query() throws IOException { + public void query() throws Exception { // setup scenario createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); @@ -92,7 +92,7 @@ public void query() throws IOException { * Given the new query, see if another peer would respond correctly */ @Test - public void response() throws IOException { + public void response() throws Exception { // setup scenario createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object2); @@ -140,7 +140,7 @@ public void response() throws IOException { * However, we add an additional test to ensure Bisq version follows a strict pattern. */ @Test - public void faultySpecialKey() throws IOException { + public void faultySpecialKey() throws Exception { // setup scenario createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(0)), object1); createDatabase(createFile(false, "AccountAgeWitnessStore"), object2); @@ -192,7 +192,7 @@ public void testBisqVersionFormat() { *
    • is a sane way of reacting to a newer client
    */ @Test - public void futureSpecialKey() throws IOException { + public void futureSpecialKey() throws Exception { // setup scenario createDatabase(createFile(false, "AccountAgeWitnessStore_" + getVersion(-1)), object1); createDatabase(createFile(false, "AccountAgeWitnessStore"), object2, object3); From 469c374ee7988753aab37dbd23d515f4dfb10ab4 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Thu, 21 May 2020 10:38:26 +0200 Subject: [PATCH 093/108] Fixed a potential issue when loading stores Previously, we only checked if the newest store is there. If it is not, we copy and reload everything. If it is there, we are satisfied. Now, we check for each and every store we should have and copy and load them individually. --- .../core/network/p2p/FileDatabaseTest.java | 2 +- .../network/p2p/FileDatabaseTestUtils.java | 27 ++-- .../persistence/SplitStoreService.java | 119 ++++++------------ 3 files changed, 59 insertions(+), 89 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 42a0bfbd0a3..08a389b3730 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -211,7 +211,7 @@ public void freshInstallScenario() throws Exception { // - check total number of elements Assert.assertEquals(2, DUT.getMap().size()); // - are there 2 data stores in working-dir - Assert.assertEquals(2, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); + Assert.assertEquals(3, storageDir.list((dir, name) -> name.startsWith("AccountAgeWitnessStore") && !name.endsWith("_TEST")).length); // - do the 3 data stores share objects Assert.assertEquals(0, DUT.getMap("since " + getVersion(0)).size()); Assert.assertEquals(1, DUT.getMap("since " + getVersion(-1)).size()); diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java index b45a3acc69a..7ade603a739 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -42,16 +42,7 @@ public class FileDatabaseTestUtils { @After public void cleanup() { - try { - boolean done = false; - while (!done) { - Thread.sleep(100); - Set threads = Thread.getAllStackTraces().keySet(); - done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + waitForFile(); for (File file : files) { file.delete(); @@ -81,12 +72,28 @@ public File createFile(boolean isResourceFile, String name) { return tmp; } + public void waitForFile() { + try { + boolean done = false; + while (!done) { + Thread.sleep(100); + Set threads = Thread.getAllStackTraces().keySet(); + done = threads.stream().noneMatch(thread -> thread.getName().startsWith("Save-file-task")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + protected AppendOnlyDataStoreService loadDatabase() { Storage storage = new Storage<>(storageDir, new CorePersistenceProtoResolver(null, null, null, null), null); AccountAgeWitnessStorageService storageService = new AccountAgeWitnessStorageService(storageDir, storage); final AppendOnlyDataStoreService protectedDataStoreService = new AppendOnlyDataStoreService(); protectedDataStoreService.addService(storageService); protectedDataStoreService.readFromResources("_TEST"); + + waitForFile(); + return protectedDataStoreService; } diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 11c8dd3ca10..d5a80f563ec 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -8,8 +8,6 @@ import bisq.common.storage.ResourceNotFoundException; import bisq.common.storage.Storage; -import java.nio.file.Paths; - import java.io.File; import java.util.ArrayList; @@ -121,39 +119,28 @@ private int parseSpecialKey(String specialKey) { */ @Override protected void readFromResources(String postFix) { - // check Version.VERSION and see if we have latest r/o data store file in working directory - if (!new File(absolutePathOfStorageDir, getFileName() + "_" + Version.VERSION).exists()) - makeFileFromResourceFile(postFix); // if we have the latest file, we are good, else do stuff // TODO are we? - else { - // if we have the r/o data stores in our working directory already, we can proceed on loading them. - File dbDir = new File(absolutePathOfStorageDir); - List resourceFiles = Arrays.asList(dbDir.list((dir, name) -> name.startsWith(getFileName() + "_"))) - .stream() - .map(s -> new File(s)) - .collect(Collectors.toList()); - - history = new HashMap<>(); - store = readStore(getFileName()); - resourceFiles.forEach(file -> { - SplitStore tmp = readStore(file.getName().replace(postFix, "")); - history.put(extractSpecialKey(file.getName(), postFix), tmp); - }); - } - } + // initialize here in case this method gets called twice + history = new HashMap<>(); - /** - * Extracts the special key from a file name.

    - * - * Example: "TradeStatistics2Store_1.3.4_BTC_MAINNET" becomes "1.3.4" - * - * @param name the file name to begin with - * @param postFix the postfix eg. "BTC_MAINNET" - * @return the special key eg. "1.3.4" - */ - private String extractSpecialKey(String name, String postFix) { - return name.replace(postFix, "") // remove the postfix - .replace("_", "") // remove the spacings - .replace(getFileName(), ""); // remove the file name + // load our live data store + store = readStore(getFileName()); + + // create file list of files we should have by now + List versions = new ArrayList<>(); + versions.add(Version.VERSION); + versions.addAll(Version.history); + + // go through the list one by one + versions.forEach(version -> { + String filename = getFileName() + "_" + version + postFix; // postFix has a preceding "_" + if (new File(absolutePathOfStorageDir, filename).exists()) { + // if it is there already, load + history.put(version, readStore(getFileName() + "_" + version)); + } else { + // either copy and split + history.put(version, copyAndSplit(version, postFix)); + } + }); } /** @@ -179,60 +166,36 @@ private T readStore(String name) { } /** - * We compile a list of files available in resources and:
    - *
    • copy them to our working directory
    • - *
    • load the freshly copied stores
    • - *
    • remove the contents from our live data store
    • - *
    • persist the cleaned-up live store
    + * Copy the missing data store from resources and remove its object from the live store. * - * @param postFix + * @param version to identify the data store eg. "1.3.4" + * @param postFix the global postfix eg. "_BTC_MAINNET" + * @return the freshly copied and loaded data store */ - @Override - protected void makeFileFromResourceFile(String postFix) { - File dbDir = new File(absolutePathOfStorageDir); - if (!dbDir.exists() && !dbDir.mkdir()) - log.warn("make dir failed.\ndbDir=" + dbDir.getAbsolutePath()); - - // check resources for files - List versions = new ArrayList<>(); - versions.add(Version.VERSION); - versions.addAll(Version.history); - List resourceFiles = versions.stream() - .map(s -> getFileName() + "_" + s + postFix) - .collect(Collectors.toList()); - + private SplitStore copyAndSplit(String version, String postFix) { // if not, copy and split - resourceFiles.forEach(resourceFileName -> { - final File destinationFile = new File(absolutePathOfStorageDir, resourceFileName.replace(postFix, "")); - if (!destinationFile.exists()) { - try { - log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); - FileUtil.resourceToFile(resourceFileName, destinationFile); - } catch (ResourceNotFoundException e) { - log.info("Could not find resourceFile {}. That is expected if none is provided yet.", resourceFileName); - } catch (Throwable e) { - log.error("Could not copy resourceFile {} to {}.\n{}", resourceFileName, destinationFile.getAbsolutePath(), e.getMessage()); - e.printStackTrace(); - } - } else { - log.debug(resourceFileName + " file exists already."); - } - }); + final File destinationFile = new File(absolutePathOfStorageDir, getFileName() + "_" + version); + String resourceFileName = destinationFile.getName() + postFix; + try { + log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); + FileUtil.resourceToFile(resourceFileName, destinationFile); + } catch (ResourceNotFoundException e) { + log.info("Could not find resourceFile {}. That is expected if none is provided yet.", resourceFileName); + } catch (Throwable e) { + log.error("Could not copy resourceFile {} to {}.\n{}", resourceFileName, destinationFile.getAbsolutePath(), e.getMessage()); + e.printStackTrace(); + } // split // - get all - history = new HashMap<>(); - store = readStore(getFileName()); - resourceFiles.forEach(file -> { - SplitStore tmp = readStore(file.replace(postFix, "")); - history.put(extractSpecialKey(file, postFix), tmp); - // - subtract all that is in resource files - store.getMap().keySet().removeAll(tmp.getMap().keySet()); - }); + SplitStore historicalStore = readStore(destinationFile.getName()); + // - subtract all that is in resource files + store.getMap().keySet().removeAll(historicalStore.getMap().keySet()); // - create new file with leftovers storage.initAndGetPersisted(store, 0); storage.queueUpForSave(); + return historicalStore; } } From 249e185878272c9e994bf5c60c5db923ed774a29 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 22 May 2020 09:52:53 +0200 Subject: [PATCH 094/108] Fixed wrong filename --- .../network/p2p/storage/persistence/SplitStoreService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index d5a80f563ec..8e1af1a2cd1 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -132,7 +132,7 @@ protected void readFromResources(String postFix) { // go through the list one by one versions.forEach(version -> { - String filename = getFileName() + "_" + version + postFix; // postFix has a preceding "_" + String filename = getFileName() + "_" + version; if (new File(absolutePathOfStorageDir, filename).exists()) { // if it is there already, load history.put(version, readStore(getFileName() + "_" + version)); @@ -175,7 +175,7 @@ private T readStore(String name) { private SplitStore copyAndSplit(String version, String postFix) { // if not, copy and split final File destinationFile = new File(absolutePathOfStorageDir, getFileName() + "_" + version); - String resourceFileName = destinationFile.getName() + postFix; + String resourceFileName = destinationFile.getName() + postFix; // postFix has a preceding "_" already try { log.info("We copy resource to file: resourceFileName={}, destinationFile={}", resourceFileName, destinationFile); FileUtil.resourceToFile(resourceFileName, destinationFile); From 06ec223f4282d8b4818a92b903b1b8436647eae4 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 22 May 2020 10:52:30 +0200 Subject: [PATCH 095/108] Refactoring --- .../bisq/network/p2p/storage/P2PDataStorage.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index db24500e98d..f34af092fb2 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -92,7 +92,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; @@ -231,15 +230,14 @@ private byte[] getSpecialKey() { * See if the request contains a "special key". * * @param knownPayloadHashes - * @throws NoSuchElementException if there is no "special key" in the list - * @return the "special key" + * @return the "special key" or null if no special key has been found */ - private String containsSpecialKey(Set knownPayloadHashes) { + private String findSpecialKey(Set knownPayloadHashes) { return knownPayloadHashes.stream() .map(byteArray -> new String(byteArray.bytes).trim()) .filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")) .findFirst() - .orElseThrow(); + .orElse(null); } /** @@ -317,11 +315,11 @@ public GetDataResponse buildGetDataResponse( // In case we get a "new" data request, ie. with a "special key" like "1.3.4", we // pre-filter the data. If there is no "special key", we use all data. Map prefilteredData; - try { - String specialKey = containsSpecialKey(excludedKeysAsByteArray); - prefilteredData = this.appendOnlyDataStoreService.getMap("since " + specialKey); - } catch (NoSuchElementException e) { + String specialKey = findSpecialKey(excludedKeysAsByteArray); + if (specialKey == null) { prefilteredData = this.appendOnlyDataStoreService.getMap(); + } else { + prefilteredData = this.appendOnlyDataStoreService.getMap("since " + specialKey); } Set filteredPersistableNetworkPayloads = From eabadc0f69213b3aa3e705f0ed7dbd3f8a99511c Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 26 May 2020 12:38:12 +0200 Subject: [PATCH 096/108] Make special key platform-independent --- .../main/java/bisq/network/p2p/storage/P2PDataStorage.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index f34af092fb2..faa14c2d2c3 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -82,6 +82,8 @@ import java.time.Clock; +import java.nio.charset.StandardCharsets; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -222,7 +224,7 @@ public GetUpdatedDataRequest buildGetUpdatedDataRequest(NodeAddress senderNodeAd private byte[] getSpecialKey() { byte[] result = new byte[20]; Arrays.fill(result, (byte) 0); - System.arraycopy(Version.VERSION.getBytes(), 0, result, 0, Version.VERSION.length()); + System.arraycopy(Version.VERSION.getBytes(StandardCharsets.UTF_8), 0, result, 0, Version.VERSION.length()); return result; } @@ -234,7 +236,7 @@ private byte[] getSpecialKey() { */ private String findSpecialKey(Set knownPayloadHashes) { return knownPayloadHashes.stream() - .map(byteArray -> new String(byteArray.bytes).trim()) + .map(byteArray -> new String(byteArray.bytes, StandardCharsets.UTF_8).trim()) .filter(s -> s.matches("^[0-9]\\.[0-9]\\.[0-9]$")) .findFirst() .orElse(null); From 755fc03303467636baffa11a535dcbf96c6d6c65 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 12 Jun 2020 15:00:08 +0200 Subject: [PATCH 097/108] Fixed persisting wrong data store --- .../network/p2p/storage/persistence/SplitStoreService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 8e1af1a2cd1..855fc61c611 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -141,6 +141,9 @@ protected void readFromResources(String postFix) { history.put(version, copyAndSplit(version, postFix)); } }); + + // load our live data store again - ie to make sure our storage is indeed our live store + store = readStore(getFileName()); } /** From d218f2e27b1164049c8538c731bb6834b83308df Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 12 Jun 2020 15:00:52 +0200 Subject: [PATCH 098/108] Renamed data stores --- ...ore_BTC_MAINNET => AccountAgeWitnessStore_1.3.5_BTC_MAINNET} | 0 ...ssStore_BTC_MAINNET => SignedWitnessStore_1.3.5_BTC_MAINNET} | 0 ...tore_BTC_MAINNET => TradeStatistics2Store_1.3.5_BTC_MAINNET} | 0 reduce_initial_request_size_test.sh | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename p2p/src/main/resources/{AccountAgeWitnessStore_BTC_MAINNET => AccountAgeWitnessStore_1.3.5_BTC_MAINNET} (100%) rename p2p/src/main/resources/{SignedWitnessStore_BTC_MAINNET => SignedWitnessStore_1.3.5_BTC_MAINNET} (100%) rename p2p/src/main/resources/{TradeStatistics2Store_BTC_MAINNET => TradeStatistics2Store_1.3.5_BTC_MAINNET} (100%) diff --git a/p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.5_BTC_MAINNET similarity index 100% rename from p2p/src/main/resources/AccountAgeWitnessStore_BTC_MAINNET rename to p2p/src/main/resources/AccountAgeWitnessStore_1.3.5_BTC_MAINNET diff --git a/p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET b/p2p/src/main/resources/SignedWitnessStore_1.3.5_BTC_MAINNET similarity index 100% rename from p2p/src/main/resources/SignedWitnessStore_BTC_MAINNET rename to p2p/src/main/resources/SignedWitnessStore_1.3.5_BTC_MAINNET diff --git a/p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET b/p2p/src/main/resources/TradeStatistics2Store_1.3.5_BTC_MAINNET similarity index 100% rename from p2p/src/main/resources/TradeStatistics2Store_BTC_MAINNET rename to p2p/src/main/resources/TradeStatistics2Store_1.3.5_BTC_MAINNET diff --git a/reduce_initial_request_size_test.sh b/reduce_initial_request_size_test.sh index 04365f70573..86e0580daf5 100755 --- a/reduce_initial_request_size_test.sh +++ b/reduce_initial_request_size_test.sh @@ -1,6 +1,6 @@ #!/bin/sh -LAST_RELEASE='1.3.4' +LAST_RELEASE='1.3.5' PR='reduce_initial_request_size' install() From c34bfeee4e9760ef45be18fa111b77cee76bef09 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 16 Jun 2020 14:53:44 +0200 Subject: [PATCH 099/108] Replace TODO comment --- .../p2p/storage/persistence/AppendOnlyDataStoreService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java index 2a4155bccf5..89f50931a86 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/AppendOnlyDataStoreService.java @@ -56,7 +56,11 @@ public void readFromResources(String postFix) { } /** - * TODO to be implemented + * Same as {@link AppendOnlyDataStoreService#getMap()}, but takes a filter string. + * Currently, a string of format "since " + a version string is supported. Eg. "since 1.3.5". + * + * The filter string is kept generic so that we may not need to change the API again + * once other filters become necessary in the future. * * @param filter * @return From d6f195d7b0822cb68684e065a411cdc78dab1b7a Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Wed, 1 Jul 2020 13:46:54 +0200 Subject: [PATCH 100/108] Fixed a timing issue --- .../bisq/network/p2p/storage/persistence/SplitStoreService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 855fc61c611..12cb1540f2b 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -143,7 +143,7 @@ protected void readFromResources(String postFix) { }); // load our live data store again - ie to make sure our storage is indeed our live store - store = readStore(getFileName()); + storage.initAndGetPersistedWithFileName(getFileName(), 10); } /** From d81bdab81fbdf3fa555d9f29b03243b15a47ac18 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 1 Sep 2020 11:22:58 +0200 Subject: [PATCH 101/108] Added 1.3.6 and 1.3.7 data stores --- .../main/resources/AccountAgeWitnessStore_1.3.6_BTC_MAINNET | 3 +++ .../main/resources/AccountAgeWitnessStore_1.3.7_BTC_MAINNET | 3 +++ p2p/src/main/resources/SignedWitnessStore_1.3.6_BTC_MAINNET | 3 +++ p2p/src/main/resources/SignedWitnessStore_1.3.7_BTC_MAINNET | 3 +++ p2p/src/main/resources/TradeStatistics2Store_1.3.6_BTC_MAINNET | 3 +++ p2p/src/main/resources/TradeStatistics2Store_1.3.7_BTC_MAINNET | 3 +++ 6 files changed, 18 insertions(+) create mode 100644 p2p/src/main/resources/AccountAgeWitnessStore_1.3.6_BTC_MAINNET create mode 100644 p2p/src/main/resources/AccountAgeWitnessStore_1.3.7_BTC_MAINNET create mode 100644 p2p/src/main/resources/SignedWitnessStore_1.3.6_BTC_MAINNET create mode 100644 p2p/src/main/resources/SignedWitnessStore_1.3.7_BTC_MAINNET create mode 100644 p2p/src/main/resources/TradeStatistics2Store_1.3.6_BTC_MAINNET create mode 100644 p2p/src/main/resources/TradeStatistics2Store_1.3.7_BTC_MAINNET diff --git a/p2p/src/main/resources/AccountAgeWitnessStore_1.3.6_BTC_MAINNET b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.6_BTC_MAINNET new file mode 100644 index 00000000000..612d2ee70df --- /dev/null +++ b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.6_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:270254b409fb57bb4acf39cae0670e4e597d2688202d36fe9cdd212c2feb24ed +size 59992 diff --git a/p2p/src/main/resources/AccountAgeWitnessStore_1.3.7_BTC_MAINNET b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.7_BTC_MAINNET new file mode 100644 index 00000000000..7c72ea3dba2 --- /dev/null +++ b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.7_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a82466b1cadc97e3868c99d2832e902469164486b9ae1ec6558961f71c410ec6 +size 58318 diff --git a/p2p/src/main/resources/SignedWitnessStore_1.3.6_BTC_MAINNET b/p2p/src/main/resources/SignedWitnessStore_1.3.6_BTC_MAINNET new file mode 100644 index 00000000000..56b36618c7a --- /dev/null +++ b/p2p/src/main/resources/SignedWitnessStore_1.3.6_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a457c277e9bf72d8aa9a0792d6843271ebd030a87b8e38fccee8b4582e2836b +size 189112 diff --git a/p2p/src/main/resources/SignedWitnessStore_1.3.7_BTC_MAINNET b/p2p/src/main/resources/SignedWitnessStore_1.3.7_BTC_MAINNET new file mode 100644 index 00000000000..7d547a47719 --- /dev/null +++ b/p2p/src/main/resources/SignedWitnessStore_1.3.7_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf0749ee2edb07c9d647621656c621043090c06ae780e23d556b53033e85073e +size 581520 diff --git a/p2p/src/main/resources/TradeStatistics2Store_1.3.6_BTC_MAINNET b/p2p/src/main/resources/TradeStatistics2Store_1.3.6_BTC_MAINNET new file mode 100644 index 00000000000..696f4922db2 --- /dev/null +++ b/p2p/src/main/resources/TradeStatistics2Store_1.3.6_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4541868eb2a03a84151ca19fe67e4b3b22ab89cefa0342603d70f0d89471abe +size 595840 diff --git a/p2p/src/main/resources/TradeStatistics2Store_1.3.7_BTC_MAINNET b/p2p/src/main/resources/TradeStatistics2Store_1.3.7_BTC_MAINNET new file mode 100644 index 00000000000..e93bce51792 --- /dev/null +++ b/p2p/src/main/resources/TradeStatistics2Store_1.3.7_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:487dbff48cb101fea5db0e54f556c874d345ddf56c2eded4e2d85c59db0d35a2 +size 598071 From abbfedc9625af64635286c9e8773c6050cd3a0ba Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 1 Sep 2020 11:33:33 +0200 Subject: [PATCH 102/108] Update history version list --- common/src/main/java/bisq/common/app/Version.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index a0c1cfac926..6cafe2f2ab1 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -38,9 +38,10 @@ public class Version { * Has been introduced with version 1.3.2. Prior versions did not need the history. * * The history of versions is used in the differential data store solution which - * was introduced to get rid of storing all data every time. + * was introduced to eliminate the need to rewrite all data to disk every time the + * data changes. */ - public static final List history = Arrays.asList(); + public static final List history = Arrays.asList("1.3.5", "1.3.6"); public static int getMajorVersion(String version) { return getSubVersion(version, 0); From 5f9440639f6e8be333d74f9f378d0c1ebbb1fa68 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 1 Sep 2020 12:48:22 +0200 Subject: [PATCH 103/108] Removed deprecated test mechanisms Up until now, the tests needed to create a version history set for testing. These changes had to be made to a final static variable during runtime. Now, enough versions are in the history set anyways, so we could remove this part of the testing framework. --- .../network/p2p/FileDatabaseTestUtils.java | 37 ++----------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java index 7ade603a739..794baf50a46 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -22,9 +22,6 @@ import java.util.List; import java.util.Set; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; - import org.junit.After; /** @@ -120,41 +117,13 @@ protected void createDatabase(File target, /** * note that this function assumes a Bisq version format of x.y.z. It will not work with formats other than that eg. x.yy.z * @param offset - * @return relative version string to the Version.VERSION constant + * @return version string relative to v1.3.7 */ public String getVersion(int offset) throws Exception { - String result = new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); + String result = new StringBuilder().append(Integer.valueOf("1.3.7".replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); - if (offset < 0 && !Version.history.contains(result)) { - // beware of the nasty hack! - // FIXME replace as soon as we have at least one version string in history - setFinalStatic(Version.class.getField("history"), Arrays.asList(result)); - } + assert result.equals(Version.VERSION) | Version.history.contains(result) : "A test in FileDatabaseTest requested a version which is not included in the test environment."; return result; } - - /** - * Beware of this nasty hack!

    - * - * TODO get rid of this as soon as we have at least one version in Version.history

    - * - * Because Version.history should be static and final, we cannot create - * proper tests. Hence, we use reflections to do it. This can be removed as soon as - * there is at least one version in Version.history! - * - * @param field - * @param newValue - * @throws Exception - */ - static void setFinalStatic(Field field, Object newValue) throws Exception { - field.setAccessible(true); - - // remove final modifier from field - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - - field.set(null, newValue); - } } From 0eccec799098149fed62ce826f1a892ae8c2561e Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 1 Sep 2020 13:10:30 +0200 Subject: [PATCH 104/108] Cleanup imports --- .../core/network/p2p/FileDatabaseTest.java | 18 ------------------ .../bisq/core/network/p2p/RequestDataTest.java | 1 - .../storage/persistence/SplitStoreService.java | 2 -- 3 files changed, 21 deletions(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java index 08a389b3730..272da6f56aa 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTest.java @@ -18,33 +18,15 @@ package bisq.core.network.p2p; import bisq.core.account.witness.AccountAgeWitness; -import bisq.core.account.witness.AccountAgeWitnessStorageService; -import bisq.core.account.witness.AccountAgeWitnessStore; -import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.payload.PersistableNetworkPayload; import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; -import bisq.common.app.Version; -import bisq.common.storage.Storage; - -import java.nio.file.Files; -import java.nio.file.Path; - -import java.io.File; -import java.io.IOException; - -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import org.junit.After; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; /** diff --git a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java index a42ec9d6239..7b69ef6fe48 100644 --- a/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java +++ b/core/src/test/java/bisq/core/network/p2p/RequestDataTest.java @@ -30,7 +30,6 @@ import bisq.common.storage.Storage; import java.io.File; -import java.io.IOException; import java.util.Arrays; import java.util.HashSet; diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 12cb1540f2b..12c59401258 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -11,11 +11,9 @@ import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; From 270f1e70f40ffcce98c95d5cb12c0e29ce77aba1 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 1 Sep 2020 14:35:05 +0200 Subject: [PATCH 105/108] Included PR feedback --- .../network/p2p/storage/P2PDataStorage.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index faa14c2d2c3..e3272fa3b30 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -252,20 +252,14 @@ private Set getKnownPayloadHashes() { // an object gets removed in between PreliminaryGetDataRequest and the GetUpdatedDataRequest and we would // miss that event if we do not load the full set or use some delta handling. Set excludedKeys; - if (seedNodeRepository != null && seedNodeRepository.isSeedNode(networkNode.getNodeAddress())) { - excludedKeys = this.appendOnlyDataStoreService.getMap().keySet().stream() - .map(e -> e.bytes) - .collect(Collectors.toSet()); + boolean weAreASeedNode = seedNodeRepository != null && seedNodeRepository.isSeedNode(networkNode.getNodeAddress()); + if (weAreASeedNode) { + excludedKeys = getKeySetInBytes(this.appendOnlyDataStoreService.getMap()); } else { - excludedKeys = this.appendOnlyDataStoreService.getMap("since " + Version.VERSION).keySet().stream() - .map(e -> e.bytes) - .collect(Collectors.toSet()); + excludedKeys = getKeySetInBytes(this.appendOnlyDataStoreService.getMap("since " + Version.VERSION)); } - Set excludedKeysFromPersistedEntryMap = this.map.keySet() - .stream() - .map(e -> e.bytes) - .collect(Collectors.toSet()); + Set excludedKeysFromPersistedEntryMap = getKeySetInBytes(this.map); excludedKeys.addAll(excludedKeysFromPersistedEntryMap); excludedKeys.add(getSpecialKey()); @@ -273,6 +267,18 @@ private Set getKnownPayloadHashes() { return excludedKeys; } + /** + * Helper for extracting hash bytes from a map of objects. + * + * @param input a map of objects + * @return a list of hash bytes of the objects in the input map + */ + private Set getKeySetInBytes(Map input) { + return input.keySet().stream() + .map(e -> e.bytes) + .collect(Collectors.toSet()); + } + /** * Generic function that can be used to filter a Map * by a given set of keys and peer capabilities. From c8c63efe4bc9b7970ffacf0ea0bb1d2596e3e877 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 1 Sep 2020 15:01:33 +0200 Subject: [PATCH 106/108] Included PR feedback --- .../java/bisq/network/p2p/storage/P2PDataStorage.java | 11 ++++++++++- .../p2p/storage/persistence/SplitStoreService.java | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java index e3272fa3b30..9dce1c55351 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/P2PDataStorage.java @@ -217,6 +217,11 @@ public GetUpdatedDataRequest buildGetUpdatedDataRequest(NodeAddress senderNodeAd /** * Create the special key.

    * + *
      + *
    • A "key" is a 20 byte cryptographic hash code to identify a single p2p network message
    • + *
    • A "special key" is the label to a bundle of p2p network messages, each having standard keys
    • + *
    + * * For example: "1.3.4" encoded into a 20 byte array. * * @return the special key @@ -229,7 +234,11 @@ private byte[] getSpecialKey() { } /** - * See if the request contains a "special key". + * See if the request contains a "special key". A special key is a label of a bundle of messages. + *
      + *
    • A "key" is a 20 byte cryptographic hash code to identify a single p2p network message
    • + *
    • A "special key" is the label to a bundle of p2p network messages, each having standard keys
    • + *
    * * @param knownPayloadHashes * @return the "special key" or null if no special key has been found diff --git a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java index 12c59401258..ede4c7e9be4 100644 --- a/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java +++ b/p2p/src/main/java/bisq/network/p2p/storage/persistence/SplitStoreService.java @@ -71,7 +71,7 @@ protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, if (getMap().containsKey(hash)) return null; - PersistableNetworkPayload previous = store.getMap().putIfAbsent(hash, payload); + PersistableNetworkPayload previous = store.getMap().put(hash, payload); persist(); return previous; } From 76cc37e6bb3a87cb01ce7c5de11fa81a4156caec Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Sat, 12 Sep 2020 13:12:32 +0200 Subject: [PATCH 107/108] Added 1.3.8 data stores --- common/src/main/java/bisq/common/app/Version.java | 2 +- .../main/resources/AccountAgeWitnessStore_1.3.8_BTC_MAINNET | 3 +++ p2p/src/main/resources/SignedWitnessStore_1.3.8_BTC_MAINNET | 3 +++ p2p/src/main/resources/TradeStatistics2Store_1.3.8_BTC_MAINNET | 3 +++ 4 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 p2p/src/main/resources/AccountAgeWitnessStore_1.3.8_BTC_MAINNET create mode 100644 p2p/src/main/resources/SignedWitnessStore_1.3.8_BTC_MAINNET create mode 100644 p2p/src/main/resources/TradeStatistics2Store_1.3.8_BTC_MAINNET diff --git a/common/src/main/java/bisq/common/app/Version.java b/common/src/main/java/bisq/common/app/Version.java index 6cafe2f2ab1..986a6ed59b7 100644 --- a/common/src/main/java/bisq/common/app/Version.java +++ b/common/src/main/java/bisq/common/app/Version.java @@ -41,7 +41,7 @@ public class Version { * was introduced to eliminate the need to rewrite all data to disk every time the * data changes. */ - public static final List history = Arrays.asList("1.3.5", "1.3.6"); + public static final List history = Arrays.asList("1.3.5", "1.3.6", "1.3.7"); public static int getMajorVersion(String version) { return getSubVersion(version, 0); diff --git a/p2p/src/main/resources/AccountAgeWitnessStore_1.3.8_BTC_MAINNET b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.8_BTC_MAINNET new file mode 100644 index 00000000000..2cd4bc95078 --- /dev/null +++ b/p2p/src/main/resources/AccountAgeWitnessStore_1.3.8_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e16fedb10f60ca5f4efcd42dfb5433217445e9c77035e539bf089591e739975c +size 88574 diff --git a/p2p/src/main/resources/SignedWitnessStore_1.3.8_BTC_MAINNET b/p2p/src/main/resources/SignedWitnessStore_1.3.8_BTC_MAINNET new file mode 100644 index 00000000000..4fdc946522f --- /dev/null +++ b/p2p/src/main/resources/SignedWitnessStore_1.3.8_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4027b836bfcc4178fe131e8c5c08d10821cd1aa33cb57bab5fac9a16a3c25745 +size 438640 diff --git a/p2p/src/main/resources/TradeStatistics2Store_1.3.8_BTC_MAINNET b/p2p/src/main/resources/TradeStatistics2Store_1.3.8_BTC_MAINNET new file mode 100644 index 00000000000..b1b49939733 --- /dev/null +++ b/p2p/src/main/resources/TradeStatistics2Store_1.3.8_BTC_MAINNET @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21b93f17351defea68332d9bce9d53e8a211b08cf7093f6e40e622c18ae8c36e +size 881553 From b4aae51ea504d700706a021d194ebd19ddccb7ac Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Sat, 12 Sep 2020 13:37:09 +0200 Subject: [PATCH 108/108] Use Version.VERSION again I thought I could get by using a fixed version string so we can have tests involing future data stores... --- .../test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java index 794baf50a46..416bc1a4c11 100644 --- a/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java +++ b/core/src/test/java/bisq/core/network/p2p/FileDatabaseTestUtils.java @@ -120,7 +120,7 @@ protected void createDatabase(File target, * @return version string relative to v1.3.7 */ public String getVersion(int offset) throws Exception { - String result = new StringBuilder().append(Integer.valueOf("1.3.7".replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); + String result = new StringBuilder().append(Integer.valueOf(Version.VERSION.replace(".", "")) + offset).insert(2, ".").insert(1, ".").toString(); assert result.equals(Version.VERSION) | Version.history.contains(result) : "A test in FileDatabaseTest requested a version which is not included in the test environment.";