diff --git a/common/pom.xml b/common/pom.xml index 5e2c8619b09..066c24a23e2 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/common/src/main/java/io/bitsquare/app/Version.java b/common/src/main/java/io/bitsquare/app/Version.java index 4adb79695c9..0de3cc0cd72 100644 --- a/common/src/main/java/io/bitsquare/app/Version.java +++ b/common/src/main/java/io/bitsquare/app/Version.java @@ -24,7 +24,7 @@ public class Version { private static final Logger log = LoggerFactory.getLogger(Version.class); // The application versions - public static final String VERSION = "0.4.9.5"; + public static final String VERSION = "0.4.9.6"; // The version nr. for the objects sent over the network. A change will break the serialization of old objects. // If objects are used for both network and database the network version is applied. diff --git a/core/pom.xml b/core/pom.xml index 45dbafb6f39..905f127e131 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 core diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java b/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java index 253a7327a00..551d62edf02 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeManager.java @@ -62,6 +62,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.stream.Collectors; import java.util.stream.Stream; public class DisputeManager { @@ -543,94 +544,94 @@ private void onDisputeResultMessage(DisputeResultMessage disputeResultMessage) { dispute.setIsClosed(true); - if (dispute.disputeResultProperty().get() == null) { - dispute.setDisputeResult(disputeResult); + if (dispute.disputeResultProperty().get() != null) + log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " + + "again because the first close did not succeed. TradeId = " + tradeId); - // We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals - // There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb) - // The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives - // more BTC as he has deposited - final Contract contract = dispute.getContract(); + dispute.setDisputeResult(disputeResult); - boolean isBuyer = keyRing.getPubKeyRing().equals(contract.getBuyerPubKeyRing()); - if ((isBuyer && disputeResult.getWinner() == DisputeResult.Winner.BUYER) - || (!isBuyer && disputeResult.getWinner() == DisputeResult.Winner.SELLER) - || (isBuyer && disputeResult.getWinner() == DisputeResult.Winner.STALE_MATE)) { + // We need to avoid publishing the tx from both traders as it would create problems with zero confirmation withdrawals + // There would be different transactions if both sign and publish (signers: once buyer+arb, once seller+arb) + // The tx publisher is the winner or in case both get 50% the buyer, as the buyer has more inventive to publish the tx as he receives + // more BTC as he has deposited + final Contract contract = dispute.getContract(); + boolean isBuyer = keyRing.getPubKeyRing().equals(contract.getBuyerPubKeyRing()); + if ((isBuyer && disputeResult.getWinner() == DisputeResult.Winner.BUYER) + || (!isBuyer && disputeResult.getWinner() == DisputeResult.Winner.SELLER) + || (isBuyer && disputeResult.getWinner() == DisputeResult.Winner.STALE_MATE)) { - final Optional tradeOptional = tradeManager.getTradeById(tradeId); - Transaction payoutTx = null; - if (tradeOptional.isPresent()) { - payoutTx = tradeOptional.get().getPayoutTx(); - } else { - final Optional tradableOptional = closedTradableManager.getTradableById(tradeId); - if (tradableOptional.isPresent() && tradableOptional.get() instanceof Trade) { - payoutTx = ((Trade) tradableOptional.get()).getPayoutTx(); - } - } - if (payoutTx == null) { - if (dispute.getDepositTxSerialized() != null) { - try { - log.debug("do payout Transaction "); - - Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx( - dispute.getDepositTxSerialized(), - disputeResult.getArbitratorSignature(), - disputeResult.getBuyerPayoutAmount(), - disputeResult.getSellerPayoutAmount(), - disputeResult.getArbitratorPayoutAmount(), - contract.getBuyerPayoutAddressString(), - contract.getSellerPayoutAddressString(), - disputeResult.getArbitratorAddressAsString(), - walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG), - contract.getBuyerBtcPubKey(), - contract.getSellerBtcPubKey(), - disputeResult.getArbitratorPubKey() - ); - Transaction committedDisputedPayoutTx = tradeWalletService.addTransactionToWallet(signedDisputedPayoutTx); - log.debug("broadcast committedDisputedPayoutTx"); - tradeWalletService.broadcastTx(committedDisputedPayoutTx, new FutureCallback() { - @Override - public void onSuccess(Transaction transaction) { - log.debug("BroadcastTx succeeded. Transaction:" + transaction); - - // after successful publish we send peer the tx - - dispute.setDisputePayoutTxId(transaction.getHashAsString()); - sendPeerPublishedPayoutTxMessage(transaction, dispute, contract); - - // set state after payout as we call swapTradeEntryToAvailableEntry - if (tradeManager.getTradeById(dispute.getTradeId()).isPresent()) - tradeManager.closeDisputedTrade(dispute.getTradeId()); - else { - Optional openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId()); - if (openOfferOptional.isPresent()) - openOfferManager.closeOpenOffer(openOfferOptional.get().getOffer()); - } - } + final Optional tradeOptional = tradeManager.getTradeById(tradeId); + Transaction payoutTx = null; + if (tradeOptional.isPresent()) { + payoutTx = tradeOptional.get().getPayoutTx(); + } else { + final Optional tradableOptional = closedTradableManager.getTradableById(tradeId); + if (tradableOptional.isPresent() && tradableOptional.get() instanceof Trade) { + payoutTx = ((Trade) tradableOptional.get()).getPayoutTx(); + } + } - @Override - public void onFailure(@NotNull Throwable t) { - log.error(t.getMessage()); + if (payoutTx == null) { + if (dispute.getDepositTxSerialized() != null) { + try { + log.debug("do payout Transaction "); + + Transaction signedDisputedPayoutTx = tradeWalletService.traderSignAndFinalizeDisputedPayoutTx( + dispute.getDepositTxSerialized(), + disputeResult.getArbitratorSignature(), + disputeResult.getBuyerPayoutAmount(), + disputeResult.getSellerPayoutAmount(), + disputeResult.getArbitratorPayoutAmount(), + contract.getBuyerPayoutAddressString(), + contract.getSellerPayoutAddressString(), + disputeResult.getArbitratorAddressAsString(), + walletService.getOrCreateAddressEntry(dispute.getTradeId(), AddressEntry.Context.MULTI_SIG), + contract.getBuyerBtcPubKey(), + contract.getSellerBtcPubKey(), + disputeResult.getArbitratorPubKey() + ); + Transaction committedDisputedPayoutTx = tradeWalletService.addTransactionToWallet(signedDisputedPayoutTx); + log.debug("broadcast committedDisputedPayoutTx"); + tradeWalletService.broadcastTx(committedDisputedPayoutTx, new FutureCallback() { + @Override + public void onSuccess(Transaction transaction) { + log.debug("BroadcastTx succeeded. Transaction:" + transaction); + + // after successful publish we send peer the tx + + dispute.setDisputePayoutTxId(transaction.getHashAsString()); + sendPeerPublishedPayoutTxMessage(transaction, dispute, contract); + + // set state after payout as we call swapTradeEntryToAvailableEntry + if (tradeManager.getTradeById(dispute.getTradeId()).isPresent()) + tradeManager.closeDisputedTrade(dispute.getTradeId()); + else { + Optional openOfferOptional = openOfferManager.getOpenOfferById(dispute.getTradeId()); + if (openOfferOptional.isPresent()) + openOfferManager.closeOpenOffer(openOfferOptional.get().getOffer()); } - }); - } catch (AddressFormatException | WalletException | TransactionVerificationException e) { - e.printStackTrace(); - log.error("Error at traderSignAndFinalizeDisputedPayoutTx " + e.getMessage()); - } - } else { - log.warn("DepositTx is null. TradeId = " + tradeId); + } + + @Override + public void onFailure(@NotNull Throwable t) { + log.error(t.getMessage()); + } + }); + } catch (AddressFormatException | WalletException | TransactionVerificationException e) { + e.printStackTrace(); + log.error("Error at traderSignAndFinalizeDisputedPayoutTx " + e.getMessage()); } } else { - log.warn("We got already a payout tx. That might be the case if the other peer did not get the " + - "payout tx and opened a dispute. TradeId = " + tradeId); - dispute.setDisputePayoutTxId(payoutTx.getHashAsString()); - sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract); + log.warn("DepositTx is null. TradeId = " + tradeId); } + } else { + log.warn("We got already a payout tx. That might be the case if the other peer did not get the " + + "payout tx and opened a dispute. TradeId = " + tradeId); + dispute.setDisputePayoutTxId(payoutTx.getHashAsString()); + sendPeerPublishedPayoutTxMessage(payoutTx, dispute, contract); } - } else { - log.warn("We got a dispute msg what we have already stored. TradeId = " + tradeId); } } else { log.debug("We got a dispute result msg but we don't have a matching dispute. " + @@ -699,6 +700,23 @@ private boolean isArbitrator(DisputeResult disputeResult) { return disputeResult.getArbitratorAddressAsString().equals(walletService.getOrCreateAddressEntry(AddressEntry.Context.ARBITRATOR).getAddressString()); } + public String getNrOfDisputes(boolean isBuyer, Contract contract) { + return String.valueOf(getDisputesAsObservableList().stream() + .filter(e -> { + Contract contract1 = e.getContract(); + if (contract1 == null) + return false; + + if (isBuyer) { + NodeAddress buyerNodeAddress = contract1.getBuyerNodeAddress(); + return buyerNodeAddress != null && buyerNodeAddress.equals(contract.getBuyerNodeAddress()); + } else { + NodeAddress sellerNodeAddress = contract1.getSellerNodeAddress(); + return sellerNodeAddress != null && sellerNodeAddress.equals(contract.getSellerNodeAddress()); + } + }) + .collect(Collectors.toSet()).size()); + } /////////////////////////////////////////////////////////////////////////////////////////// // Utils diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java b/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java index a7a059023b2..7cf0fdba4ab 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Arrays; @@ -47,18 +48,27 @@ public enum Winner { STALE_MATE } + // only append new values as we use the ordinal value public enum Reason { BUG, USABILITY, SCAM, - OTHER + OTHER, + PROTOCOL_VIOLATION, + NO_REPLY } public final String tradeId; public final int traderId; private DisputeFeePolicy disputeFeePolicy; private Winner winner; + + // Keep it for backward compatibility but use reasonOrdinal to allow changes in the ENum without breaking serialisation + @Deprecated + @Nullable private Reason reason; + private int reasonOrdinal = Reason.OTHER.ordinal(); + private boolean tamperProofEvidence; private boolean idVerification; private boolean screenCast; @@ -145,17 +155,20 @@ public DisputeFeePolicy getDisputeFeePolicy() { } public void setReason(Reason reason) { - this.reason = reason; + this.reasonOrdinal = reason.ordinal(); } public Reason getReason() { - return reason; + if (reasonOrdinal < Reason.values().length) + return Reason.values()[reasonOrdinal]; + else + return Reason.OTHER; } public void setSummaryNotes(String summaryNotes) { this.summaryNotesProperty.set(summaryNotes); } - + public StringProperty summaryNotesProperty() { return summaryNotesProperty; } @@ -249,6 +262,7 @@ public boolean equals(Object o) { if (closeDate != that.closeDate) return false; if (tradeId != null ? !tradeId.equals(that.tradeId) : that.tradeId != null) return false; if (disputeFeePolicy != that.disputeFeePolicy) return false; + if (reasonOrdinal != that.reasonOrdinal) return false; if (reason != that.reason) return false; if (disputeFeePolicy != null && that.disputeFeePolicy != null && disputeFeePolicy.ordinal() != that.disputeFeePolicy.ordinal()) @@ -265,7 +279,7 @@ else if ((reason == null && that.reason != null) || (reason != null && that.reas return false; else if ((winner == null && that.winner != null) || (winner != null && that.winner == null)) return false; - + if (summaryNotes != null ? !summaryNotes.equals(that.summaryNotes) : that.summaryNotes != null) return false; if (disputeCommunicationMessage != null ? !disputeCommunicationMessage.equals(that.disputeCommunicationMessage) : that.disputeCommunicationMessage != null) return false; @@ -282,6 +296,7 @@ public int hashCode() { int result = tradeId != null ? tradeId.hashCode() : 0; result = 31 * result + traderId; result = 31 * result + (disputeFeePolicy != null ? disputeFeePolicy.ordinal() : 0); + result = 31 * result + reasonOrdinal; result = 31 * result + (reason != null ? reason.ordinal() : 0); result = 31 * result + (winner != null ? winner.ordinal() : 0); result = 31 * result + (tamperProofEvidence ? 1 : 0); diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntry.java b/core/src/main/java/io/bitsquare/btc/AddressEntry.java index 38663c644d2..9b4ca524e46 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntry.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntry.java @@ -93,8 +93,21 @@ public AddressEntry(@Nullable DeterministicKey keyPair, NetworkParameters params this.keyPair = keyPair; this.params = params; this.context = context; - this.offerId = offerId; + // We got some issues that users created offers with a dev version where we added the version nr after + // the id, but we reverted that as it caused issues. To avoid ongoing issues with those dangling offers + // we add that check. + // TODO remove after version 0.4.9.7 (if no offers with that invalid id are online anymore) + if (offerId != null) { + String[] tokens = offerId.split("_"); + if (tokens.length > 1) + this.offerId = tokens[0]; + else + this.offerId = offerId; + } else { + this.offerId = null; + } + paramId = params.getId(); checkNotNull(keyPair); @@ -130,13 +143,26 @@ public void setDeterministicKey(DeterministicKey deterministicKey) { @Nullable public String getOfferId() { - return offerId; + // We got some issues that users created offers with a dev version where we added the version nr after + // the id, but we reverted that as it caused issues. To avoid ongoing issues with those dangling offers + // we add that check. + // TODO remove after version 0.4.9.7 (if no offers with that invalid id are online anymore) + if (offerId != null) { + String[] tokens = offerId.split("_"); + + if (tokens.length > 1) + return tokens[0]; + else + return offerId; + } else { + return null; + } } // For display we usually only display the first 8 characters. @Nullable public String getShortOfferId() { - return offerId != null ? offerId.substring(0, Math.min(8, offerId.length())) : null; + return getOfferId() != null ? getOfferId().substring(0, Math.min(8, getOfferId().length())) : null; } public Context getContext() { @@ -190,7 +216,7 @@ public Coin getLockedTradeAmount() { @Override public String toString() { return "AddressEntry{" + - "offerId='" + offerId + '\'' + + "offerId='" + getOfferId() + '\'' + ", context=" + context + ", address=" + getAddressString() + '}'; diff --git a/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java b/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java index 51c93054f23..dc717f286d1 100644 --- a/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java +++ b/core/src/main/java/io/bitsquare/locale/CurrencyUtil.java @@ -158,6 +158,8 @@ public static List createAllSortedCryptoCurrenciesList() { result.add(new CryptoCurrency("1CR", "1CRedit")); result.add(new CryptoCurrency("YACC", "YACCoin")); result.add(new CryptoCurrency("AIB", "Advanced Internet Blocks")); + result.add(new CryptoCurrency("OPAL", "Opal")); + result.add(new CryptoCurrency("AMP", "Synereo", true)); result.sort(TradeCurrency::compareTo); return result; diff --git a/core/src/main/java/io/bitsquare/payment/CashDepositAccount.java b/core/src/main/java/io/bitsquare/payment/CashDepositAccount.java index a2319416f7b..ef94e8f8fc9 100644 --- a/core/src/main/java/io/bitsquare/payment/CashDepositAccount.java +++ b/core/src/main/java/io/bitsquare/payment/CashDepositAccount.java @@ -19,6 +19,8 @@ import io.bitsquare.app.Version; +import javax.annotation.Nullable; + public final class CashDepositAccount extends CountryBasedPaymentAccount implements SameCountryRestrictedBankAccount { // That object is saved to disc. We need to take care of changes to not break deserialization. private static final long serialVersionUID = Version.LOCAL_DB_VERSION; @@ -41,4 +43,13 @@ public String getBankId() { public String getCountryCode() { return getCountry() != null ? getCountry().code : ""; } + + @Nullable + public String getRequirements() { + return ((CashDepositAccountContractData) contractData).getRequirements(); + } + + public void setRequirements(String requirements) { + ((CashDepositAccountContractData) contractData).setRequirements(requirements); + } } diff --git a/core/src/main/java/io/bitsquare/payment/CashDepositAccountContractData.java b/core/src/main/java/io/bitsquare/payment/CashDepositAccountContractData.java index df785450430..3a0d25c1ad3 100644 --- a/core/src/main/java/io/bitsquare/payment/CashDepositAccountContractData.java +++ b/core/src/main/java/io/bitsquare/payment/CashDepositAccountContractData.java @@ -38,7 +38,8 @@ public class CashDepositAccountContractData extends CountryBasedPaymentAccountCo protected String branchId; protected String accountNr; protected String accountType; - + @Nullable + protected String requirements; @Nullable protected String holderTaxId; @@ -59,6 +60,7 @@ public String getPaymentDetailsForTradePopup() { String accountNr = BankUtil.isAccountNrRequired(countryCode) ? BankUtil.getAccountNrLabel(countryCode) + " " + this.accountNr + "\n" : ""; String accountType = BankUtil.isAccountTypeRequired(countryCode) ? BankUtil.getAccountTypeLabel(countryCode) + " " + this.accountType + "\n" : ""; String holderIdString = BankUtil.isHolderIdRequired(countryCode) ? (BankUtil.getHolderIdLabel(countryCode) + " " + holderTaxId + "\n") : ""; + String requirementsString = requirements != null && !requirements.isEmpty() ? ("Extra requirements: " + requirements + "\n") : ""; return "Holder name: " + holderName + "\n" + "Holder email: " + holderEmail + "\n" + @@ -68,6 +70,7 @@ public String getPaymentDetailsForTradePopup() { accountNr + accountType + holderIdString + + requirementsString + "Country of bank: " + CountryUtil.getNameAndCode(getCountryCode()); } @@ -145,4 +148,14 @@ public void setAccountType(String accountType) { public String getAccountType() { return accountType; } + + @Nullable + public String getRequirements() { + return requirements; + } + + public void setRequirements(String requirements) { + this.requirements = requirements; + } + } diff --git a/core/src/main/java/io/bitsquare/payment/FasterPaymentsAccount.java b/core/src/main/java/io/bitsquare/payment/FasterPaymentsAccount.java new file mode 100644 index 00000000000..4bfd97ab406 --- /dev/null +++ b/core/src/main/java/io/bitsquare/payment/FasterPaymentsAccount.java @@ -0,0 +1,52 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare 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. + * + * Bitsquare 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 Bitsquare. If not, see . + */ + +package io.bitsquare.payment; + +import io.bitsquare.app.Version; +import io.bitsquare.locale.FiatCurrency; + +public final class FasterPaymentsAccount extends PaymentAccount { + // That object is saved to disc. We need to take care of changes to not break deserialization. + private static final long serialVersionUID = Version.LOCAL_DB_VERSION; + + public FasterPaymentsAccount() { + super(PaymentMethod.FASTER_PAYMENTS); + setSingleTradeCurrency(new FiatCurrency("GBP")); + } + + @Override + protected PaymentAccountContractData setContractData() { + return new FasterPaymentsAccountContractData(paymentMethod.getId(), id, paymentMethod.getMaxTradePeriod()); + } + + public void setSortCode(String value) { + ((FasterPaymentsAccountContractData) contractData).setSortCode(value); + } + + public String getSortCode() { + return ((FasterPaymentsAccountContractData) contractData).getSortCode(); + } + + public void setAccountNr(String value) { + ((FasterPaymentsAccountContractData) contractData).setAccountNr(value); + } + + public String getAccountNr() { + return ((FasterPaymentsAccountContractData) contractData).getAccountNr(); + } +} diff --git a/core/src/main/java/io/bitsquare/payment/FasterPaymentsAccountContractData.java b/core/src/main/java/io/bitsquare/payment/FasterPaymentsAccountContractData.java new file mode 100644 index 00000000000..98b749c2914 --- /dev/null +++ b/core/src/main/java/io/bitsquare/payment/FasterPaymentsAccountContractData.java @@ -0,0 +1,59 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare 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. + * + * Bitsquare 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 Bitsquare. If not, see . + */ + +package io.bitsquare.payment; + +import io.bitsquare.app.Version; + +public final class FasterPaymentsAccountContractData extends PaymentAccountContractData { + // That object is sent over the wire, so we need to take care of version compatibility. + private static final long serialVersionUID = Version.P2P_NETWORK_VERSION; + + private String sortCode; + private String accountNr; + + public FasterPaymentsAccountContractData(String paymentMethod, String id, long maxTradePeriod) { + super(paymentMethod, id, maxTradePeriod); + } + + public void setAccountNr(String accountNr) { + this.accountNr = accountNr; + } + + public String getAccountNr() { + return accountNr; + } + + public String getSortCode() { + return sortCode; + } + + public void setSortCode(String sortCode) { + this.sortCode = sortCode; + } + + @Override + public String getPaymentDetails() { + return "FasterPayments - UK Sort code: " + sortCode + ", Account number: " + accountNr; + } + + @Override + public String getPaymentDetailsForTradePopup() { + return "UK Sort code: " + sortCode + "\n" + + "Account number: " + accountNr; + } +} diff --git a/core/src/main/java/io/bitsquare/payment/PaymentAccountFactory.java b/core/src/main/java/io/bitsquare/payment/PaymentAccountFactory.java index c519f522222..397fa09a9ac 100644 --- a/core/src/main/java/io/bitsquare/payment/PaymentAccountFactory.java +++ b/core/src/main/java/io/bitsquare/payment/PaymentAccountFactory.java @@ -31,6 +31,8 @@ public static PaymentAccount getPaymentAccount(PaymentMethod paymentMethod) { return new PerfectMoneyAccount(); case PaymentMethod.SEPA_ID: return new SepaAccount(); + case PaymentMethod.FASTER_PAYMENTS_ID: + return new FasterPaymentsAccount(); case PaymentMethod.NATIONAL_BANK_ID: return new NationalBankAccount(); case PaymentMethod.SAME_BANK_ID: diff --git a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java index 5ef034f938b..b1a2e7fec69 100644 --- a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java +++ b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java @@ -44,6 +44,7 @@ public final class PaymentMethod implements Persistable, Comparable { public static final String OK_PAY_ID = "OK_PAY"; public static final String PERFECT_MONEY_ID = "PERFECT_MONEY"; public static final String SEPA_ID = "SEPA"; + public static final String FASTER_PAYMENTS_ID = "FASTER_PAYMENTS"; public static final String NATIONAL_BANK_ID = "NATIONAL_BANK"; public static final String SAME_BANK_ID = "SAME_BANK"; public static final String SPECIFIC_BANKS_ID = "SPECIFIC_BANKS"; @@ -57,6 +58,7 @@ public final class PaymentMethod implements Persistable, Comparable { public static PaymentMethod OK_PAY; public static PaymentMethod PERFECT_MONEY; public static PaymentMethod SEPA; + public static PaymentMethod FASTER_PAYMENTS; public static PaymentMethod NATIONAL_BANK; public static PaymentMethod SAME_BANK; public static PaymentMethod SPECIFIC_BANKS; @@ -70,15 +72,16 @@ public final class PaymentMethod implements Persistable, Comparable { public static final List ALL_VALUES = new ArrayList<>(Arrays.asList( OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY, Coin.parseCoin("1.5")), // tx instant so min. wait time SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY, Coin.parseCoin("1")), // sepa takes 1-3 business days. We use 8 days to include safety for holidays + FASTER_PAYMENTS = new PaymentMethod(FASTER_PAYMENTS_ID, 0, DAY, Coin.parseCoin("1")), + CLEAR_X_CHANGE = new PaymentMethod(CLEAR_X_CHANGE_ID, 0, 8 * DAY, Coin.parseCoin("0.5")), + SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("1.5")), NATIONAL_BANK = new PaymentMethod(NATIONAL_BANK_ID, 0, 4 * DAY, Coin.parseCoin("1")), SAME_BANK = new PaymentMethod(SAME_BANK_ID, 0, 2 * DAY, Coin.parseCoin("1")), SPECIFIC_BANKS = new PaymentMethod(SPECIFIC_BANKS_ID, 0, 4 * DAY, Coin.parseCoin("1")), PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY, Coin.parseCoin("1")), - SWISH = new PaymentMethod(SWISH_ID, 0, DAY, Coin.parseCoin("1.5")), ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY, Coin.parseCoin("1.5")), - CLEAR_X_CHANGE = new PaymentMethod(CLEAR_X_CHANGE_ID, 0, 8 * DAY, Coin.parseCoin("0.5")), - US_POSTAL_MONEY_ORDER = new PaymentMethod(US_POSTAL_MONEY_ORDER_ID, 0, 6 * DAY, Coin.parseCoin("0.5")), CASH_DEPOSIT = new PaymentMethod(CASH_DEPOSIT_ID, 0, 6 * DAY, Coin.parseCoin("0.5")), + US_POSTAL_MONEY_ORDER = new PaymentMethod(US_POSTAL_MONEY_ORDER_ID, 0, 6 * DAY, Coin.parseCoin("0.5")), BLOCK_CHAINS = new PaymentMethod(BLOCK_CHAINS_ID, 0, DAY, Coin.parseCoin("2")) )); diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index cb805b94473..68d3e99e0a5 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -277,7 +277,7 @@ public Fiat getMinOfferVolume() { } public String getReferenceText() { - return id.substring(0, Math.min(8, id.length())); + return getId().substring(0, Math.min(8, getId().length())); } @@ -348,11 +348,23 @@ public long getProtocolVersion() { } public String getId() { - return id; + // We got some issues that users created offers with a dev version where we added the version nr after + // the id, but we reverted that as it caused issues. To avoid ongoing issues with those dangling offers + // we add that check. + // TODO remove after version 0.4.9.7 (if no offers with that invalid id are online anymore) + if (id != null) { + String[] tokens = id.split("_"); + if (tokens.length > 1) + return tokens[0]; + else + return id; + } else { + return null; + } } public String getShortId() { - return id.substring(0, Math.min(8, id.length())); + return getId().substring(0, Math.min(8, getId().length())); } public NodeAddress getOffererNodeAddress() { @@ -513,7 +525,7 @@ public boolean equals(Object o) { if (useMarketBasedPrice != that.useMarketBasedPrice) return false; if (amount != that.amount) return false; if (minAmount != that.minAmount) return false; - if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false; if (direction != null && that.direction != null && direction.ordinal() != that.direction.ordinal()) return false; @@ -542,7 +554,7 @@ else if ((direction == null && that.direction != null) || (direction != null && @Override public int hashCode() { - int result = id != null ? id.hashCode() : 0; + int result = getId() != null ? getId().hashCode() : 0; result = 31 * result + (direction != null ? direction.ordinal() : 0); result = 31 * result + (currencyCode != null ? currencyCode.hashCode() : 0); result = 31 * result + (int) (date ^ (date >>> 32)); @@ -568,7 +580,7 @@ public int hashCode() { @Override public String toString() { return "Offer{" + - "\n\tid='" + id + '\'' + + "\n\tid='" + getId() + '\'' + "\n\tdirection=" + direction + "\n\tcurrencyCode='" + currencyCode + '\'' + "\n\tdate=" + new Date(date) + diff --git a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java index d80cb315e36..948950ade5d 100644 --- a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java +++ b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatistics.java @@ -97,6 +97,18 @@ public Fiat getTradeVolume() { return new ExchangeRate(getTradePrice()).coinToFiat(getTradeAmount()); } + public String getOfferId() { + // We got some issues that users created offers with a dev version where we added the version nr after + // the id, but we reverted that as it caused issues. To avoid ongoing issues with those dangling offers + // we add that check. + // TODO remove after version 0.4.9.7 (if no offers with that invalid id are online anymore) + String[] tokens = offerId.split("_"); + if (tokens.length > 1) + return tokens[0]; + else + return offerId; + } + // We don't include the pubKeyRing as both traders might publish it if the offerer uses an old // version and update later (taker publishes first, then later offerer) // We also don't include the trade date as that is set locally and different for offerer and taker @@ -123,7 +135,7 @@ else if ((direction == null && that.direction != null) || (direction != null && if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null) return false; - if (offerId != null ? !offerId.equals(that.offerId) : that.offerId != null) return false; + if (getOfferId() != null ? !getOfferId().equals(that.getOfferId()) : that.getOfferId() != null) return false; return !(depositTxId != null ? !depositTxId.equals(that.depositTxId) : that.depositTxId != null); } @@ -142,7 +154,7 @@ public int hashCode() { result = 31 * result + (int) (temp ^ (temp >>> 32)); result = 31 * result + (int) (offerAmount ^ (offerAmount >>> 32)); result = 31 * result + (int) (offerMinAmount ^ (offerMinAmount >>> 32)); - result = 31 * result + (offerId != null ? offerId.hashCode() : 0); + result = 31 * result + (getOfferId() != null ? getOfferId().hashCode() : 0); result = 31 * result + (depositTxId != null ? depositTxId.hashCode() : 0); return result; } @@ -161,7 +173,7 @@ public String toString() { ", marketPriceMargin=" + marketPriceMargin + ", offerAmount=" + offerAmount + ", offerMinAmount=" + offerMinAmount + - ", offerId='" + offerId + '\'' + + ", offerId='" + getOfferId() + '\'' + ", depositTxId='" + depositTxId + '\'' + ", pubKeyRing=" + pubKeyRing + ", hashCode=" + hashCode() + diff --git a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsForJson.java b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsForJson.java index e6cefd2f320..9a8528b1621 100644 --- a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsForJson.java +++ b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsForJson.java @@ -54,7 +54,7 @@ public TradeStatisticsForJson(TradeStatistics tradeStatistics) { this.marketPriceMargin = tradeStatistics.marketPriceMargin; this.offerAmount = tradeStatistics.offerAmount; this.offerMinAmount = tradeStatistics.offerMinAmount; - this.offerId = tradeStatistics.offerId; + this.offerId = tradeStatistics.getOfferId(); this.tradePrice = tradeStatistics.tradePrice; this.tradeAmount = tradeStatistics.tradeAmount; this.tradeDate = tradeStatistics.tradeDate; diff --git a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java index a3e1dd2e9ea..c0eff3d91d8 100644 --- a/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java +++ b/core/src/main/java/io/bitsquare/trade/statistics/TradeStatisticsManager.java @@ -95,7 +95,7 @@ public void onRemoved(ProtectedStorageEntry data) { public void add(TradeStatistics tradeStatistics) { if (!tradeStatisticsSet.contains(tradeStatistics)) { - boolean itemAlreadyAdded = tradeStatisticsSet.stream().filter(e -> (e.offerId.equals(tradeStatistics.offerId))).findAny().isPresent(); + boolean itemAlreadyAdded = tradeStatisticsSet.stream().filter(e -> (e.getOfferId().equals(tradeStatistics.getOfferId()))).findAny().isPresent(); if (!itemAlreadyAdded) { tradeStatisticsSet.add(tradeStatistics); observableTradeStatisticsSet.add(tradeStatistics); diff --git a/doc/build.md b/doc/build.md index e04ec0bad06..0034ad4cb7e 100644 --- a/doc/build.md +++ b/doc/build.md @@ -73,11 +73,11 @@ You need to get the Bitsquare dependencies first as we need to copy the BountyCa ### 4. Copy the jdkfix jar file -Copy the jdkfix-0.4.9.5.jar from the Bitsquare jdkfix/target directory to $JAVA_HOME/jre/lib/ext/. -jdkfix-0.4.9.5.jar includes a bugfix of the SortedList class which will be released with the next JDK version. +Copy the jdkfix-0.4.9.6.jar from the Bitsquare jdkfix/target directory to $JAVA_HOME/jre/lib/ext/. +jdkfix-0.4.9.6.jar includes a bugfix of the SortedList class which will be released with the next JDK version. We need to load that class before the default java class. This step will be removed once the bugfix is in the official JDK. - $ sudo cp bitsquare/jdkfix/target/jdkfix-0.4.9.5.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.5.jar + $ sudo cp bitsquare/jdkfix/target/jdkfix-0.4.9.6.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.6.jar ### 5. Copy the BountyCastle provider jar file diff --git a/doc/install_on_unix.sh b/doc/install_on_unix.sh index 5c8b696c6ec..bab05578139 100755 --- a/doc/install_on_unix.sh +++ b/doc/install_on_unix.sh @@ -36,7 +36,7 @@ cd bitsquare mvn clean package -DskipTests -Dmaven.javadoc.skip=true echo "Copy the jdkfix jar file" -cp bitsquare/jdkfix/target/jdkfix-0.4.9.5.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.5.jar +cp bitsquare/jdkfix/target/jdkfix-0.4.9.6.jar $JAVA_HOME/jre/lib/ext/jdkfix-0.4.9.6.jar echo "Add BountyCastle.jar" cd ~ diff --git a/gui/pom.xml b/gui/pom.xml index 7447db6326a..ce6db9c02a3 100644 --- a/gui/pom.xml +++ b/gui/pom.xml @@ -22,7 +22,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java index bb25d655793..ac59f9392b3 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/BankForm.java @@ -50,7 +50,6 @@ abstract class BankForm extends PaymentMethodForm { protected final BankAccountContractData bankAccountContractData; private InputTextField bankNameInputTextField, bankIdInputTextField, branchIdInputTextField, accountNrInputTextField, holderIdInputTextField; - private TextField currencyTextField; private Label holderIdLabel; protected InputTextField holderNameInputTextField; private Label bankIdLabel; @@ -66,6 +65,8 @@ abstract class BankForm extends PaymentMethodForm { private boolean validatorsApplied; private boolean useHolderID; private Runnable closeHandler; + private ComboBox currencyComboBox; + static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) { BankAccountContractData data = (BankAccountContractData) paymentAccountContractData; String countryCode = ((BankAccountContractData) paymentAccountContractData).getCountryCode(); @@ -231,8 +232,6 @@ public void addFormForAddAccount() { gridRowFrom = gridRow + 1; Tuple3 tuple3 = addLabelComboBoxComboBox(gridPane, ++gridRow, "Country:"); - currencyTextField = addLabelTextField(gridPane, ++gridRow, "Currency:").second; - currencyTextField.setMouseTransparent(true); ComboBox regionComboBox = tuple3.second; regionComboBox.setPromptText("Select region"); @@ -278,7 +277,8 @@ public Country fromString(String s) { String countryCode = selectedItem.code; TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(countryCode); paymentAccount.setSingleTradeCurrency(currency); - currencyTextField.setText(currency.getNameAndCode()); + currencyComboBox.setDisable(false); + currencyComboBox.getSelectionModel().select(currency); bankIdLabel.setText(BankUtil.getBankIdLabel(countryCode)); branchIdLabel.setText(BankUtil.getBranchIdLabel(countryCode)); @@ -320,7 +320,7 @@ public Country fromString(String s) { holderNameInputTextField.minWidthProperty().unbind(); holderNameInputTextField.setMinWidth(300); } else { - holderNameInputTextField.minWidthProperty().bind(currencyTextField.widthProperty()); + holderNameInputTextField.minWidthProperty().bind(currencyComboBox.widthProperty()); } if (useHolderID) { @@ -382,6 +382,40 @@ public Country fromString(String s) { } }); + currencyComboBox = addLabelComboBox(gridPane, ++gridRow, "Currency:").second; + currencyComboBox.setPromptText("Select currency"); + currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedFiatCurrencies())); + currencyComboBox.setOnAction(e -> { + TradeCurrency selectedItem = currencyComboBox.getSelectionModel().getSelectedItem(); + FiatCurrency defaultCurrency = CurrencyUtil.getCurrencyByCountryCode(countryComboBox.getSelectionModel().getSelectedItem().code); + if (!defaultCurrency.equals(selectedItem)) { + new Popup<>().warning("Are you sure you want to choose a currency other than the countries default currency?") + .actionButtonText("Yes") + .onAction(() -> { + paymentAccount.setSingleTradeCurrency(selectedItem); + autoFillNameTextField(); + }) + .closeButtonText("No, restore default currency") + .onClose(() -> currencyComboBox.getSelectionModel().select(defaultCurrency)) + .show(); + } else { + paymentAccount.setSingleTradeCurrency(selectedItem); + autoFillNameTextField(); + } + }); + currencyComboBox.setConverter(new StringConverter() { + @Override + public String toString(TradeCurrency currency) { + return currency.getNameAndCode(); + } + + @Override + public TradeCurrency fromString(String string) { + return null; + } + }); + currencyComboBox.setDisable(true); + addAcceptedBanksForAddAccount(); addHolderNameAndId(); @@ -454,7 +488,7 @@ protected void addHolderNameAndId() { bankAccountContractData.setHolderName(newValue); updateFromInputs(); }); - holderNameInputTextField.minWidthProperty().bind(currencyTextField.widthProperty()); + holderNameInputTextField.minWidthProperty().bind(currencyComboBox.widthProperty()); holderNameInputTextField.setValidator(inputValidator); useHolderID = true; diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CashDepositForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CashDepositForm.java index e327854a338..3cb00c71fb0 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CashDepositForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/CashDepositForm.java @@ -21,6 +21,7 @@ import io.bitsquare.common.util.Tuple3; import io.bitsquare.common.util.Tuple4; import io.bitsquare.gui.components.InputTextField; +import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.Layout; import io.bitsquare.gui.util.validation.AccountNrValidator; @@ -35,6 +36,7 @@ import javafx.collections.FXCollections; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; +import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.util.StringConverter; @@ -49,7 +51,6 @@ public class CashDepositForm extends PaymentMethodForm { protected final CashDepositAccountContractData cashDepositAccountContractData; private InputTextField bankNameInputTextField, bankIdInputTextField, branchIdInputTextField, accountNrInputTextField, holderIdInputTextField; - private TextField currencyTextField; private Label holderIdLabel; protected InputTextField holderNameInputTextField, holderEmailInputTextField; private Label bankIdLabel; @@ -64,10 +65,13 @@ public class CashDepositForm extends PaymentMethodForm { private ComboBox accountTypeComboBox; private boolean validatorsApplied; private boolean useHolderID; + private ComboBox currencyComboBox; public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) { CashDepositAccountContractData data = (CashDepositAccountContractData) paymentAccountContractData; - String countryCode = ((CashDepositAccountContractData) paymentAccountContractData).getCountryCode(); + String countryCode = data.getCountryCode(); + String requirements = data.getRequirements(); + boolean showRequirements = requirements != null && !requirements.isEmpty(); if (data.getHolderTaxId() != null) addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Account holder name / email / " + BankUtil.getHolderIdLabel(countryCode), @@ -75,7 +79,10 @@ public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccount else addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Account holder name / email:", data.getHolderName() + " / " + data.getHolderEmail()); - addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Country of bank:", CountryUtil.getNameAndCode(countryCode)); + if (!showRequirements) + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Country of bank:", CountryUtil.getNameAndCode(countryCode)); + else + requirements += "\nCountry of bank: " + CountryUtil.getNameAndCode(countryCode); // We don't want to display more than 6 rows to avoid scrolling, so if we get too many fields we combine them horizontally int nrRows = 0; @@ -96,7 +103,6 @@ public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccount String accountNrLabel = BankUtil.getAccountNrLabel(countryCode); String accountTypeLabel = BankUtil.getAccountTypeLabel(countryCode); - boolean accountNrAccountTypeCombined = false; boolean bankNameBankIdCombined = false; boolean bankIdBranchIdCombined = false; @@ -185,6 +191,15 @@ public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccount if (!accountNrAccountTypeCombined && BankUtil.isAccountTypeRequired(countryCode)) addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, accountTypeLabel, data.getAccountType()); + if (showRequirements) { + TextArea textArea = addLabelTextArea(gridPane, ++gridRow, "Extra requirements:", "").second; + textArea.setMinHeight(45); + textArea.setMaxHeight(45); + textArea.setEditable(false); + textArea.setId("text-area-disabled"); + textArea.setText(requirements); + } + return gridRow; } @@ -222,6 +237,17 @@ public void addFormForDisplayAccount() { if (BankUtil.isAccountTypeRequired(countryCode)) addLabelTextField(gridPane, ++gridRow, BankUtil.getAccountTypeLabel(countryCode), cashDepositAccountContractData.getAccountType()).second.setMouseTransparent(false); + String requirements = cashDepositAccountContractData.getRequirements(); + boolean showRequirements = requirements != null && !requirements.isEmpty(); + if (showRequirements) { + TextArea textArea = addLabelTextArea(gridPane, ++gridRow, "Extra requirements:", "").second; + textArea.setMinHeight(30); + textArea.setMaxHeight(30); + textArea.setEditable(false); + textArea.setId("text-area-disabled"); + textArea.setText(requirements); + } + addAllowedPeriod(); } @@ -230,8 +256,6 @@ public void addFormForAddAccount() { gridRowFrom = gridRow + 1; Tuple3 tuple3 = addLabelComboBoxComboBox(gridPane, ++gridRow, "Country:"); - currencyTextField = addLabelTextField(gridPane, ++gridRow, "Currency:").second; - currencyTextField.setMouseTransparent(true); ComboBox regionComboBox = tuple3.second; regionComboBox.setPromptText("Select region"); @@ -270,7 +294,8 @@ public Country fromString(String s) { String countryCode = selectedItem.code; TradeCurrency currency = CurrencyUtil.getCurrencyByCountryCode(countryCode); paymentAccount.setSingleTradeCurrency(currency); - currencyTextField.setText(currency.getNameAndCode()); + currencyComboBox.setDisable(false); + currencyComboBox.getSelectionModel().select(currency); bankIdLabel.setText(BankUtil.getBankIdLabel(countryCode)); branchIdLabel.setText(BankUtil.getBranchIdLabel(countryCode)); @@ -313,7 +338,7 @@ public Country fromString(String s) { holderNameInputTextField.minWidthProperty().unbind(); holderNameInputTextField.setMinWidth(300); } else { - holderNameInputTextField.minWidthProperty().bind(currencyTextField.widthProperty()); + holderNameInputTextField.minWidthProperty().bind(currencyComboBox.widthProperty()); } if (useHolderID) { @@ -374,6 +399,40 @@ public Country fromString(String s) { } }); + currencyComboBox = addLabelComboBox(gridPane, ++gridRow, "Currency:").second; + currencyComboBox.setPromptText("Select currency"); + currencyComboBox.setItems(FXCollections.observableArrayList(CurrencyUtil.getAllSortedFiatCurrencies())); + currencyComboBox.setOnAction(e -> { + TradeCurrency selectedItem = currencyComboBox.getSelectionModel().getSelectedItem(); + FiatCurrency defaultCurrency = CurrencyUtil.getCurrencyByCountryCode(countryComboBox.getSelectionModel().getSelectedItem().code); + if (!defaultCurrency.equals(selectedItem)) { + new Popup<>().warning("Are you sure you want to choose a currency other than the countries default currency?") + .actionButtonText("Yes") + .onAction(() -> { + paymentAccount.setSingleTradeCurrency(selectedItem); + autoFillNameTextField(); + }) + .closeButtonText("No, restore default currency") + .onClose(() -> currencyComboBox.getSelectionModel().select(defaultCurrency)) + .show(); + } else { + paymentAccount.setSingleTradeCurrency(selectedItem); + autoFillNameTextField(); + } + }); + currencyComboBox.setConverter(new StringConverter() { + @Override + public String toString(TradeCurrency currency) { + return currency.getNameAndCode(); + } + + @Override + public TradeCurrency fromString(String string) { + return null; + } + }); + currencyComboBox.setDisable(true); + addAcceptedBanksForAddAccount(); addHolderNameAndId(); @@ -425,6 +484,14 @@ public Country fromString(String s) { } }); + TextArea requirementsTextArea = addLabelTextArea(gridPane, ++gridRow, "Extra requirements:", "").second; + requirementsTextArea.setMinHeight(30); + requirementsTextArea.setMaxHeight(30); + requirementsTextArea.textProperty().addListener((ov, oldValue, newValue) -> { + cashDepositAccountContractData.setRequirements(newValue); + updateFromInputs(); + }); + addAllowedPeriod(); addAccountNameTextFieldWithAutoFillCheckBox(); @@ -446,7 +513,7 @@ protected void addHolderNameAndId() { cashDepositAccountContractData.setHolderName(newValue); updateFromInputs(); }); - holderNameInputTextField.minWidthProperty().bind(currencyTextField.widthProperty()); + holderNameInputTextField.minWidthProperty().bind(currencyComboBox.widthProperty()); holderNameInputTextField.setValidator(inputValidator); holderEmailInputTextField = addLabelInputTextField(gridPane, ++gridRow, "Account holder email:").second; @@ -454,7 +521,7 @@ protected void addHolderNameAndId() { cashDepositAccountContractData.setHolderEmail(newValue); updateFromInputs(); }); - holderEmailInputTextField.minWidthProperty().bind(currencyTextField.widthProperty()); + holderEmailInputTextField.minWidthProperty().bind(currencyComboBox.widthProperty()); holderEmailInputTextField.setValidator(inputValidator); useHolderID = true; diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/ClearXchangeForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/ClearXchangeForm.java index 96de263b134..139468bd7de 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/ClearXchangeForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/ClearXchangeForm.java @@ -33,8 +33,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField; -import static io.bitsquare.gui.util.FormBuilder.addLabelTextField; +import static io.bitsquare.gui.util.FormBuilder.*; public class ClearXchangeForm extends PaymentMethodForm { private static final Logger log = LoggerFactory.getLogger(ClearXchangeForm.class); @@ -44,8 +43,8 @@ public class ClearXchangeForm extends PaymentMethodForm { private InputTextField mobileNrInputTextField; public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) { - addLabelTextField(gridPane, ++gridRow, "Account holder name:", ((ClearXchangeAccountContractData) paymentAccountContractData).getHolderName()); - addLabelTextField(gridPane, ++gridRow, "Email or mobile nr.:", ((ClearXchangeAccountContractData) paymentAccountContractData).getEmailOrMobileNr()); + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Account holder name:", ((ClearXchangeAccountContractData) paymentAccountContractData).getHolderName()); + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Email or mobile nr.:", ((ClearXchangeAccountContractData) paymentAccountContractData).getEmailOrMobileNr()); return gridRow; } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/FasterPaymentsForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/FasterPaymentsForm.java new file mode 100644 index 00000000000..4269ff8798a --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/FasterPaymentsForm.java @@ -0,0 +1,112 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare 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. + * + * Bitsquare 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 Bitsquare. If not, see . + */ + +package io.bitsquare.gui.components.paymentmethods; + +import io.bitsquare.gui.components.InputTextField; +import io.bitsquare.gui.util.BSFormatter; +import io.bitsquare.gui.util.Layout; +import io.bitsquare.gui.util.validation.AccountNrValidator; +import io.bitsquare.gui.util.validation.BranchIdValidator; +import io.bitsquare.gui.util.validation.InputValidator; +import io.bitsquare.locale.BSResources; +import io.bitsquare.payment.FasterPaymentsAccount; +import io.bitsquare.payment.FasterPaymentsAccountContractData; +import io.bitsquare.payment.PaymentAccount; +import io.bitsquare.payment.PaymentAccountContractData; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.bitsquare.gui.util.FormBuilder.addLabelInputTextField; +import static io.bitsquare.gui.util.FormBuilder.addLabelTextField; + +public class FasterPaymentsForm extends PaymentMethodForm { + private static final Logger log = LoggerFactory.getLogger(FasterPaymentsForm.class); + + private final FasterPaymentsAccount fasterPaymentsAccount; + private InputTextField accountNrInputTextField; + private InputTextField sortCodeInputTextField; + + public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) { + addLabelTextField(gridPane, ++gridRow, "UK Sort code:", ((FasterPaymentsAccountContractData) paymentAccountContractData).getSortCode()); + addLabelTextField(gridPane, ++gridRow, "Account number:", ((FasterPaymentsAccountContractData) paymentAccountContractData).getAccountNr()); + return gridRow; + } + + public FasterPaymentsForm(PaymentAccount paymentAccount, InputValidator inputValidator, GridPane gridPane, + int gridRow, BSFormatter formatter) { + super(paymentAccount, inputValidator, gridPane, gridRow, formatter); + this.fasterPaymentsAccount = (FasterPaymentsAccount) paymentAccount; + } + + @Override + public void addFormForAddAccount() { + gridRowFrom = gridRow + 1; + + sortCodeInputTextField = addLabelInputTextField(gridPane, ++gridRow, "UK Sort code:").second; + sortCodeInputTextField.setValidator(inputValidator); + sortCodeInputTextField.setValidator(new BranchIdValidator("GB")); + sortCodeInputTextField.textProperty().addListener((ov, oldValue, newValue) -> { + fasterPaymentsAccount.setSortCode(newValue); + updateFromInputs(); + }); + + accountNrInputTextField = addLabelInputTextField(gridPane, ++gridRow, "Account number:").second; + accountNrInputTextField.setValidator(new AccountNrValidator("GB")); + accountNrInputTextField.textProperty().addListener((ov, oldValue, newValue) -> { + fasterPaymentsAccount.setAccountNr(newValue); + updateFromInputs(); + }); + + addLabelTextField(gridPane, ++gridRow, "Currency:", fasterPaymentsAccount.getSingleTradeCurrency().getNameAndCode()); + addAllowedPeriod(); + addAccountNameTextFieldWithAutoFillCheckBox(); + } + + @Override + protected void autoFillNameTextField() { + if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { + String accountNr = accountNrInputTextField.getText(); + accountNr = StringUtils.abbreviate(accountNr, 9); + String method = BSResources.get(paymentAccount.getPaymentMethod().getId()); + accountNameTextField.setText(method.concat(": ").concat(accountNr)); + } + } + + @Override + public void addFormForDisplayAccount() { + gridRowFrom = gridRow; + addLabelTextField(gridPane, gridRow, "Account name:", fasterPaymentsAccount.getAccountName(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); + addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(fasterPaymentsAccount.getPaymentMethod().getId())); + addLabelTextField(gridPane, ++gridRow, "UK Sort code:", fasterPaymentsAccount.getSortCode()); + TextField field = addLabelTextField(gridPane, ++gridRow, "Account number:", fasterPaymentsAccount.getAccountNr()).second; + field.setMouseTransparent(false); + addLabelTextField(gridPane, ++gridRow, "Currency:", fasterPaymentsAccount.getSingleTradeCurrency().getNameAndCode()); + addAllowedPeriod(); + } + + @Override + public void updateAllInputsValid() { + allInputsValid.set(isAccountNameValid() + && sortCodeInputTextField.getValidator().validate(fasterPaymentsAccount.getSortCode()).isValid + && accountNrInputTextField.getValidator().validate(fasterPaymentsAccount.getAccountNr()).isValid + && fasterPaymentsAccount.getTradeCurrencies().size() > 0); + } +} diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PaymentMethodForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PaymentMethodForm.java index 292b18ecf8e..42531ddd8d9 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PaymentMethodForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PaymentMethodForm.java @@ -53,7 +53,7 @@ public abstract class PaymentMethodForm { protected int gridRowFrom; protected InputTextField accountNameTextField; protected CheckBox useCustomAccountNameCheckBox; - private ComboBox currencyComboBox; + protected ComboBox currencyComboBox; public PaymentMethodForm(PaymentAccount paymentAccount, InputValidator inputValidator, GridPane gridPane, int gridRow, BSFormatter formatter) { this.paymentAccount = paymentAccount; diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java index ea96896fbd1..3b000d4cd54 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/PerfectMoneyForm.java @@ -23,10 +23,12 @@ import io.bitsquare.gui.util.validation.InputValidator; import io.bitsquare.gui.util.validation.PerfectMoneyValidator; import io.bitsquare.locale.BSResources; +import io.bitsquare.locale.FiatCurrency; import io.bitsquare.payment.PaymentAccount; import io.bitsquare.payment.PaymentAccountContractData; import io.bitsquare.payment.PerfectMoneyAccount; import io.bitsquare.payment.PerfectMoneyAccountContractData; +import javafx.collections.FXCollections; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import org.apache.commons.lang3.StringUtils; @@ -65,12 +67,14 @@ public void addFormForAddAccount() { updateFromInputs(); }); - addLabelTextField(gridPane, ++gridRow, "Currency:", perfectMoneyAccount.getSingleTradeCurrency().getNameAndCode()); + addTradeCurrencyComboBox(); + currencyComboBox.setItems(FXCollections.observableArrayList(new FiatCurrency("USD"), new FiatCurrency("EUR"))); + currencyComboBox.getSelectionModel().select(0); + addAllowedPeriod(); addAccountNameTextFieldWithAutoFillCheckBox(); } - @Override protected void autoFillNameTextField() { if (useCustomAccountNameCheckBox != null && !useCustomAccountNameCheckBox.isSelected()) { @@ -88,7 +92,9 @@ public void addFormForDisplayAccount() { addLabelTextField(gridPane, ++gridRow, "Payment method:", BSResources.get(perfectMoneyAccount.getPaymentMethod().getId())); TextField field = addLabelTextField(gridPane, ++gridRow, "Account nr.:", perfectMoneyAccount.getAccountNr()).second; field.setMouseTransparent(false); + addLabelTextField(gridPane, ++gridRow, "Currency:", perfectMoneyAccount.getSingleTradeCurrency().getNameAndCode()); + addAllowedPeriod(); } diff --git a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/USPostalMoneyOrderForm.java b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/USPostalMoneyOrderForm.java index 972be156461..2ccd4f66c0d 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/USPostalMoneyOrderForm.java +++ b/gui/src/main/java/io/bitsquare/gui/components/paymentmethods/USPostalMoneyOrderForm.java @@ -43,7 +43,7 @@ public class USPostalMoneyOrderForm extends PaymentMethodForm { private TextArea postalAddressTextArea; public static int addFormForBuyer(GridPane gridPane, int gridRow, PaymentAccountContractData paymentAccountContractData) { - addLabelTextField(gridPane, ++gridRow, "Account holder name:", ((USPostalMoneyOrderAccountContractData) paymentAccountContractData).getHolderName()); + addLabelTextFieldWithCopyIcon(gridPane, ++gridRow, "Account holder name:", ((USPostalMoneyOrderAccountContractData) paymentAccountContractData).getHolderName()); TextArea textArea = addLabelTextArea(gridPane, ++gridRow, "Postal address:", "").second; textArea.setPrefHeight(60); textArea.setEditable(false); diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/content/fiataccounts/FiatAccountsView.java b/gui/src/main/java/io/bitsquare/gui/main/account/content/fiataccounts/FiatAccountsView.java index a938d19f7dd..2a57709a05e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/content/fiataccounts/FiatAccountsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/content/fiataccounts/FiatAccountsView.java @@ -307,6 +307,8 @@ private PaymentMethodForm getPaymentMethodForm(PaymentMethod paymentMethod, Paym return new PerfectMoneyForm(paymentAccount, perfectMoneyValidator, inputValidator, root, gridRow, formatter); case PaymentMethod.SEPA_ID: return new SepaForm(paymentAccount, ibanValidator, bicValidator, inputValidator, root, gridRow, formatter); + case PaymentMethod.FASTER_PAYMENTS_ID: + return new FasterPaymentsForm(paymentAccount, inputValidator, root, gridRow, formatter); case PaymentMethod.NATIONAL_BANK_ID: return new NationalBankForm(paymentAccount, inputValidator, root, gridRow, formatter, () -> onCancelNewAccount()); case PaymentMethod.SAME_BANK_ID: diff --git a/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/TraderDisputeView.java b/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/TraderDisputeView.java index 237cb9dc903..ac4522b08d0 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/TraderDisputeView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/TraderDisputeView.java @@ -39,8 +39,10 @@ import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.GUIUtil; +import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.P2PService; import io.bitsquare.p2p.network.Connection; +import io.bitsquare.trade.Contract; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import javafx.beans.property.ReadOnlyBooleanProperty; @@ -162,6 +164,13 @@ public void initialize() { TableColumn tradeIdColumn = getTradeIdColumn(); tableView.getColumns().add(tradeIdColumn); + TableColumn buyerOnionAddressColumn = getBuyerOnionAddressColumn(); + tableView.getColumns().add(buyerOnionAddressColumn); + + TableColumn sellerOnionAddressColumn = getSellerOnionAddressColumn(); + tableView.getColumns().add(sellerOnionAddressColumn); + + TableColumn marketColumn = getMarketColumn(); tableView.getColumns().add(marketColumn); @@ -173,6 +182,8 @@ public void initialize() { tradeIdColumn.setComparator((o1, o2) -> o1.getTradeId().compareTo(o2.getTradeId())); dateColumn.setComparator((o1, o2) -> o1.getOpeningDate().compareTo(o2.getOpeningDate())); + buyerOnionAddressColumn.setComparator((o1, o2) -> getBuyerOnionAddressColumnLabel(o1).compareTo(getBuyerOnionAddressColumnLabel(o2))); + sellerOnionAddressColumn.setComparator((o1, o2) -> getSellerOnionAddressColumnLabel(o1).compareTo(getSellerOnionAddressColumnLabel(o2))); marketColumn.setComparator((o1, o2) -> formatter.getCurrencyPair(o1.getContract().offer.getCurrencyCode()).compareTo(o2.getContract().offer.getCurrencyCode())); dateColumn.setSortType(TableColumn.SortType.DESCENDING); @@ -787,8 +798,8 @@ private void showArrivedIcon() { private TableColumn getSelectColumn() { TableColumn column = new TableColumn("Select") { { - setMinWidth(110); - setMaxWidth(110); + setMinWidth(80); + setMaxWidth(80); setSortable(false); } }; @@ -828,10 +839,74 @@ public void updateItem(final Dispute item, boolean empty) { return column; } + private TableColumn getContractColumn() { + TableColumn column = new TableColumn("Details") { + { + setMinWidth(80); + setSortable(false); + } + }; + column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); + column.setCellFactory( + new Callback, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + final Button button = new Button("Details"); + + { + + } + + @Override + public void updateItem(final Dispute item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + button.setOnAction(e -> onOpenContract(item)); + setGraphic(button); + } else { + setGraphic(null); + button.setOnAction(null); + } + } + }; + } + }); + return column; + } + + private TableColumn getDateColumn() { + TableColumn column = new TableColumn("Date") { + { + setMinWidth(150); + } + }; + column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); + column.setCellFactory( + new Callback, TableCell>() { + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + @Override + public void updateItem(final Dispute item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) + setText(formatter.formatDateTime(item.getOpeningDate())); + else + setText(""); + } + }; + } + }); + return column; + } + private TableColumn getTradeIdColumn() { TableColumn column = new TableColumn("Trade ID") { { - setMinWidth(130); + setMinWidth(110); } }; column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); @@ -869,10 +944,10 @@ public void updateItem(final Dispute item, boolean empty) { return column; } - private TableColumn getMarketColumn() { - TableColumn column = new TableColumn("Market") { + private TableColumn getBuyerOnionAddressColumn() { + TableColumn column = new TableColumn("BTC buyer address") { { - setMinWidth(130); + setMinWidth(170); } }; column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); @@ -885,7 +960,7 @@ public TableCell call(TableColumn column) { public void updateItem(final Dispute item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) - setText(formatter.getCurrencyPair(item.getContract().offer.getCurrencyCode())); + setText(getBuyerOnionAddressColumnLabel(item)); else setText(""); } @@ -895,10 +970,10 @@ public void updateItem(final Dispute item, boolean empty) { return column; } - private TableColumn getRoleColumn() { - TableColumn column = new TableColumn("Role") { + private TableColumn getSellerOnionAddressColumn() { + TableColumn column = new TableColumn("BTC seller address") { { - setMinWidth(130); + setMinWidth(170); } }; column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); @@ -910,14 +985,10 @@ public TableCell call(TableColumn column) { @Override public void updateItem(final Dispute item, boolean empty) { super.updateItem(item, empty); - if (item != null && !empty) { - if (item.isDisputeOpenerIsOfferer()) - setText(item.isDisputeOpenerIsBuyer() ? "BTC buyer/Offerer" : "BTC seller/Offerer"); - else - setText(item.isDisputeOpenerIsBuyer() ? "BTC buyer/Taker" : "BTC seller/Taker"); - } else { + if (item != null && !empty) + setText(getSellerOnionAddressColumnLabel(item)); + else setText(""); - } } }; } @@ -925,8 +996,35 @@ public void updateItem(final Dispute item, boolean empty) { return column; } - private TableColumn getDateColumn() { - TableColumn column = new TableColumn("Date") { + + private String getBuyerOnionAddressColumnLabel(Dispute item) { + Contract contract = item.getContract(); + if (contract != null) { + NodeAddress buyerNodeAddress = contract.getBuyerNodeAddress(); + if (buyerNodeAddress != null) + return buyerNodeAddress.getHostNameWithoutPostFix() + " (" + disputeManager.getNrOfDisputes(true, contract) + ")"; + else + return "N/A"; + } else { + return "N/A"; + } + } + + private String getSellerOnionAddressColumnLabel(Dispute item) { + Contract contract = item.getContract(); + if (contract != null) { + NodeAddress sellerNodeAddress = contract.getSellerNodeAddress(); + if (sellerNodeAddress != null) + return sellerNodeAddress.getHostNameWithoutPostFix() + " (" + disputeManager.getNrOfDisputes(false, contract) + ")"; + else + return "N/A"; + } else { + return "N/A"; + } + } + + private TableColumn getMarketColumn() { + TableColumn column = new TableColumn("Market") { { setMinWidth(130); } @@ -941,7 +1039,7 @@ public TableCell call(TableColumn column) { public void updateItem(final Dispute item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) - setText(formatter.formatDateTime(item.getOpeningDate())); + setText(formatter.getCurrencyPair(item.getContract().offer.getCurrencyCode())); else setText(""); } @@ -951,36 +1049,28 @@ public void updateItem(final Dispute item, boolean empty) { return column; } - private TableColumn getContractColumn() { - TableColumn column = new TableColumn("Contract") { + private TableColumn getRoleColumn() { + TableColumn column = new TableColumn("Role") { { - setMinWidth(80); - setSortable(false); + setMinWidth(130); } }; column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellFactory( new Callback, TableCell>() { - @Override public TableCell call(TableColumn column) { return new TableCell() { - final Button button = new Button("Open contract"); - - { - - } - @Override public void updateItem(final Dispute item, boolean empty) { super.updateItem(item, empty); - if (item != null && !empty) { - button.setOnAction(e -> onOpenContract(item)); - setGraphic(button); + if (item.isDisputeOpenerIsOfferer()) + setText(item.isDisputeOpenerIsBuyer() ? "BTC buyer/Offerer" : "BTC seller/Offerer"); + else + setText(item.isDisputeOpenerIsBuyer() ? "BTC buyer/Taker" : "BTC seller/Taker"); } else { - setGraphic(null); - button.setOnAction(null); + setText(""); } } }; diff --git a/gui/src/main/java/io/bitsquare/gui/main/market/offerbook/OfferBookChartView.java b/gui/src/main/java/io/bitsquare/gui/main/market/offerbook/OfferBookChartView.java index f46ffccb462..0075f7d62a8 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/market/offerbook/OfferBookChartView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/market/offerbook/OfferBookChartView.java @@ -174,11 +174,11 @@ protected void activate() { public String toString(Number object) { final double doubleValue = (double) object; if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) { - final String withPrecision4 = formatter.formatRoundedDoubleWithPrecision(doubleValue, 4); - if (withPrecision4.equals("0.0000")) + final String withPrecision3 = formatter.formatRoundedDoubleWithPrecision(doubleValue, 3); + if (withPrecision3.equals("0.000")) return formatter.formatRoundedDoubleWithPrecision(doubleValue, 8); else - return withPrecision4; + return withPrecision3; } else { return formatter.formatRoundedDoubleWithPrecision(doubleValue, 2); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBook.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBook.java index 026ccf126cf..c6006380d62 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBook.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBook.java @@ -91,8 +91,7 @@ public ObservableList getOfferBookListItems() { public void fillOfferBookListItems() { log.debug("fillOfferBookListItems"); - offerBookListItems.clear(); - offerBookListItems.addAll(offerBookService.getOffers().stream() + offerBookListItems.setAll(offerBookService.getOffers().stream() .map(OfferBookListItem::new) .collect(Collectors.toList())); diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java index 20ce5d953db..dd57f786f2d 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookView.java @@ -344,10 +344,20 @@ private void onCreateOffer() { "You need to setup a national currency or altcoin account before you can create an offer.\n" + "Do you want to setup an account?", FiatAccountsView.class, "\"Account\""); } else if (!model.hasPaymentAccountForCurrency()) { - openPopupForMissingAccountSetup("No matching payment account", - "You don't have a payment account for the currency required for that offer.\n" + - "You need to setup a payment account for that currency to be able to take this offer.\n" + - "Do you want to do this now?", FiatAccountsView.class, "\"Account\""); + new Popup().headLine("No payment account for selected currency") + .instruction("You don't have a payment account for the selected currency.\n" + + "Do you want to create an offer with one of your existing payment accounts?") + .actionButtonText("Yes, create offer") + .onAction(() -> { + createOfferButton.setDisable(true); + offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency()); + }) + .closeButtonText("Set up a new payment account") + .onClose(() -> { + navigation.setReturnPath(navigation.getCurrentPath()); + navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, FiatAccountsView.class); + }) + .show(); } else if (!model.hasAcceptedArbitrators()) { openPopupForMissingAccountSetup("You don't have an arbitrator selected.", "You need to setup at least one arbitrator to be able to trade.\n" + diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java index 18bffef4727..37b73736b64 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/offerbook/OfferBookViewModel.java @@ -433,7 +433,7 @@ private void applyFilterPredicate() { boolean currencyResult; final String currencyCode = offer.getCurrencyCode(); if (showAllTradeCurrenciesProperty.get()) { - currencyResult = tradeCurrencyCodes.contains(currencyCode); + currencyResult = true; } else currencyResult = currencyCode.equals(selectedTradeCurrency.getCode()); diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/ContractWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/ContractWindow.java index 5df5fb2de3f..0b10a74c4c3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/ContractWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/ContractWindow.java @@ -19,6 +19,7 @@ import com.google.common.base.Joiner; import io.bitsquare.arbitration.Dispute; +import io.bitsquare.arbitration.DisputeManager; import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.overlays.Overlay; import io.bitsquare.gui.util.BSFormatter; @@ -52,6 +53,7 @@ public class ContractWindow extends Overlay { protected static final Logger log = LoggerFactory.getLogger(ContractWindow.class); + private DisputeManager disputeManager; private final BSFormatter formatter; private Dispute dispute; @@ -61,7 +63,8 @@ public class ContractWindow extends Overlay { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - public ContractWindow(BSFormatter formatter) { + public ContractWindow(DisputeManager disputeManager, BSFormatter formatter) { + this.disputeManager = disputeManager; this.formatter = formatter; type = Type.Confirmation; } @@ -70,7 +73,7 @@ public void show(Dispute dispute) { this.dispute = dispute; rowIndex = -1; - width = 850; + width = 1100; createGridPane(); addContent(); display(); @@ -100,7 +103,7 @@ private void addContent() { List acceptedCountryCodes = offer.getAcceptedCountryCodes(); boolean showAcceptedCountryCodes = acceptedCountryCodes != null && !acceptedCountryCodes.isEmpty(); - int rows = 18; + int rows = 16; if (dispute.getDepositTxSerialized() != null) rows++; if (dispute.getPayoutTxSerialized() != null) @@ -111,31 +114,29 @@ private void addContent() { rows++; PaymentAccountContractData sellerPaymentAccountContractData = contract.getSellerPaymentAccountContractData(); - addTitledGroupBg(gridPane, ++rowIndex, rows, "Contract"); + addTitledGroupBg(gridPane, ++rowIndex, rows, "Dispute details"); addLabelTextFieldWithCopyIcon(gridPane, rowIndex, "Offer ID:", offer.getId(), Layout.FIRST_ROW_DISTANCE).second.setMouseTransparent(false); - addLabelTextField(gridPane, ++rowIndex, "Offer date:", formatter.formatDateTime(offer.getDate())); - addLabelTextField(gridPane, ++rowIndex, "Trade date:", formatter.formatDateTime(dispute.getTradeDate())); + addLabelTextField(gridPane, ++rowIndex, "Offer date / Trade date:", formatter.formatDateTime(offer.getDate()) + " / " + formatter.formatDateTime(dispute.getTradeDate())); String currencyCode = offer.getCurrencyCode(); addLabelTextField(gridPane, ++rowIndex, "Trade type:", formatter.getDirectionBothSides(offer.getDirection(), currencyCode)); addLabelTextField(gridPane, ++rowIndex, "Trade price:", formatter.formatPrice(contract.getTradePrice())); addLabelTextField(gridPane, ++rowIndex, "Trade amount:", formatter.formatCoinWithCode(contract.getTradeAmount())); addLabelTextField(gridPane, ++rowIndex, formatter.formatVolumeLabel(currencyCode, ":"), formatter.formatVolumeWithCode(new ExchangeRate(contract.getTradePrice()).coinToFiat(contract.getTradeAmount()))); - addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "BTC buyer bitcoin address:", - contract.getBuyerPayoutAddressString()).second.setMouseTransparent(false); - addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "BTC seller bitcoin address:", - contract.getSellerPayoutAddressString()).second.setMouseTransparent(false); - addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Contract hash:", - Utils.HEX.encode(dispute.getContractHash())).second.setMouseTransparent(false); - addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "BTC buyer address:", contract.getBuyerNodeAddress().getFullAddress()); - addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "BTC seller address:", contract.getSellerNodeAddress().getFullAddress()); - addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Selected arbitrator:", contract.arbitratorNodeAddress.getFullAddress()); + addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Bitcoin address BTC buyer / BTC seller:", + contract.getBuyerPayoutAddressString() + " / " + contract.getSellerPayoutAddressString()).second.setMouseTransparent(false); + addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Network address BTC buyer / BTC seller:", contract.getBuyerNodeAddress().getFullAddress() + " / " + contract.getSellerNodeAddress().getFullAddress()); + + addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Nr. of disputes BTC buyer / BTC seller:", disputeManager.getNrOfDisputes(true, contract) + " / " + disputeManager.getNrOfDisputes(false, contract)); + addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "BTC buyer payment details:", BSResources.get(contract.getBuyerPaymentAccountContractData().getPaymentDetails())).second.setMouseTransparent(false); addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "BTC seller payment details:", BSResources.get(sellerPaymentAccountContractData.getPaymentDetails())).second.setMouseTransparent(false); + addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Selected arbitrator:", contract.arbitratorNodeAddress.getFullAddress()); + if (showAcceptedCountryCodes) { String countries; Tooltip tooltip = null; @@ -168,6 +169,9 @@ private void addContent() { if (dispute.getPayoutTxSerialized() != null) addLabelTxIdTextField(gridPane, ++rowIndex, "Payout transaction ID:", dispute.getPayoutTxId()); + addLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, "Contract hash:", + Utils.HEX.encode(dispute.getContractHash())).second.setMouseTransparent(false); + if (contract != null) { Button viewContractButton = addLabelButton(gridPane, ++rowIndex, "Contract in JSON format:", "View contract in JSON format", 0).second; viewContractButton.setDefaultButton(false); diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java index 863eae92025..4143031146b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/DisputeSummaryWindow.java @@ -70,7 +70,7 @@ public class DisputeSummaryWindow extends Overlay { private ToggleGroup tradeAmountToggleGroup; private DisputeResult disputeResult; private RadioButton buyerIsWinnerRadioButton, sellerIsWinnerRadioButton, shareRadioButton, loserPaysFeeRadioButton, splitFeeRadioButton, - waiveFeeRadioButton, reasonWasBugRadioButton, reasonWasUsabilityIssueRadioButton, reasonWasScamRadioButton, reasonWasOtherRadioButton; + waiveFeeRadioButton, reasonWasBugRadioButton, reasonWasUsabilityIssueRadioButton, reasonProtocolViolationRadioButton, reasonNoReplyRadioButton, reasonWasScamRadioButton, reasonWasOtherRadioButton; private Optional peersDisputeOptional; private Coin arbitratorPayoutAmount, winnerPayoutAmount, loserPayoutAmount, stalematePayoutAmount; private ToggleGroup feeToggleGroup, reasonToggleGroup; @@ -201,6 +201,8 @@ private void addContent() { reasonWasBugRadioButton.setDisable(true); reasonWasUsabilityIssueRadioButton.setDisable(true); + reasonProtocolViolationRadioButton.setDisable(true); + reasonNoReplyRadioButton.setDisable(true); reasonWasScamRadioButton.setDisable(true); reasonWasOtherRadioButton.setDisable(true); @@ -209,7 +211,7 @@ private void addContent() { } else { applyPayoutAmounts(disputeResult.disputeFeePolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get()); feePaymentPolicyChanged = Bindings.createObjectBinding( - () -> new Tuple2(disputeResult.disputeFeePolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get()), + () -> new Tuple2<>(disputeResult.disputeFeePolicyProperty().get(), tradeAmountToggleGroup.selectedToggleProperty().get()), disputeResult.disputeFeePolicyProperty(), tradeAmountToggleGroup.selectedToggleProperty()); feePaymentPolicyListener = (observable, oldValue, newValue) -> { @@ -358,12 +360,15 @@ private void addReasonControls() { reasonWasBugRadioButton = new RadioButton("Bug"); reasonWasUsabilityIssueRadioButton = new RadioButton("Usability"); + reasonProtocolViolationRadioButton = new RadioButton("Protocol violation"); + reasonNoReplyRadioButton = new RadioButton("No reply"); reasonWasScamRadioButton = new RadioButton("Scam"); reasonWasOtherRadioButton = new RadioButton("Other"); HBox feeRadioButtonPane = new HBox(); feeRadioButtonPane.setSpacing(20); feeRadioButtonPane.getChildren().addAll(reasonWasBugRadioButton, reasonWasUsabilityIssueRadioButton, + reasonProtocolViolationRadioButton, reasonNoReplyRadioButton, reasonWasScamRadioButton, reasonWasOtherRadioButton); GridPane.setRowIndex(feeRadioButtonPane, rowIndex); GridPane.setColumnIndex(feeRadioButtonPane, 1); @@ -373,6 +378,8 @@ private void addReasonControls() { reasonToggleGroup = new ToggleGroup(); reasonWasBugRadioButton.setToggleGroup(reasonToggleGroup); reasonWasUsabilityIssueRadioButton.setToggleGroup(reasonToggleGroup); + reasonProtocolViolationRadioButton.setToggleGroup(reasonToggleGroup); + reasonNoReplyRadioButton.setToggleGroup(reasonToggleGroup); reasonWasScamRadioButton.setToggleGroup(reasonToggleGroup); reasonWasOtherRadioButton.setToggleGroup(reasonToggleGroup); @@ -381,6 +388,10 @@ private void addReasonControls() { disputeResult.setReason(DisputeResult.Reason.BUG); else if (newValue == reasonWasUsabilityIssueRadioButton) disputeResult.setReason(DisputeResult.Reason.USABILITY); + else if (newValue == reasonProtocolViolationRadioButton) + disputeResult.setReason(DisputeResult.Reason.PROTOCOL_VIOLATION); + else if (newValue == reasonNoReplyRadioButton) + disputeResult.setReason(DisputeResult.Reason.NO_REPLY); else if (newValue == reasonWasScamRadioButton) disputeResult.setReason(DisputeResult.Reason.SCAM); else if (newValue == reasonWasOtherRadioButton) @@ -398,6 +409,12 @@ private void setReasonRadioButtonState() { case USABILITY: reasonToggleGroup.selectToggle(reasonWasUsabilityIssueRadioButton); break; + case PROTOCOL_VIOLATION: + reasonToggleGroup.selectToggle(reasonProtocolViolationRadioButton); + break; + case NO_REPLY: + reasonToggleGroup.selectToggle(reasonNoReplyRadioButton); + break; case SCAM: reasonToggleGroup.selectToggle(reasonWasScamRadioButton); break; diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index a4171699ecf..3ccf7709f81 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -172,6 +172,9 @@ protected void addContent() { case PaymentMethod.SEPA_ID: gridRow = SepaForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData); break; + case PaymentMethod.FASTER_PAYMENTS_ID: + gridRow = FasterPaymentsForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData); + break; case PaymentMethod.NATIONAL_BANK_ID: gridRow = NationalBankForm.addFormForBuyer(gridPane, gridRow, paymentAccountContractData); break; diff --git a/gui/src/main/resources/i18n/displayStrings.properties b/gui/src/main/resources/i18n/displayStrings.properties index ee8b5a31d6b..fb3848af4e6 100644 --- a/gui/src/main/resources/i18n/displayStrings.properties +++ b/gui/src/main/resources/i18n/displayStrings.properties @@ -150,6 +150,7 @@ OK_PAY=OKPay PERFECT_MONEY=Perfect Money ALI_PAY=AliPay SEPA=SEPA +FASTER_PAYMENTS=Faster Payments NATIONAL_BANK=National Bank transfer SAME_BANK=Transfer with same Bank SPECIFIC_BANKS=Transfers with specific banks @@ -165,6 +166,7 @@ OK_PAY_SHORT=OKPay PERFECT_MONEY_SHORT=Perfect Money ALI_PAY_SHORT=AliPay SEPA_SHORT=SEPA +FASTER_PAYMENTS_SHORT=Faster Payments NATIONAL_BANK_SHORT=National Banks SAME_BANK_SHORT=Same Bank SPECIFIC_BANKS_SHORT=Specific banks diff --git a/headless/pom.xml b/headless/pom.xml index 991dca486cc..4ccc9fd52f3 100644 --- a/headless/pom.xml +++ b/headless/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/jdkfix/pom.xml b/jdkfix/pom.xml index 600d3fcd615..bd1c2571243 100644 --- a/jdkfix/pom.xml +++ b/jdkfix/pom.xml @@ -22,7 +22,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/jsocks/pom.xml b/jsocks/pom.xml index b72468037fc..0b7564ccde8 100644 --- a/jsocks/pom.xml +++ b/jsocks/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/jtorctl/pom.xml b/jtorctl/pom.xml index 97a8c094c90..22c7450cdc3 100644 --- a/jtorctl/pom.xml +++ b/jtorctl/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/jtorproxy/pom.xml b/jtorproxy/pom.xml index 899b770b9eb..a2b699a7c61 100644 --- a/jtorproxy/pom.xml +++ b/jtorproxy/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/monitor/pom.xml b/monitor/pom.xml index 2d8f360757c..2c3d931e6a3 100644 --- a/monitor/pom.xml +++ b/monitor/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/network/pom.xml b/network/pom.xml index 20f282b3d9e..018dfe5b929 100644 --- a/network/pom.xml +++ b/network/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/network/src/main/java/io/bitsquare/p2p/peers/BroadcastHandler.java b/network/src/main/java/io/bitsquare/p2p/peers/BroadcastHandler.java index cdbe10a44ee..7897f70fa08 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/BroadcastHandler.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/BroadcastHandler.java @@ -109,8 +109,8 @@ public void broadcast(BroadcastMessage message, @Nullable NodeAddress sender, Re numOfPeers = connectedPeersList.size(); int delay = 50; if (!isDataOwner) { - // for not data owner (relay nodes) we send to max. 5 nodes and use a longer delay - numOfPeers = Math.min(5, connectedPeersList.size()); + // for not data owner (relay nodes) we send to max. 7 nodes and use a longer delay + numOfPeers = Math.min(7, connectedPeersList.size()); delay = 100; } diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java index ff04ff35348..1cd48dd3e7f 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataHandler.java @@ -37,6 +37,7 @@ public class RequestDataHandler implements MessageListener { private static final Logger log = LoggerFactory.getLogger(RequestDataHandler.class); private static final long TIME_OUT_SEC = 20; + private NodeAddress peersNodeAddress; /////////////////////////////////////////////////////////////////////////////////////////// @@ -85,8 +86,9 @@ public void cancel() { // API /////////////////////////////////////////////////////////////////////////////////////////// - public void requestData(NodeAddress nodeAddress) { + public void requestData(NodeAddress nodeAddress, boolean isPreliminaryDataRequest) { Log.traceCall("nodeAddress=" + nodeAddress); + peersNodeAddress = nodeAddress; if (!stopped) { GetDataRequest getDataRequest; @@ -99,7 +101,7 @@ public void requestData(NodeAddress nodeAddress) { .map(e -> e.getKey().bytes) .collect(Collectors.toSet()); - if (networkNode.getNodeAddress() == null) + if (isPreliminaryDataRequest) getDataRequest = new PreliminaryGetDataRequest(nonce, excludedKeys); else getDataRequest = new GetUpdatedDataRequest(networkNode.getNodeAddress(), nonce, excludedKeys); @@ -120,13 +122,13 @@ public void requestData(NodeAddress nodeAddress) { } log.debug("We send a {} to peer {}. ", getDataRequest.getClass().getSimpleName(), nodeAddress); + networkNode.addMessageListener(this); SettableFuture future = networkNode.sendMessage(nodeAddress, getDataRequest); Futures.addCallback(future, new FutureCallback() { @Override public void onSuccess(Connection connection) { if (!stopped) { RequestDataHandler.this.connection = connection; - connection.addMessageListener(RequestDataHandler.this); log.trace("Send " + getDataRequest + " to " + nodeAddress + " succeeded."); } else { log.trace("We have stopped already. We ignore that networkNode.sendMessage.onSuccess call." + @@ -161,84 +163,87 @@ public void onFailure(@NotNull Throwable throwable) { @Override public void onMessage(Message message, Connection connection) { - if (message instanceof GetDataResponse) { - Log.traceCall(message.toString() + "\n\tconnection=" + connection); - if (!stopped) { - GetDataResponse getDataResponse = (GetDataResponse) message; - Map> payloadByClassName = new HashMap<>(); - final HashSet dataSet = getDataResponse.dataSet; - dataSet.stream().forEach(e -> { - final StoragePayload storagePayload = e.getStoragePayload(); - String className = storagePayload.getClass().getSimpleName(); - if (!payloadByClassName.containsKey(className)) - payloadByClassName.put(className, new HashSet<>()); - - payloadByClassName.get(className).add(storagePayload); - }); - StringBuilder sb = new StringBuilder("Received data size: ").append(dataSet.size()).append(", data items: "); - payloadByClassName.entrySet().stream().forEach(e -> sb.append(e.getValue().size()).append(" items of ").append(e.getKey()).append("; ")); - log.info(sb.toString()); - - if (getDataResponse.requestNonce == nonce) { - stopTimeoutTimer(); - checkArgument(connection.getPeersNodeAddressOptional().isPresent(), - "RequestDataHandler.onMessage: connection.getPeersNodeAddressOptional() must be present " + - "at that moment"); - - final NodeAddress sender = connection.getPeersNodeAddressOptional().get(); - - List processDelayedItems = new ArrayList<>(); + if (connection.getPeersNodeAddressOptional().isPresent() && connection.getPeersNodeAddressOptional().get().equals(peersNodeAddress)) { + if (message instanceof GetDataResponse) { + Log.traceCall(message.toString() + "\n\tconnection=" + connection); + if (!stopped) { + GetDataResponse getDataResponse = (GetDataResponse) message; + Map> payloadByClassName = new HashMap<>(); + final HashSet dataSet = getDataResponse.dataSet; dataSet.stream().forEach(e -> { - if (e.getStoragePayload() instanceof LazyProcessedStoragePayload) - processDelayedItems.add(e); - else { - // We dont broadcast here (last param) as we are only connected to the seed node and would be pointless - dataStorage.add(e, sender, null, false, false); - } + final StoragePayload storagePayload = e.getStoragePayload(); + String className = storagePayload.getClass().getSimpleName(); + if (!payloadByClassName.containsKey(className)) + payloadByClassName.put(className, new HashSet<>()); + + payloadByClassName.get(className).add(storagePayload); }); + StringBuilder sb = new StringBuilder("Received data size: ").append(dataSet.size()).append(", data items: "); + payloadByClassName.entrySet().stream().forEach(e -> sb.append(e.getValue().size()).append(" items of ").append(e.getKey()).append("; ")); + log.info(sb.toString()); + + if (getDataResponse.requestNonce == nonce) { + stopTimeoutTimer(); + checkArgument(connection.getPeersNodeAddressOptional().isPresent(), + "RequestDataHandler.onMessage: connection.getPeersNodeAddressOptional() must be present " + + "at that moment"); + + final NodeAddress sender = connection.getPeersNodeAddressOptional().get(); + + List processDelayedItems = new ArrayList<>(); + dataSet.stream().forEach(e -> { + if (e.getStoragePayload() instanceof LazyProcessedStoragePayload) + processDelayedItems.add(e); + else { + // We dont broadcast here (last param) as we are only connected to the seed node and would be pointless + dataStorage.add(e, sender, null, false, false); + } + }); + + // We process the LazyProcessedStoragePayload items (TradeStatistics) in batches with a delay in between. + // We want avoid that the UI get stuck when processing many entries. + // The dataStorage.add call is a bit expensive as sig checks is done there. + + // Using a background thread might be an alternative but it would require much more effort and + // it would also decrease user experience if the app gets under heavy load (like at startup with wallet sync). + // Beside that we mitigated the problem already as we will not get the whole TradeStatistics as we + // pass the excludeKeys and we pack the latest data dump + // into the resources, so a new user do not need to request all data. + + // In future we will probably limit by date or load on demand from user intent to not get too much data. + + // We split the list into sub lists with max 50 items and delay each batch with 200 ms. + int size = processDelayedItems.size(); + int chunkSize = 50; + int chunks = 1 + size / chunkSize; + int startIndex = 0; + for (int i = 0; i < chunks && startIndex < size; i++, startIndex += chunkSize) { + long delay = (i + 1) * 200; + int endIndex = Math.min(size, startIndex + chunkSize); + List subList = processDelayedItems.subList(startIndex, endIndex); + UserThread.runAfter(() -> { + subList.stream().forEach(protectedStorageEntry -> dataStorage.add(protectedStorageEntry, sender, null, false, false)); + }, delay, TimeUnit.MILLISECONDS); + } - // We process the LazyProcessedStoragePayload items (TradeStatistics) in batches with a delay in between. - // We want avoid that the UI get stuck when processing many entries. - // The dataStorage.add call is a bit expensive as sig checks is done there. - - // Using a background thread might be an alternative but it would require much more effort and - // it would also decrease user experience if the app gets under heavy load (like at startup with wallet sync). - // Beside that we mitigated the problem already as we will not get the whole TradeStatistics as we - // pass the excludeKeys and we pack the latest data dump - // into the resources, so a new user do not need to request all data. - - // In future we will probably limit by date or load on demand from user intent to not get too much data. - - // We split the list into sub lists with max 50 items and delay each batch with 200 ms. - int size = processDelayedItems.size(); - int chunkSize = 50; - int chunks = 1 + size / chunkSize; - int startIndex = 0; - for (int i = 0; i < chunks && startIndex < size; i++, startIndex += chunkSize) { - long delay = (i + 1) * 200; - int endIndex = Math.min(size, startIndex + chunkSize); - List subList = processDelayedItems.subList(startIndex, endIndex); - UserThread.runAfter(() -> { - subList.stream().forEach(protectedStorageEntry -> dataStorage.add(protectedStorageEntry, sender, null, false, false)); - }, delay, TimeUnit.MILLISECONDS); + cleanup(); + listener.onComplete(); + } else { + log.debug("Nonce not matching. That can happen rarely if we get a response after a canceled " + + "handshake (timeout causes connection close but peer might have sent a msg before " + + "connection was closed).\n\t" + + "We drop that message. nonce={} / requestNonce={}", + nonce, getDataResponse.requestNonce); } - - cleanup(); - listener.onComplete(); } else { - log.debug("Nonce not matching. That can happen rarely if we get a response after a canceled " + - "handshake (timeout causes connection close but peer might have sent a msg before " + - "connection was closed).\n\t" + - "We drop that message. nonce={} / requestNonce={}", - nonce, getDataResponse.requestNonce); + log.warn("We have stopped already. We ignore that onDataRequest call."); } - } else { - log.warn("We have stopped already. We ignore that onDataRequest call."); } + } else { + log.trace("We got a message from another connection and ignore it."); } } - public void stop() { cleanup(); } @@ -258,8 +263,7 @@ private void handleFault(String errorMessage, NodeAddress nodeAddress, CloseConn private void cleanup() { Log.traceCall(); stopped = true; - if (connection != null) - connection.removeMessageListener(this); + networkNode.removeMessageListener(this); stopTimeoutTimer(); } diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataManager.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataManager.java index dc9ac896913..af7cc99d5e0 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataManager.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/RequestDataManager.java @@ -24,6 +24,7 @@ public class RequestDataManager implements MessageListener, ConnectionListener, private static final long RETRY_DELAY_SEC = 10; private static final long CLEANUP_TIMER = 120; + private boolean isPreliminaryDataRequest = true; /////////////////////////////////////////////////////////////////////////////////////////// @@ -101,6 +102,7 @@ public void requestPreliminaryData() { Collections.shuffle(nodeAddresses); NodeAddress nextCandidate = nodeAddresses.get(0); nodeAddresses.remove(nextCandidate); + isPreliminaryDataRequest = true; requestData(nextCandidate, nodeAddresses); } } @@ -114,6 +116,7 @@ public void requestUpdateData() { Collections.shuffle(remainingNodeAddresses); NodeAddress candidate = nodeAddressOfPreliminaryDataRequest.get(); remainingNodeAddresses.remove(candidate); + isPreliminaryDataRequest = false; requestData(candidate, remainingNodeAddresses); } } @@ -301,7 +304,7 @@ public void onFault(String errorMessage, @Nullable Connection connection) { } }); handlerMap.put(nodeAddress, requestDataHandler); - requestDataHandler.requestData(nodeAddress); + requestDataHandler.requestData(nodeAddress, isPreliminaryDataRequest); } else { log.warn("We have started already a requestDataHandshake to peer. nodeAddress=" + nodeAddress + "\n" + "We start a cleanup timer if the handler has not closed by itself in between 2 minutes."); diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataResponse.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataResponse.java index dc06eac92d4..2e537656def 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataResponse.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetDataResponse.java @@ -41,11 +41,11 @@ public int getMessageVersion() { @Override public String toString() { return "GetDataResponse{" + - "messageVersion=" + messageVersion + - ", dataSet.size()=" + dataSet.size() + + "dataSet.size()=" + dataSet.size() + + ", isGetUpdatedDataResponse=" + isGetUpdatedDataResponse + ", requestNonce=" + requestNonce + ", supportedCapabilities=" + supportedCapabilities + - ", isGetUpdatedDataResponse=" + isGetUpdatedDataResponse + + ", messageVersion=" + messageVersion + '}'; } } diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java index 1956d71864c..94b598a3f41 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/GetUpdatedDataRequest.java @@ -47,9 +47,9 @@ public int getMessageVersion() { @Override public String toString() { return "GetUpdatedDataRequest{" + - "messageVersion=" + messageVersion + - ", senderNodeAddress=" + senderNodeAddress + + "senderNodeAddress=" + senderNodeAddress + ", nonce=" + nonce + + ", messageVersion=" + messageVersion + '}'; } } diff --git a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java index 99e9c77a5c3..b68d74b7c3d 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/getdata/messages/PreliminaryGetDataRequest.java @@ -48,9 +48,9 @@ public int getMessageVersion() { @Override public String toString() { return "PreliminaryGetDataRequest{" + - "messageVersion=" + messageVersion + - ", nonce=" + nonce + + "nonce=" + nonce + ", supportedCapabilities=" + supportedCapabilities + + ", messageVersion=" + messageVersion + '}'; } } diff --git a/network/src/main/java/io/bitsquare/p2p/peers/keepalive/KeepAliveManager.java b/network/src/main/java/io/bitsquare/p2p/peers/keepalive/KeepAliveManager.java index 7007eef9089..ce37cff830b 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/keepalive/KeepAliveManager.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/keepalive/KeepAliveManager.java @@ -22,7 +22,7 @@ public class KeepAliveManager implements MessageListener, ConnectionListener, PeerManager.Listener { private static final Logger log = LoggerFactory.getLogger(KeepAliveManager.class); - private static final int INTERVAL_SEC = new Random().nextInt(5) + 40; + private static final int INTERVAL_SEC = new Random().nextInt(5) + 30; private static final long LAST_ACTIVITY_AGE_MS = INTERVAL_SEC / 2; private final NetworkNode networkNode; diff --git a/network/src/main/resources/PersistedP2PStorageData b/network/src/main/resources/PersistedP2PStorageData index 0716efebb4e..2f35042ef7c 100644 Binary files a/network/src/main/resources/PersistedP2PStorageData and b/network/src/main/resources/PersistedP2PStorageData differ diff --git a/package/linux/32bitBuild.sh b/package/linux/32bitBuild.sh index 513be64ac8f..ace62b50e93 100644 --- a/package/linux/32bitBuild.sh +++ b/package/linux/32bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p gui/deploy set -e # Edit version -version=0.4.9.5 +version=0.4.9.6 jarFile="/media/sf_vm_shared_ubuntu14_32bit/Bitsquare-$version.jar" jdkfixFile="/media/sf_vm_shared_ubuntu14_32bit/jdkfix-$version.jar" diff --git a/package/linux/64bitBuild.sh b/package/linux/64bitBuild.sh index a4be033a697..46073465a7b 100644 --- a/package/linux/64bitBuild.sh +++ b/package/linux/64bitBuild.sh @@ -6,7 +6,7 @@ mkdir -p gui/deploy set -e # Edit version -version=0.4.9.5 +version=0.4.9.6 jarFile="/media/sf_vm_shared_ubuntu/Bitsquare-$version.jar" jdkfixFile="/media/sf_vm_shared_ubuntu/jdkfix-$version.jar" diff --git a/package/mac/create_app.sh b/package/mac/create_app.sh index e66cd85ac40..0047c44e4f4 100755 --- a/package/mac/create_app.sh +++ b/package/mac/create_app.sh @@ -5,7 +5,7 @@ mkdir -p gui/deploy set -e -version="0.4.9.5" +version="0.4.9.6" mvn clean package -DskipTests -Dmaven.javadoc.skip=true diff --git a/package/mac/finalize.sh b/package/mac/finalize.sh index 2b3edc20cc1..33addbf73f3 100644 --- a/package/mac/finalize.sh +++ b/package/mac/finalize.sh @@ -1,6 +1,6 @@ #!/bin/bash -version="0.4.9.5" +version="0.4.9.6" target_dir="/Users/mk/Documents/__bitsquare/_releases/$version" src_dir="/Users/mk/Documents/_intellij/bitsquare" diff --git a/package/windows/32bitBuild.bat b/package/windows/32bitBuild.bat index 6bd3136ab32..4a2d00f5db1 100644 --- a/package/windows/32bitBuild.bat +++ b/package/windows/32bitBuild.bat @@ -6,7 +6,7 @@ :: 64 bit build :: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php) -SET version=0.4.9.5 +SET version=0.4.9.6 SET jdk=C:\Program Files\Java\jdk1.8.0_92 SET outdir=\\VBOXSVR\vm_shared_windows_32bit diff --git a/package/windows/64bitBuild.bat b/package/windows/64bitBuild.bat index 663434b5d22..22c5ab1c614 100644 --- a/package/windows/64bitBuild.bat +++ b/package/windows/64bitBuild.bat @@ -8,7 +8,7 @@ :: Did not get -BjvmOptions=-Xbootclasspath working on windows, but if the jdkfix jar is copied into the jdk/jre dir it will override the default classes -SET version=0.4.9.5 +SET version=0.4.9.6 SET jdk=C:\Program Files\Java\jdk1.8.0_92 SET outdir=\\VBOXSVR\vm_shared_windows diff --git a/package/windows/Bitsquare.iss b/package/windows/Bitsquare.iss index 96da37f32fc..a682222a52f 100755 --- a/package/windows/Bitsquare.iss +++ b/package/windows/Bitsquare.iss @@ -3,7 +3,7 @@ [Setup] AppId={{bitsquare}} AppName=Bitsquare -AppVersion=0.4.9.5 +AppVersion=0.4.9.6 AppVerName=Bitsquare AppPublisher=Bitsquare AppComments=Bitsquare diff --git a/pom.xml b/pom.xml index 1f99d1e3aa2..c13608b8e60 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.bitsquare parent pom - 0.4.9.5 + 0.4.9.6 Bitsquare - The decentralized bitcoin exchange https://bitsquare.io diff --git a/seednode/pom.xml b/seednode/pom.xml index dfd1642fc35..911001aaeb4 100644 --- a/seednode/pom.xml +++ b/seednode/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0 diff --git a/statistics/pom.xml b/statistics/pom.xml index 0f7265d23f2..f701e36c7be 100644 --- a/statistics/pom.xml +++ b/statistics/pom.xml @@ -5,7 +5,7 @@ parent io.bitsquare - 0.4.9.5 + 0.4.9.6 4.0.0