From 43e4809d81929143c5d5b1dc53aed911d681605f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 26 Jul 2020 18:55:07 -0500 Subject: [PATCH 01/85] Add basic support for validation for XMR transfer with tx key Main part missing is the XMR proof service request processing. I did not get the service compiled yet, so could not test response data and error conditions. Further it is missing a "news badge" and popup to guide the user to the new feature. Only basic dev tested so far. Anyone welcome to pick the project up from here as I might not have time soon to continue. --- .../witness/AccountAgeWitnessService.java | 59 +++++++ core/src/main/java/bisq/core/trade/Trade.java | 10 ++ .../java/bisq/core/trade/TradeManager.java | 143 +++++++++++++++- .../core/trade/asset/xmr/XmrProofResult.java | 31 ++++ .../asset/xmr/XmrProofResultWithTradeId.java | 31 ++++ .../asset/xmr/XmrTransferProofRequester.java | 158 ++++++++++++++++++ .../asset/xmr/XmrTransferProofService.java | 78 +++++++++ .../trade/asset/xmr/XmrTxProofHttpClient.java | 32 ++++ ...CounterCurrencyTransferStartedMessage.java | 18 +- ...CounterCurrencyTransferStartedMessage.java | 1 + ...CounterCurrencyTransferStartedMessage.java | 12 +- .../main/java/bisq/core/user/Preferences.java | 9 +- .../bisq/core/user/PreferencesPayload.java | 9 +- .../resources/i18n/displayStrings.properties | 8 + .../overlays/windows/SetXmrTxKeyWindow.java | 101 +++++++++++ .../pendingtrades/PendingTradesDataModel.java | 10 +- .../pendingtrades/PendingTradesViewModel.java | 64 ------- .../steps/buyer/BuyerStep2View.java | 149 ++++++++++------- .../steps/seller/SellerStep3View.java | 76 ++++++++- .../settings/preferences/PreferencesView.java | 58 +------ proto/src/main/proto/pb.proto | 3 + 21 files changed, 869 insertions(+), 191 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java create mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java create mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java create mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java create mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java create mode 100644 desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index f4620c7d0e2..884fbf266d5 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -34,12 +34,16 @@ import bisq.core.support.dispute.Dispute; import bisq.core.support.dispute.DisputeResult; import bisq.core.support.dispute.arbitration.TraderDataItem; +import bisq.core.trade.Contract; import bisq.core.trade.Trade; +import bisq.core.trade.messages.TraderSignedWitnessMessage; import bisq.core.trade.protocol.TradingPeer; import bisq.core.user.User; import bisq.network.p2p.BootstrapListener; +import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; +import bisq.network.p2p.SendMailboxMessageListener; import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; @@ -73,6 +77,7 @@ import java.util.Optional; import java.util.Random; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -852,4 +857,58 @@ public void signSameNameAccounts() { public Set getUnsignedSignerPubKeys() { return signedWitnessService.getUnsignedSignerPubKeys(); } + + public boolean isSignWitnessTrade(Trade trade) { + checkNotNull(trade, "trade must not be null"); + checkNotNull(trade.getOffer(), "offer must not be null"); + Contract contract = checkNotNull(trade.getContract()); + PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); + AccountAgeWitness myWitness = getMyWitness(sellerPaymentAccountPayload); + + getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness); + + return accountIsSigner(myWitness) && + !peerHasSignedWitness(trade) && + tradeAmountIsSufficient(trade.getTradeAmount()); + } + + public void maybeSignWitness(Trade trade) { + if (isSignWitnessTrade(trade)) { + var signedWitnessOptional = traderSignPeersAccountAgeWitness(trade); + signedWitnessOptional.ifPresent(signedWitness -> sendSignedWitnessToPeer(signedWitness, trade)); + } + } + + private void sendSignedWitnessToPeer(SignedWitness signedWitness, Trade trade) { + if (trade == null) return; + + NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress(); + var traderSignedWitnessMessage = new TraderSignedWitnessMessage(UUID.randomUUID().toString(), trade.getId(), + tradingPeerNodeAddress, signedWitness); + + p2PService.sendEncryptedMailboxMessage( + tradingPeerNodeAddress, + trade.getProcessModel().getTradingPeer().getPubKeyRing(), + traderSignedWitnessMessage, + new SendMailboxMessageListener() { + @Override + public void onArrived() { + log.info("SendMailboxMessageListener onArrived tradeId={} at peer {} SignedWitness {}", + trade.getId(), tradingPeerNodeAddress, signedWitness); + } + + @Override + public void onStoredInMailbox() { + log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {} SignedWitness {}", + trade.getId(), tradingPeerNodeAddress, signedWitness); + } + + @Override + public void onFault(String errorMessage) { + log.error("SendMailboxMessageListener onFault tradeId={} at peer {} SignedWitness {}", + trade.getId(), tradingPeerNodeAddress, signedWitness); + } + } + ); + } } diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 6071e59d504..f4b2fc223c0 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -428,6 +428,13 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt private long refreshInterval; private static final long MAX_REFRESH_INTERVAL = 4 * ChronoUnit.HOURS.getDuration().toMillis(); + // Added in v1.3.7 + // We use that for the XMR txKey but want to keep it generic to be flexible for other payment methods or assets. + @Getter + @Setter + private String counterCurrencyExtraData; + + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization /////////////////////////////////////////////////////////////////////////////////////////// @@ -538,6 +545,8 @@ public Message toProtoMessage() { Optional.ofNullable(mediationResultState).ifPresent(e -> builder.setMediationResultState(MediationResultState.toProtoMessage(mediationResultState))); Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState))); Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes))); + Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); + return builder.build(); } @@ -570,6 +579,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setDelayedPayoutTxBytes(ProtoUtil.byteArrayOrNullFromProto(proto.getDelayedPayoutTxBytes())); trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); + trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 31bb507e44f..b0ea7bafec5 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -21,6 +21,7 @@ import bisq.core.btc.exceptions.AddressEntryException; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.model.AddressEntry; +import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; @@ -34,10 +35,15 @@ import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.offer.availability.OfferAvailabilityModel; +import bisq.core.payment.payload.AssetsAccountPayload; +import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; +import bisq.core.trade.asset.xmr.XmrProofResult; +import bisq.core.trade.asset.xmr.XmrProofResultWithTradeId; +import bisq.core.trade.asset.xmr.XmrTransferProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; @@ -46,6 +52,7 @@ import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.Validator; @@ -65,8 +72,6 @@ import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.storage.Storage; -import bisq.common.util.Tuple2; -import bisq.common.util.Utilities; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; @@ -81,8 +86,10 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty; +import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -93,7 +100,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -111,6 +117,8 @@ import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkNotNull; + public class TradeManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(TradeManager.class); @@ -147,8 +155,14 @@ public class TradeManager implements PersistedDataHost { @Getter private final ObservableList tradesWithoutDepositTx = FXCollections.observableArrayList(); private final DumpDelayedPayoutTx dumpDelayedPayoutTx; + private final XmrTransferProofService xmrTransferProofService; + private final WalletsSetup walletsSetup; + private final Preferences preferences; @Getter private final boolean allowFaultyDelayedTxs; + // This observable property can be used for UI to show a notification to user in case a XMR txKey was reused. + @Getter + private final ObjectProperty proofResultWithTradeIdProperty = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -177,6 +191,9 @@ public TradeManager(User user, ClockWatcher clockWatcher, Storage> storage, DumpDelayedPayoutTx dumpDelayedPayoutTx, + XmrTransferProofService xmrTransferProofService, + WalletsSetup walletsSetup, + Preferences preferences, @Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) { this.user = user; this.keyRing = keyRing; @@ -198,6 +215,9 @@ public TradeManager(User user, this.daoFacade = daoFacade; this.clockWatcher = clockWatcher; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; + this.xmrTransferProofService = xmrTransferProofService; + this.walletsSetup = walletsSetup; + this.preferences = preferences; this.allowFaultyDelayedTxs = allowFaultyDelayedTxs; tradableListStorage = storage; @@ -855,4 +875,121 @@ else if (now.after(halfTradePeriodDate)) public void persistTrades() { tradableList.persist(); } + + public void processCounterCurrencyExtraData(Trade trade) { + String counterCurrencyExtraData = trade.getCounterCurrencyExtraData(); + if (counterCurrencyExtraData == null || counterCurrencyExtraData.isEmpty()) { + return; + } + + String txHash = trade.getCounterCurrencyTxId(); + if (txHash == null || txHash.isEmpty()) { + return; + } + + Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); + PaymentAccountPayload sellersPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); + if (!(sellersPaymentAccountPayload instanceof AssetsAccountPayload)) { + return; + } + AssetsAccountPayload sellersAssetsAccountPayload = (AssetsAccountPayload) sellersPaymentAccountPayload; + + if (!(trade instanceof SellerTrade)) { + return; + } + + Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); + if (offer.getCurrencyCode().equals("XMR")) { + String txKey = counterCurrencyExtraData; + + // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with + // the same user (same address) and same amount. We check only for the txKey as a same txHash but different + // txKey is not possible to get a valid result at proof. + Stream failedAndOpenTrades = Stream.concat(tradableList.stream(), failedTradesManager.getFailedTrades().stream()); + Stream closedTrades = closedTradableManager.getClosedTradables().stream() + .filter(tradable -> tradable instanceof Trade) + .map(tradable -> (Trade) tradable); + Stream allTrades = Stream.concat(failedAndOpenTrades, closedTrades); + boolean txKeyUsedAtAnyOpenTrade = allTrades + .filter(t -> !t.getId().equals(trade.getId())) // ignore same trade + .anyMatch(t -> { + String extra = t.getCounterCurrencyExtraData(); + if (extra == null) { + return false; + } + + boolean alreadyUsed = extra.equals(txKey); + if (alreadyUsed) { + String message = "Peer used the XMR tx key already at another trade with trade ID " + + t.getId() + ". This might be a scam attempt."; + log.warn(message); + proofResultWithTradeIdProperty.set(new XmrProofResultWithTradeId(XmrProofResult.TX_KEY_REUSED, trade.getId())); + } + return alreadyUsed; + }); + + if (txKeyUsedAtAnyOpenTrade) { + return; + } + + if (preferences.isAutoConfirmXmr()) { + String address = sellersAssetsAccountPayload.getAddress(); + //TODO for dev testing + address = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub"; + // 8.90259736 is dev test value + long amount = (long) Float.parseFloat("8.90259736") * 100000000; // todo check XMR denomination + xmrTransferProofService.requestProof(trade.getId(), + txHash, + txKey, + address, + amount, + result -> { + switch (result) { + case TX_NOT_CONFIRMED: + // Repeating the requests is handled in XmrTransferProofRequester + break; + case PROOF_OK: + if (!p2PService.isBootstrapped()) { + return; + } + + if (!walletsSetup.hasSufficientPeersForBroadcast()) { + return; + } + + if (!walletsSetup.isDownloadComplete()) { + return; + } + + if (!trade.isPayoutPublished()) { + trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); + } + + accountAgeWitnessService.maybeSignWitness(trade); + + ((SellerTrade) trade).onFiatPaymentReceived(() -> { + }, errorMessage -> { + }); + break; + case UNKNOWN_ERROR: + case TX_KEY_REUSED: + case TX_NEVER_FOUND: + case TX_HASH_INVALID: + case TX_KEY_INVALID: + case ADDRESS_INVALID: + case AMOUNT_NOT_MATCHING: + case PROOF_FAILED: + default: + log.error("Case not handled. " + result); + break; + } + + proofResultWithTradeIdProperty.set(new XmrProofResultWithTradeId(result, trade.getId())); + }, + (errorMsg, throwable) -> { + log.warn(errorMsg); + }); + } + } + } } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java new file mode 100644 index 00000000000..d431826d36e --- /dev/null +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java @@ -0,0 +1,31 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.asset.xmr; + +public enum XmrProofResult { + TX_NOT_CONFIRMED, + PROOF_OK, + UNKNOWN_ERROR, + TX_KEY_REUSED, + TX_NEVER_FOUND, + TX_HASH_INVALID, + TX_KEY_INVALID, + ADDRESS_INVALID, + AMOUNT_NOT_MATCHING, + PROOF_FAILED +} diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java new file mode 100644 index 00000000000..8e01a189f4d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java @@ -0,0 +1,31 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.asset.xmr; + +import lombok.Value; + +@Value +public class XmrProofResultWithTradeId { + private final XmrProofResult xmrProofResult; + private final String tradeId; + + public XmrProofResultWithTradeId(XmrProofResult xmrProofResult, String tradeId) { + this.xmrProofResult = xmrProofResult; + this.tradeId = tradeId; + } +} diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java new file mode 100644 index 00000000000..caa8b84c1d3 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java @@ -0,0 +1,158 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.asset.xmr; + +import bisq.common.UserThread; +import bisq.common.app.Version; +import bisq.common.handlers.FaultHandler; +import bisq.common.util.Utilities; + +import javax.inject.Singleton; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import lombok.extern.slf4j.Slf4j; + +import org.jetbrains.annotations.NotNull; + +@Slf4j +@Singleton +class XmrTransferProofRequester { + + private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( + "XmrTransferProofService", 3, 5, 10 * 60); + private final XmrTxProofHttpClient httpClient; + private final String txHash; + private final String txKey; + private final String recipientAddress; + private final long amount; + private final Consumer resultHandler; + private final FaultHandler faultHandler; + + private long firstRequest; + //todo dev settings + private long REPEAT_REQUEST_SEC = TimeUnit.SECONDS.toMillis(5); + private long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + XmrTransferProofRequester(XmrTxProofHttpClient httpClient, + String txHash, + String txKey, + String recipientAddress, + long amount, + Consumer resultHandler, + FaultHandler faultHandler) { + this.httpClient = httpClient; + this.txHash = txHash; + this.txKey = txKey; + this.recipientAddress = recipientAddress; + this.amount = amount; + this.resultHandler = resultHandler; + this.faultHandler = faultHandler; + firstRequest = System.currentTimeMillis(); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void request() { + // todo dev test address for a real tx proof + /* + txID: 5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802 + txKey: f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906 + address: 85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub + ammount : 8.90259736 XMR + */ + + ListenableFuture future = executorService.submit(() -> { + Thread.currentThread().setName("XmrTransferProofRequest-" + this.toString()); + String param = "/api/outputs?txhash=" + txHash + + "&address=" + recipientAddress + + "&viewkey=" + txKey + + "&txprove=1"; + String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); + Thread.sleep(3000); + + // + + return parseResult(json); + }); + + Futures.addCallback(future, new FutureCallback<>() { + public void onSuccess(XmrProofResult result) { + if (result == XmrProofResult.TX_NOT_CONFIRMED && System.currentTimeMillis() - firstRequest < MAX_REQUEST_PERIOD) { + UserThread.runAfter(() -> request(), REPEAT_REQUEST_SEC); + } else { + UserThread.execute(() -> resultHandler.accept(result)); + } + } + + public void onFailure(@NotNull Throwable throwable) { + String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; + faultHandler.handleFault(errorMessage, throwable); + } + }); + } + + private XmrProofResult parseResult(String json) { + //TODO parse json + //TODO need service to check diff. error conditions + return XmrProofResult.PROOF_OK; + // check recipientAddress and amount + // json example + /* + +{ + "data": { + "address": "42f18fc61586554095b0799b5c4b6f00cdeb26a93b20540d366932c6001617b75db35109fbba7d5f275fef4b9c49e0cc1c84b219ec6ff652fda54f89f7f63c88", + "outputs": [ + { + "amount": 34980000000000, + "match": true, + "output_idx": 0, + "output_pubkey": "35d7200229e725c2bce0da3a2f20ef0720d242ecf88bfcb71eff2025c2501fdb" + }, + { + "amount": 0, + "match": false, + "output_idx": 1, + "output_pubkey": "44efccab9f9b42e83c12da7988785d6c4eb3ec6e7aa2ae1234e2f0f7cb9ed6dd" + } + ], + "tx_hash": "17049bc5f2d9fbca1ce8dae443bbbbed2fc02f1ee003ffdd0571996905faa831", + "tx_prove": false, + "viewkey": "f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501" + }, + "status": "success" +} + + */ + } +} diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java new file mode 100644 index 00000000000..177d6238f0c --- /dev/null +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java @@ -0,0 +1,78 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.asset.xmr; + +import bisq.common.handlers.FaultHandler; + +import javax.inject.Inject; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import lombok.extern.slf4j.Slf4j; + +/** + * Manages the XMR transfers proof requests for multiple trades. + */ +@Slf4j +public class XmrTransferProofService { + private final XmrTxProofHttpClient httpClient; + private Map map = new HashMap<>(); + + @Inject + public XmrTransferProofService(XmrTxProofHttpClient httpClient) { + this.httpClient = httpClient; + //this.httpClient.setBaseUrl("http://139.59.140.37:8081"); + this.httpClient.setBaseUrl("http://127.0.0.1:8081"); + this.httpClient.setIgnoreSocks5Proxy(false); + } + + public void requestProof(String tradeId, + String txHash, + String txKey, + String recipientAddress, + long amount, + Consumer resultHandler, + FaultHandler faultHandler) { + if (map.containsKey(tradeId)) { + log.warn("We started a proof request for trade with ID {} already", tradeId); + return; + } + + XmrTransferProofRequester requester = new XmrTransferProofRequester(httpClient, + txHash, + txKey, + recipientAddress, + amount, + result -> { + cleanup(tradeId); + resultHandler.accept(result); + }, + (errorMsg, throwable) -> { + cleanup(tradeId); + faultHandler.handleFault(errorMsg, throwable); + }); + map.put(tradeId, requester); + requester.request(); + } + + private void cleanup(String tradeId) { + map.remove(tradeId); + } +} diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java new file mode 100644 index 00000000000..d270a39a2ce --- /dev/null +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java @@ -0,0 +1,32 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.asset.xmr; + +import bisq.network.Socks5ProxyProvider; +import bisq.network.http.HttpClient; + +import javax.inject.Inject; + +import javax.annotation.Nullable; + +public class XmrTxProofHttpClient extends HttpClient { + @Inject + public XmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { + super(socks5ProxyProvider); + } +} diff --git a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java index 963947b48e7..b25bcd0ebe2 100644 --- a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java @@ -21,6 +21,7 @@ import bisq.network.p2p.NodeAddress; import bisq.common.app.Version; +import bisq.common.proto.ProtoUtil; import bisq.common.util.Utilities; import com.google.protobuf.ByteString; @@ -41,17 +42,24 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im @Nullable private final String counterCurrencyTxId; + // Added in v1.3.7 + // We use that for the XMR txKey but want to keep it generic to be flexible for data of other payment methods or assets. + @Nullable + private String counterCurrencyExtraData; + public CounterCurrencyTransferStartedMessage(String tradeId, String buyerPayoutAddress, NodeAddress senderNodeAddress, byte[] buyerSignature, @Nullable String counterCurrencyTxId, + @Nullable String counterCurrencyExtraData, String uid) { this(tradeId, buyerPayoutAddress, senderNodeAddress, buyerSignature, counterCurrencyTxId, + counterCurrencyExtraData, uid, Version.getP2PMessageVersion()); } @@ -66,6 +74,7 @@ private CounterCurrencyTransferStartedMessage(String tradeId, NodeAddress senderNodeAddress, byte[] buyerSignature, @Nullable String counterCurrencyTxId, + @Nullable String counterCurrencyExtraData, String uid, int messageVersion) { super(messageVersion, tradeId, uid); @@ -73,6 +82,7 @@ private CounterCurrencyTransferStartedMessage(String tradeId, this.senderNodeAddress = senderNodeAddress; this.buyerSignature = buyerSignature; this.counterCurrencyTxId = counterCurrencyTxId; + this.counterCurrencyExtraData = counterCurrencyExtraData; } @Override @@ -85,16 +95,19 @@ public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { .setUid(uid); Optional.ofNullable(counterCurrencyTxId).ifPresent(e -> builder.setCounterCurrencyTxId(counterCurrencyTxId)); + Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); return getNetworkEnvelopeBuilder().setCounterCurrencyTransferStartedMessage(builder).build(); } - public static CounterCurrencyTransferStartedMessage fromProto(protobuf.CounterCurrencyTransferStartedMessage proto, int messageVersion) { + public static CounterCurrencyTransferStartedMessage fromProto(protobuf.CounterCurrencyTransferStartedMessage proto, + int messageVersion) { return new CounterCurrencyTransferStartedMessage(proto.getTradeId(), proto.getBuyerPayoutAddress(), NodeAddress.fromProto(proto.getSenderNodeAddress()), proto.getBuyerSignature().toByteArray(), - proto.getCounterCurrencyTxId().isEmpty() ? null : proto.getCounterCurrencyTxId(), + ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyTxId()), + ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData()), proto.getUid(), messageVersion); } @@ -106,6 +119,7 @@ public String toString() { "\n buyerPayoutAddress='" + buyerPayoutAddress + '\'' + ",\n senderNodeAddress=" + senderNodeAddress + ",\n counterCurrencyTxId=" + counterCurrencyTxId + + ",\n counterCurrencyExtraData=" + counterCurrencyExtraData + ",\n uid='" + uid + '\'' + ",\n buyerSignature=" + Utilities.bytesAsHexString(buyerSignature) + "\n} " + super.toString(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java index 0dbf2ce3cdd..40cd15750a8 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/buyer/BuyerSendCounterCurrencyTransferStartedMessage.java @@ -54,6 +54,7 @@ protected void run() { processModel.getMyNodeAddress(), processModel.getPayoutTxSignature(), trade.getCounterCurrencyTxId(), + trade.getCounterCurrencyExtraData(), UUID.randomUUID().toString() ); NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress(); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 935d780c76e..2469774c254 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -49,7 +49,17 @@ protected void run() { // update to the latest peer address of our peer if the message is correct trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); - trade.setCounterCurrencyTxId(message.getCounterCurrencyTxId()); + + String counterCurrencyTxId = message.getCounterCurrencyTxId(); + if (counterCurrencyTxId != null && counterCurrencyTxId.length() < 100) { + trade.setCounterCurrencyTxId(counterCurrencyTxId); + } + + String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); + if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { + trade.setCounterCurrencyExtraData(counterCurrencyExtraData); + processModel.getTradeManager().processCounterCurrencyExtraData(trade); + } processModel.removeMailboxMessageAfterProcessing(trade); trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG); diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index db2306f7735..89e9fd2fd36 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -17,8 +17,8 @@ package bisq.core.user; -import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.nodes.BtcNodes; +import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Country; import bisq.core.locale.CountryUtil; @@ -394,6 +394,11 @@ public void setTacAcceptedV120(boolean tacAccepted) { persist(); } + public void setAutoConfirmXmr(boolean autoConfirmXmr) { + prefPayload.setAutoConfirmXmr(autoConfirmXmr); + persist(); + } + private void persist() { if (initialReadDone) storage.queueUpForSave(prefPayload); @@ -965,5 +970,7 @@ private interface ExcludesDelegateMethods { int getBlockNotifyPort(); void setTacAcceptedV120(boolean tacAccepted); + + void setAutoConfirmXmr(boolean autoConfirmXmr); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 3e0997d68b4..6fc38377b5a 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -127,6 +127,9 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnve private int blockNotifyPort; private boolean tacAcceptedV120; + // Added with 1.3.7 false be default + private boolean autoConfirmXmr; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -186,7 +189,8 @@ public Message toProtoMessage() { .setIgnoreDustThreshold(ignoreDustThreshold) .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) .setBlockNotifyPort(blockNotifyPort) - .setTacAcceptedV120(tacAcceptedV120); + .setTacAcceptedV120(tacAcceptedV120) + .setAutoConfirmXmr(autoConfirmXmr); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); @@ -274,6 +278,7 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co proto.getIgnoreDustThreshold(), proto.getBuyerSecurityDepositAsPercentForCrypto(), proto.getBlockNotifyPort(), - proto.getTacAcceptedV120()); + proto.getTacAcceptedV120(), + proto.getAutoConfirmXmr()); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 1b101cd767c..2dd4b1a1c47 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -718,6 +718,9 @@ portfolio.pending.step3_seller.amountToReceive=Amount to receive portfolio.pending.step3_seller.yourAddress=Your {0} address portfolio.pending.step3_seller.buyersAddress=Buyers {0} address portfolio.pending.step3_seller.yourAccount=Your trading account +portfolio.pending.step3_seller.xmrTxHash=Tx hash +portfolio.pending.step3_seller.xmrTxKey=Tx private key +portfolio.pending.step3_seller.xmrTxVerificationError=The XMR transfer validation for trade with ID ''{0}'' failed.\nReason: ''{1}'' portfolio.pending.step3_seller.buyersAccount=Buyers trading account portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1} @@ -1051,6 +1054,7 @@ setting.preferences.explorer=Bitcoin block explorer setting.preferences.explorer.bsq=BSQ block explorer setting.preferences.deviation=Max. deviation from market price setting.preferences.avoidStandbyMode=Avoid standby mode +setting.preferences.autoConfirmXMR=Use XMR tx proof tool setting.preferences.deviationToLarge=Values higher than {0}% are not allowed. setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte) setting.preferences.useCustomValue=Use custom value @@ -2488,6 +2492,10 @@ sendPrivateNotificationWindow.send=Send private notification showWalletDataWindow.walletData=Wallet data showWalletDataWindow.includePrivKeys=Include private keys +setXMRTxKeyWindow.headline=Prove sending of XMR +setXMRTxKeyWindow.txHash=Transaction hash +setXMRTxKeyWindow.txKey=Tx private key + # We do not translate the tac because of the legal nature. We would need translations checked by lawyers # in each language which is too expensive atm. tacWindow.headline=User agreement diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java new file mode 100644 index 00000000000..8b68eea6c18 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -0,0 +1,101 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.overlays.windows; + +import bisq.desktop.components.InputTextField; +import bisq.desktop.main.overlays.Overlay; + +import bisq.core.locale.Res; + +import bisq.common.UserThread; + +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; + +import javafx.geometry.HPos; +import javafx.geometry.Insets; + +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +import static bisq.desktop.util.FormBuilder.addInputTextField; +import static javafx.beans.binding.Bindings.createBooleanBinding; + +public class SetXmrTxKeyWindow extends Overlay { + + private InputTextField txHashInputTextField, txKeyInputTextField; + + public SetXmrTxKeyWindow() { + type = Type.Attention; + } + + public void show() { + if (headLine == null) + headLine = Res.get("setXMRTxKeyWindow.headline"); + + width = 868; + createGridPane(); + addHeadLine(); + addContent(); + addButtons(); + + actionButton.disableProperty().bind(createBooleanBinding(() -> + txHashInputTextField.getText().isEmpty() || txKeyInputTextField.getText().isEmpty(), + txHashInputTextField.textProperty(), txKeyInputTextField.textProperty())); + + applyStyles(); + display(); + } + + @Override + protected void createGridPane() { + gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(5); + gridPane.setPadding(new Insets(64, 64, 64, 64)); + gridPane.setPrefWidth(width); + + ColumnConstraints columnConstraints1 = new ColumnConstraints(); + columnConstraints1.setHalignment(HPos.RIGHT); + columnConstraints1.setHgrow(Priority.SOMETIMES); + gridPane.getColumnConstraints().addAll(columnConstraints1); + } + + @Nullable + public String getTxHash() { + return txHashInputTextField != null ? txHashInputTextField.getText() : null; + } + + @Nullable + public String getTxKey() { + return txKeyInputTextField != null ? txKeyInputTextField.getText() : null; + } + + private void addContent() { + txHashInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txHash"), 10); + txKeyInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txKey")); + + UserThread.runAfter(() -> { + //todo: remove dev test data + txHashInputTextField.setText("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802"); + txKeyInputTextField.setText("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906"); + }, 200, TimeUnit.MILLISECONDS); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index 9f5276f9b97..e3cf3ec1140 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -188,8 +188,6 @@ public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er final Trade trade = getTrade(); checkNotNull(trade, "trade must not be null"); checkArgument(trade instanceof BuyerTrade, "Check failed: trade instanceof BuyerTrade"); - // TODO UI not impl yet - trade.setCounterCurrencyTxId(""); ((BuyerTrade) trade).onFiatPaymentStarted(resultHandler, errorMessageHandler); } @@ -703,5 +701,13 @@ public boolean isBootstrappedOrShowPopup() { public void addTradeToFailedTrades() { tradeManager.addTradeToFailedTrades(selectedTrade); } + + public boolean isSignWitnessTrade() { + return accountAgeWitnessService.isSignWitnessTrade(selectedTrade); + } + + public void maybeSignWitness() { + accountAgeWitnessService.maybeSignWitness(selectedTrade); + } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java index 09b23eb8d13..ba7b285449c 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesViewModel.java @@ -22,8 +22,6 @@ import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.GUIUtil; -import bisq.core.account.sign.SignedWitness; -import bisq.core.account.witness.AccountAgeWitness; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.CurrencyUtil; @@ -34,17 +32,13 @@ import bisq.core.trade.Contract; import bisq.core.trade.Trade; import bisq.core.trade.closed.ClosedTradableManager; -import bisq.core.trade.messages.RefreshTradeStateRequest; -import bisq.core.trade.messages.TraderSignedWitnessMessage; import bisq.core.user.User; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.BsqFormatter; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.BtcAddressValidator; -import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; -import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.ClockWatcher; import bisq.common.app.DevEnv; @@ -63,7 +57,6 @@ import javafx.beans.property.SimpleObjectProperty; import java.util.Date; -import java.util.UUID; import java.util.stream.Collectors; import lombok.Getter; @@ -370,63 +363,6 @@ public int getNumPastTrades(Trade trade) { .size(); } - /////////////////////////////////////////////////////////////////////////////////////////// - // AccountAgeWitness signing - /////////////////////////////////////////////////////////////////////////////////////////// - - - public boolean isSignWitnessTrade() { - checkNotNull(trade, "trade must not be null"); - checkNotNull(trade.getOffer(), "offer must not be null"); - AccountAgeWitness myWitness = accountAgeWitnessService.getMyWitness(dataModel.getSellersPaymentAccountPayload()); - - accountAgeWitnessService.getAccountAgeWitnessUtils().witnessDebugLog(trade, myWitness); - - return accountAgeWitnessService.accountIsSigner(myWitness) && - !accountAgeWitnessService.peerHasSignedWitness(trade) && - accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()); - } - - public void maybeSignWitness() { - if (isSignWitnessTrade()) { - var signedWitness = accountAgeWitnessService.traderSignPeersAccountAgeWitness(trade); - signedWitness.ifPresent(this::sendSignedWitnessToPeer); - } - } - - private void sendSignedWitnessToPeer(SignedWitness signedWitness) { - Trade trade = getTrade(); - if (trade == null) return; - - NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress(); - var traderSignedWitnessMessage = new TraderSignedWitnessMessage(UUID.randomUUID().toString(), trade.getId(), - tradingPeerNodeAddress, signedWitness); - - p2PService.sendEncryptedMailboxMessage( - tradingPeerNodeAddress, - trade.getProcessModel().getTradingPeer().getPubKeyRing(), - traderSignedWitnessMessage, - new SendMailboxMessageListener() { - @Override - public void onArrived() { - log.info("SendMailboxMessageListener onArrived tradeId={} at peer {} SignedWitness {}", - trade.getId(), tradingPeerNodeAddress, signedWitness); - } - - @Override - public void onStoredInMailbox() { - log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {} SignedWitness {}", - trade.getId(), tradingPeerNodeAddress, signedWitness); - } - - @Override - public void onFault(String errorMessage) { - log.error("SendMailboxMessageListener onFault tradeId={} at peer {} SignedWitness {}", - trade.getId(), tradingPeerNodeAddress, signedWitness); - } - } - ); - } /////////////////////////////////////////////////////////////////////////////////////////// // States diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 80826ccbde4..9bd9bbaa248 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -48,6 +48,7 @@ import bisq.desktop.components.paymentmethods.WeChatPayForm; import bisq.desktop.components.paymentmethods.WesternUnionForm; import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.overlays.windows.SetXmrTxKeyWindow; import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.desktop.util.DisplayUtils; @@ -384,73 +385,93 @@ protected void applyOnDisputeOpened() { /////////////////////////////////////////////////////////////////////////////////////////// private void onPaymentStarted() { - if (model.dataModel.isBootstrappedOrShowPopup()) { - if (model.dataModel.getSellersPaymentAccountPayload() instanceof CashDepositAccountPayload) { - String key = "confirmPaperReceiptSent"; - if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { - Popup popup = new Popup(); - popup.headLine(Res.get("portfolio.pending.step2_buyer.paperReceipt.headline")) - .feedback(Res.get("portfolio.pending.step2_buyer.paperReceipt.msg")) - .onAction(this::showConfirmPaymentStartedPopup) - .closeButtonText(Res.get("shared.no")) - .onClose(popup::hide) - .dontShowAgainId(key) - .show(); - } else { - showConfirmPaymentStartedPopup(); - } - } else if (model.dataModel.getSellersPaymentAccountPayload() instanceof WesternUnionAccountPayload) { - String key = "westernUnionMTCNSent"; - if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { - String email = ((WesternUnionAccountPayload) model.dataModel.getSellersPaymentAccountPayload()).getEmail(); - Popup popup = new Popup(); - popup.headLine(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.headline")) - .feedback(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg", email)) - .onAction(this::showConfirmPaymentStartedPopup) - .actionButtonText(Res.get("shared.yes")) - .closeButtonText(Res.get("shared.no")) - .onClose(popup::hide) - .dontShowAgainId(key) - .show(); - } else { - showConfirmPaymentStartedPopup(); - } - } else if (model.dataModel.getSellersPaymentAccountPayload() instanceof MoneyGramAccountPayload) { - String key = "moneyGramMTCNSent"; - if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { - String email = ((MoneyGramAccountPayload) model.dataModel.getSellersPaymentAccountPayload()).getEmail(); - Popup popup = new Popup(); - popup.headLine(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.headline")) - .feedback(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg", email)) - .onAction(this::showConfirmPaymentStartedPopup) - .actionButtonText(Res.get("shared.yes")) - .closeButtonText(Res.get("shared.no")) - .onClose(popup::hide) - .dontShowAgainId(key) - .show(); - } else { - showConfirmPaymentStartedPopup(); - } - } else if (model.dataModel.getSellersPaymentAccountPayload() instanceof HalCashAccountPayload) { - String key = "halCashCodeInfo"; - if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { - String mobileNr = ((HalCashAccountPayload) model.dataModel.getSellersPaymentAccountPayload()).getMobileNr(); - Popup popup = new Popup(); - popup.headLine(Res.get("portfolio.pending.step2_buyer.halCashInfo.headline")) - .feedback(Res.get("portfolio.pending.step2_buyer.halCashInfo.msg", - model.dataModel.getTrade().getShortId(), mobileNr)) - .onAction(this::showConfirmPaymentStartedPopup) - .actionButtonText(Res.get("shared.yes")) - .closeButtonText(Res.get("shared.no")) - .onClose(popup::hide) - .dontShowAgainId(key) - .show(); - } else { - showConfirmPaymentStartedPopup(); - } + if (!model.dataModel.isBootstrappedOrShowPopup()) { + return; + } + + PaymentAccountPayload sellersPaymentAccountPayload = model.dataModel.getSellersPaymentAccountPayload(); + Trade trade = checkNotNull(model.dataModel.getTrade(), "trade must not be null"); + if (sellersPaymentAccountPayload instanceof CashDepositAccountPayload) { + String key = "confirmPaperReceiptSent"; + if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { + Popup popup = new Popup(); + popup.headLine(Res.get("portfolio.pending.step2_buyer.paperReceipt.headline")) + .feedback(Res.get("portfolio.pending.step2_buyer.paperReceipt.msg")) + .onAction(this::showConfirmPaymentStartedPopup) + .closeButtonText(Res.get("shared.no")) + .onClose(popup::hide) + .dontShowAgainId(key) + .show(); + } else { + showConfirmPaymentStartedPopup(); + } + } else if (sellersPaymentAccountPayload instanceof WesternUnionAccountPayload) { + String key = "westernUnionMTCNSent"; + if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { + String email = ((WesternUnionAccountPayload) sellersPaymentAccountPayload).getEmail(); + Popup popup = new Popup(); + popup.headLine(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.headline")) + .feedback(Res.get("portfolio.pending.step2_buyer.westernUnionMTCNInfo.msg", email)) + .onAction(this::showConfirmPaymentStartedPopup) + .actionButtonText(Res.get("shared.yes")) + .closeButtonText(Res.get("shared.no")) + .onClose(popup::hide) + .dontShowAgainId(key) + .show(); } else { showConfirmPaymentStartedPopup(); } + } else if (sellersPaymentAccountPayload instanceof MoneyGramAccountPayload) { + String key = "moneyGramMTCNSent"; + if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { + String email = ((MoneyGramAccountPayload) sellersPaymentAccountPayload).getEmail(); + Popup popup = new Popup(); + popup.headLine(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.headline")) + .feedback(Res.get("portfolio.pending.step2_buyer.moneyGramMTCNInfo.msg", email)) + .onAction(this::showConfirmPaymentStartedPopup) + .actionButtonText(Res.get("shared.yes")) + .closeButtonText(Res.get("shared.no")) + .onClose(popup::hide) + .dontShowAgainId(key) + .show(); + } else { + showConfirmPaymentStartedPopup(); + } + } else if (sellersPaymentAccountPayload instanceof HalCashAccountPayload) { + String key = "halCashCodeInfo"; + if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { + String mobileNr = ((HalCashAccountPayload) sellersPaymentAccountPayload).getMobileNr(); + Popup popup = new Popup(); + popup.headLine(Res.get("portfolio.pending.step2_buyer.halCashInfo.headline")) + .feedback(Res.get("portfolio.pending.step2_buyer.halCashInfo.msg", + trade.getShortId(), mobileNr)) + .onAction(this::showConfirmPaymentStartedPopup) + .actionButtonText(Res.get("shared.yes")) + .closeButtonText(Res.get("shared.no")) + .onClose(popup::hide) + .dontShowAgainId(key) + .show(); + } else { + showConfirmPaymentStartedPopup(); + } + } else if (sellersPaymentAccountPayload instanceof AssetsAccountPayload) { + Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); + if (offer.getCurrencyCode().equals("XMR")) { + SetXmrTxKeyWindow setXmrTxKeyWindow = new SetXmrTxKeyWindow(); + setXmrTxKeyWindow.actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) + .onAction(() -> { + String txKey = setXmrTxKeyWindow.getTxKey(); + String txHash = setXmrTxKeyWindow.getTxHash(); + trade.setCounterCurrencyExtraData(txKey); + trade.setCounterCurrencyTxId(txHash); + showConfirmPaymentStartedPopup(); + }) + .closeButtonText(Res.get("shared.cancel")) + .onClose(setXmrTxKeyWindow::hide) + .show(); + } + } else { + showConfirmPaymentStartedPopup(); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 07ab8774ed4..c2da474a49f 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -42,6 +42,7 @@ import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Contract; import bisq.core.trade.Trade; +import bisq.core.trade.asset.xmr.XmrProofResultWithTradeId; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -59,6 +60,8 @@ import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; +import javafx.beans.value.ChangeListener; + import java.util.Optional; import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup; @@ -73,6 +76,8 @@ public class SellerStep3View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; + private final ChangeListener xmrProofResultWithTradeIdListener; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -80,6 +85,10 @@ public class SellerStep3View extends TradeStepView { public SellerStep3View(PendingTradesViewModel model) { super(model); + + xmrProofResultWithTradeIdListener = (observable, oldValue, newValue) -> { + processXmrProofResult(newValue); + }; } @Override @@ -139,6 +148,9 @@ public void activate() { } } }); + + model.dataModel.tradeManager.getProofResultWithTradeIdProperty().addListener(xmrProofResultWithTradeIdListener); + processXmrProofResult(model.dataModel.tradeManager.getProofResultWithTradeIdProperty().get()); } @Override @@ -154,6 +166,9 @@ public void deactivate() { if (timeoutTimer != null) timeoutTimer.stop(); + + model.dataModel.tradeManager.getProofResultWithTradeIdProperty().removeListener(xmrProofResultWithTradeIdListener); + } /////////////////////////////////////////////////////////////////////////////////////////// @@ -216,6 +231,20 @@ protected void addContent() { peersPaymentDetailsTextField.setMouseTransparent(false); peersPaymentDetailsTextField.setTooltip(new Tooltip(peersPaymentDetails)); + String counterCurrencyTxId = trade.getCounterCurrencyTxId(); + String counterCurrencyExtraData = trade.getCounterCurrencyExtraData(); + if (counterCurrencyTxId != null && !counterCurrencyTxId.isEmpty() && + counterCurrencyExtraData != null && !counterCurrencyExtraData.isEmpty()) { + TextFieldWithCopyIcon txHashTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, + 0, Res.get("portfolio.pending.step3_seller.xmrTxHash"), counterCurrencyTxId).second; + txHashTextField.setMouseTransparent(false); + txHashTextField.setTooltip(new Tooltip(myPaymentDetails)); + + TextFieldWithCopyIcon txKeyDetailsTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, gridRow, + 1, Res.get("portfolio.pending.step3_seller.xmrTxKey"), counterCurrencyExtraData).second; + txKeyDetailsTextField.setMouseTransparent(false); + txKeyDetailsTextField.setTooltip(new Tooltip(peersPaymentDetails)); + } Tuple4 tuple = addButtonBusyAnimationLabelAfterGroup(gridPane, ++gridRow, Res.get("portfolio.pending.step3_seller.confirmReceipt")); @@ -294,7 +323,7 @@ private void onPaymentReceived() { } } message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.note"); - if (model.isSignWitnessTrade()) { + if (model.dataModel.isSignWitnessTrade()) { message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer"); } new Popup() @@ -351,7 +380,7 @@ else if (paymentAccountPayload instanceof F2FAccountPayload) message += Res.get("portfolio.pending.step3_seller.bankCheck", optionalHolderName.get(), part); } - if (model.isSignWitnessTrade()) { + if (model.dataModel.isSignWitnessTrade()) { message += Res.get("portfolio.pending.step3_seller.onPaymentReceived.signer"); } } @@ -370,7 +399,7 @@ private void confirmPaymentReceived() { if (!trade.isPayoutPublished()) trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); - model.maybeSignWitness(); + model.dataModel.maybeSignWitness(); model.dataModel.onFiatPaymentReceived(() -> { // In case the first send failed we got the support button displayed. @@ -406,6 +435,47 @@ else if (paymentAccountPayload instanceof SepaInstantAccountPayload) protected void deactivatePaymentButtons(boolean isDisabled) { confirmButton.setDisable(isDisabled); } + + private void processXmrProofResult(XmrProofResultWithTradeId result) { + if (result == null) { + return; + } + + Trade trade = model.dataModel.getTrade(); + if (trade == null) { + return; + } + + if (result.getTradeId().equals(trade.getId())) { + boolean hasFailed; + switch (result.getXmrProofResult()) { + case TX_NOT_CONFIRMED: + case PROOF_OK: + hasFailed = false; + break; + case UNKNOWN_ERROR: + case TX_KEY_REUSED: + case TX_NEVER_FOUND: + case TX_HASH_INVALID: + case TX_KEY_INVALID: + case ADDRESS_INVALID: + case AMOUNT_NOT_MATCHING: + case PROOF_FAILED: + default: + hasFailed = true; + break; + } + + if (hasFailed) { + // We don't show yet translated messages for the diff. error cases but the ENUM name. + new Popup().warning(Res.get("portfolio.pending.step3_seller.xmrTxVerificationError", + result.getTradeId(), + result.getXmrProofResult().toString())) + .width(800) + .show(); + } + } + } } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index b8e182c4a9b..40904fd3512 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -100,9 +100,6 @@ @FxmlView public class PreferencesView extends ActivatableViewAndModel { - - // not supported yet - //private ComboBox btcDenominationComboBox; private ComboBox blockChainExplorerComboBox; private ComboBox bsqBlockChainExplorerComboBox; private ComboBox userLanguageComboBox; @@ -110,7 +107,7 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, - avoidStandbyMode, useCustomFee; + avoidStandbyMode, useCustomFee, autoConfirmXmr; private int gridRow = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, /*referralIdInputTextField,*/ @@ -133,7 +130,6 @@ public class PreferencesView extends ActivatableViewAndModel cryptoCurrenciesListView; private ComboBox cryptoCurrenciesComboBox; private Button resetDontShowAgainButton, resyncDaoFromGenesisButton, resyncDaoFromResourcesButton; - // private ListChangeListener displayCurrenciesListChangeListener; private ObservableList blockExplorers; private ObservableList bsqBlockChainExplorers; private ObservableList languageCodes; @@ -232,7 +228,7 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// private void initializeGeneralOptions() { - int titledGroupBgRowSpan = displayStandbyModeFeature ? 8 : 7; + int titledGroupBgRowSpan = displayStandbyModeFeature ? 10 : 9; TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general")); GridPane.setColumnSpan(titledGroupBg, 1); @@ -367,6 +363,8 @@ private void initializeGeneralOptions() { } }; + autoConfirmXmr = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.autoConfirmXmr")); + if (displayStandbyModeFeature) { // AvoidStandbyModeService feature works only on OSX & Windows avoidStandbyMode = addSlideToggleButton(root, ++gridRow, @@ -592,7 +590,6 @@ private void initializeDisplayOptions() { TitledGroupBg titledGroupBg = addTitledGroupBg(root, ++gridRow, 5, Res.get("setting.preferences.displayOptions"), Layout.GROUP_DISTANCE); GridPane.setColumnSpan(titledGroupBg, 1); -// showOwnOffersInOfferBook = addLabelCheckBox(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); showOwnOffersInOfferBook = addSlideToggleButton(root, gridRow, Res.get("setting.preferences.showOwnOffers"), Layout.FIRST_ROW_AND_GROUP_DISTANCE); useAnimations = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useAnimations")); useDarkMode = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.useDarkMode")); @@ -649,18 +646,6 @@ private void initializeDaoOptions() { /////////////////////////////////////////////////////////////////////////////////////////// private void activateGeneralOptions() { - /* List baseCurrencyNetworks = Arrays.asList(BaseCurrencyNetwork.values()); - - // We allow switching to testnet to make it easier for users to test the testnet DAO version - // We only show mainnet and dao testnet. Testnet is rather un-usable for application testing when asics - // create 10000s of blocks per day. - baseCurrencyNetworks = baseCurrencyNetworks.stream() - .filter(e -> e.isMainnet() || e.isDaoBetaNet() || e.isDaoRegTest()) - .collect(Collectors.toList()); - selectBaseCurrencyNetworkComboBox.setItems(FXCollections.observableArrayList(baseCurrencyNetworks)); - selectBaseCurrencyNetworkComboBox.setOnAction(e -> onSelectNetwork()); - selectBaseCurrencyNetworkComboBox.getSelectionModel().select(BaseCurrencyNetwork.CURRENT_VALUE);*/ - boolean useCustomWithdrawalTxFee = preferences.isUseCustomWithdrawalTxFee(); useCustomFee.setSelected(useCustomWithdrawalTxFee); @@ -705,17 +690,6 @@ public String fromString(String string) { .show(); } } - // Should we apply the changed currency immediately to the language list? - // If so and the user selects a unknown language he might get lost and it is hard to find - // again the language he understands - /* if (selectedItem != null && !selectedItem.equals(preferences.getUserLanguage())) { - preferences.setUserLanguage(selectedItem); - UserThread.execute(() -> { - languageCodes.clear(); - languageCodes.addAll(LanguageUtil.getAllLanguageCodes()); - userLanguageComboBox.getSelectionModel().select(preferences.getUserLanguage()); - }); - }*/ }); userCountryComboBox.setItems(countries); @@ -839,9 +813,6 @@ private void activateDisplayPreferences() { useDarkMode.setSelected(preferences.getCssTheme() == 1); useDarkMode.setOnAction(e -> preferences.setCssTheme(useDarkMode.isSelected())); - // useStickyMarketPriceCheckBox.setSelected(preferences.isUseStickyMarketPrice()); - // useStickyMarketPriceCheckBox.setOnAction(e -> preferences.setUseStickyMarketPrice(useStickyMarketPriceCheckBox.isSelected())); - sortMarketCurrenciesNumerically.setSelected(preferences.isSortMarketCurrenciesNumerically()); sortMarketCurrenciesNumerically.setOnAction(e -> preferences.setSortMarketCurrenciesNumerically(sortMarketCurrenciesNumerically.isSelected())); @@ -855,6 +826,9 @@ private void activateDisplayPreferences() { } else { preferences.setUseStandbyMode(false); } + + autoConfirmXmr.setSelected(preferences.isAutoConfirmXmr()); + autoConfirmXmr.setOnAction(e -> preferences.setAutoConfirmXmr(autoConfirmXmr.isSelected())); } private void activateDaoPreferences() { @@ -943,22 +917,6 @@ private void updateDaoFields() { blockNotifyPortTextField.setDisable(daoOptionsSet); } - /* private void onSelectNetwork() { - if (selectBaseCurrencyNetworkComboBox.getSelectionModel().getSelectedItem() != BaseCurrencyNetwork.CURRENT_VALUE) - selectNetwork(); - } - - private void selectNetwork() { - new Popup().warning(Res.get("settings.net.needRestart")) - .onAction(() -> { - bisqEnvironment.saveBaseCryptoNetwork(selectBaseCurrencyNetworkComboBox.getSelectionModel().getSelectedItem()); - UserThread.runAfter(BisqApp.getShutDownHandler(), 500, TimeUnit.MILLISECONDS); - }) - .actionButtonText(Res.get("shared.shutDown")) - .closeButtonText(Res.get("shared.cancel")) - .onClose(() -> selectBaseCurrencyNetworkComboBox.getSelectionModel().select(BaseCurrencyNetwork.CURRENT_VALUE)) - .show(); - }*/ /////////////////////////////////////////////////////////////////////////////////////////// // Deactivate @@ -995,6 +953,8 @@ private void deactivateDisplayPreferences() { if (displayStandbyModeFeature) { avoidStandbyMode.setOnAction(null); } + + autoConfirmXmr.setOnAction(null); } private void deactivateDaoPreferences() { diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 117dbb4cb53..84468dc86ab 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -293,6 +293,7 @@ message CounterCurrencyTransferStartedMessage { bytes buyer_signature = 4; string counter_currency_tx_id = 5; string uid = 6; + string counter_currency_extra_data = 7; } message FinalizePayoutTxRequest { @@ -1385,6 +1386,7 @@ message Trade { PubKeyRing refund_agent_pub_key_ring = 34; RefundResultState refund_result_state = 35; int64 last_refresh_request_date = 36; + string counter_currency_extra_data = 37; } message BuyerAsMakerTrade { @@ -1545,6 +1547,7 @@ message PreferencesPayload { int32 block_notify_port = 53; int32 css_theme = 54; bool tac_accepted_v120 = 55; + bool auto_confirm_xmr = 56; } /////////////////////////////////////////////////////////////////////////////////////////// From 78da1df9de071b165c1859859bb749c1b5e5ef97 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 27 Jul 2020 15:09:03 -0500 Subject: [PATCH 02/85] Add trade date --- .../java/bisq/core/trade/TradeManager.java | 1 + .../asset/xmr/XmrTransferProofRequester.java | 26 +++++++++++++++---- .../asset/xmr/XmrTransferProofService.java | 3 +++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index b0ea7bafec5..ee89b122166 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -939,6 +939,7 @@ public void processCounterCurrencyExtraData(Trade trade) { // 8.90259736 is dev test value long amount = (long) Float.parseFloat("8.90259736") * 100000000; // todo check XMR denomination xmrTransferProofService.requestProof(trade.getId(), + trade.getDate(), txHash, txKey, address, diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java index caa8b84c1d3..9b538195dd3 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java @@ -29,6 +29,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; +import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -43,6 +44,7 @@ class XmrTransferProofRequester { private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofService", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; + private final Date tradeDate; private final String txHash; private final String txKey; private final String recipientAddress; @@ -61,6 +63,7 @@ class XmrTransferProofRequester { /////////////////////////////////////////////////////////////////////////////////////////// XmrTransferProofRequester(XmrTxProofHttpClient httpClient, + Date tradeDate, String txHash, String txKey, String recipientAddress, @@ -68,6 +71,7 @@ class XmrTransferProofRequester { Consumer resultHandler, FaultHandler faultHandler) { this.httpClient = httpClient; + this.tradeDate = tradeDate; this.txHash = txHash; this.txKey = txKey; this.recipientAddress = recipientAddress; @@ -122,12 +126,24 @@ public void onFailure(@NotNull Throwable throwable) { } private XmrProofResult parseResult(String json) { - //TODO parse json - //TODO need service to check diff. error conditions + + // Avoid Codacy warning by using amount temporarily... + log.info("json " + json); + log.info("amount " + amount); + log.info("tradeDate " + tradeDate); + + // TODO parse json + // TODO need service to check diff. error conditions + // TODO check amount + + // TODO check if date of tx is after tradeDate (allow some tolerance to avoid clock sync issues). Otherwise a + // scammer could send an old txKey from a prev trade with same amount before seller has updated to new feature, + // thus the check for duplication would not detect the scam. + return XmrProofResult.PROOF_OK; // check recipientAddress and amount - // json example - /* + // json example (verify if that json is up to date before using it for dev) +/* { "data": { @@ -153,6 +169,6 @@ private XmrProofResult parseResult(String json) { "status": "success" } - */ +*/ } } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java index 177d6238f0c..473cf01583b 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java @@ -21,6 +21,7 @@ import javax.inject.Inject; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; @@ -44,6 +45,7 @@ public XmrTransferProofService(XmrTxProofHttpClient httpClient) { } public void requestProof(String tradeId, + Date tradeDate, String txHash, String txKey, String recipientAddress, @@ -56,6 +58,7 @@ public void requestProof(String tradeId, } XmrTransferProofRequester requester = new XmrTransferProofRequester(httpClient, + tradeDate, txHash, txKey, recipientAddress, From ca8f53c2c21e465ee18dd37e21a931d3fda0fa8b Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Sat, 1 Aug 2020 22:22:21 -0500 Subject: [PATCH 03/85] Implement XMR tx proof autoconfirm feature * XMR seller is prompted to enter txId and viewkey. * looks up the XMR transaction to verify that it has confirmed * user can run their own validating service, or use the ones provided * 100% agreement of all chosen services is necessary for confirmation * feature can be configured and turned on/off from settings screen * feature can be globally turned off via admin filter * two code review passes from chimp1984 * one text review from m52go --- .../asset/CryptoNoteAddressValidator.java | 17 +- .../main/java/bisq/core/filter/Filter.java | 20 +- .../core/trade/AutoConfirmationManager.java | 275 ++++++++++++++++++ core/src/main/java/bisq/core/trade/Trade.java | 20 +- .../java/bisq/core/trade/TradeManager.java | 145 +-------- .../core/trade/asset/xmr/XmrProofInfo.java | 170 +++++++++++ .../core/trade/asset/xmr/XmrProofResult.java | 86 +++++- .../asset/xmr/XmrProofResultWithTradeId.java | 31 -- .../asset/xmr/XmrTransferProofRequester.java | 145 ++++----- .../asset/xmr/XmrTransferProofService.java | 56 ++-- ...CounterCurrencyTransferStartedMessage.java | 2 +- .../core/trade/protocol/ProcessModel.java | 4 + ...CounterCurrencyTransferStartedMessage.java | 3 +- .../bisq/core/user/AutoConfirmSettings.java | 67 +++++ .../main/java/bisq/core/user/Preferences.java | 36 ++- .../bisq/core/user/PreferencesPayload.java | 15 +- .../resources/i18n/displayStrings.properties | 37 ++- .../trade/asset/xmr/XmrProofInfoTest.java | 153 ++++++++++ .../core/user/UserPayloadModelVOTest.java | 3 +- .../core/util/FeeReceiverSelectorTest.java | 2 +- desktop/src/main/java/bisq/desktop/bisq.css | 4 + .../main/java/bisq/desktop/main/MainView.java | 5 +- .../java/bisq/desktop/main/MainViewModel.java | 12 +- .../main/overlays/windows/FilterWindow.java | 5 +- .../overlays/windows/SetXmrTxKeyWindow.java | 26 +- .../steps/buyer/BuyerStep2View.java | 28 +- .../steps/buyer/BuyerStep4View.java | 20 +- .../steps/seller/SellerStep3View.java | 76 ++--- .../presentation/SettingsPresentation.java | 63 ++++ .../desktop/main/settings/SettingsView.java | 16 +- .../settings/preferences/PreferencesView.java | 130 +++++++-- proto/src/main/proto/pb.proto | 11 +- 32 files changed, 1256 insertions(+), 427 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/AutoConfirmationManager.java create mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java delete mode 100644 core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java create mode 100644 core/src/main/java/bisq/core/user/AutoConfirmSettings.java create mode 100644 core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java create mode 100644 desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java diff --git a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java index 6409e5a4f8e..603461dbb0d 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java @@ -17,11 +17,14 @@ package bisq.asset; +import org.bitcoinj.core.Utils; + import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.math.BigInteger; +import java.util.Arrays; import java.util.Map; /** @@ -57,6 +60,18 @@ public AddressValidationResult validate(String address) { return AddressValidationResult.invalidStructure(); } } + + public static String convertToRawHex(String address) { + try { + byte[] decoded = MoneroBase58.decode(address); + // omit the type (1st byte) and checksum (last 4 byte) + byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); + return Utils.HEX.encode(slice); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } } class Keccak { @@ -202,7 +217,7 @@ private static void decodeChunk(String input, } } - private static byte[] decode(String input) throws Exception { + public static byte[] decode(String input) throws Exception { if (input.length() == 0) { return new byte[0]; } diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index 0c2e39b0953..99b7b2526e9 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -109,6 +109,9 @@ public final class Filter implements ProtectedStoragePayload, ExpirablePayload { @Nullable private final List btcFeeReceiverAddresses; + // added after v1.3.7 + private final boolean disableAutoConf; + public Filter(List bannedOfferIds, List bannedNodeAddress, List bannedPaymentAccounts, @@ -125,7 +128,8 @@ public Filter(List bannedOfferIds, @Nullable List mediators, @Nullable List refundAgents, @Nullable List bannedSignerPubKeys, - @Nullable List btcFeeReceiverAddresses) { + @Nullable List btcFeeReceiverAddresses, + boolean disableAutoConf) { this.bannedOfferIds = bannedOfferIds; this.bannedNodeAddress = bannedNodeAddress; this.bannedPaymentAccounts = bannedPaymentAccounts; @@ -143,6 +147,7 @@ public Filter(List bannedOfferIds, this.refundAgents = refundAgents; this.bannedSignerPubKeys = bannedSignerPubKeys; this.btcFeeReceiverAddresses = btcFeeReceiverAddresses; + this.disableAutoConf = disableAutoConf; } @@ -170,7 +175,8 @@ public Filter(List bannedOfferIds, @Nullable List mediators, @Nullable List refundAgents, @Nullable List bannedSignerPubKeys, - @Nullable List btcFeeReceiverAddresses) { + @Nullable List btcFeeReceiverAddresses, + boolean disableAutoConf) { this(bannedOfferIds, bannedNodeAddress, bannedPaymentAccounts, @@ -187,7 +193,8 @@ public Filter(List bannedOfferIds, mediators, refundAgents, bannedSignerPubKeys, - btcFeeReceiverAddresses); + btcFeeReceiverAddresses, + disableAutoConf); this.signatureAsBase64 = signatureAsBase64; this.ownerPubKeyBytes = ownerPubKeyBytes; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); @@ -209,7 +216,8 @@ public protobuf.StoragePayload toProtoMessage() { .setSignatureAsBase64(signatureAsBase64) .setOwnerPubKeyBytes(ByteString.copyFrom(ownerPubKeyBytes)) .setPreventPublicBtcNetwork(preventPublicBtcNetwork) - .setDisableDao(disableDao); + .setDisableDao(disableDao) + .setDisableAutoConf(disableAutoConf); Optional.ofNullable(bannedCurrencies).ifPresent(builder::addAllBannedCurrencies); Optional.ofNullable(bannedPaymentMethods).ifPresent(builder::addAllBannedPaymentMethods); @@ -252,7 +260,8 @@ public static Filter fromProto(protobuf.Filter proto) { CollectionUtils.isEmpty(proto.getBannedSignerPubKeysList()) ? null : new ArrayList<>(proto.getBannedSignerPubKeysList()), CollectionUtils.isEmpty(proto.getBtcFeeReceiverAddressesList()) ? null : - new ArrayList<>(proto.getBtcFeeReceiverAddressesList())); + new ArrayList<>(proto.getBtcFeeReceiverAddressesList()), + proto.getDisableAutoConf()); } @@ -293,6 +302,7 @@ public String toString() { ",\n refundAgents=" + refundAgents + ",\n bannedSignerPubKeys=" + bannedSignerPubKeys + ",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses + + ",\n disableAutoConf=" + disableAutoConf + "\n}"; } } diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java new file mode 100644 index 00000000000..d9a9111b240 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java @@ -0,0 +1,275 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade; + +import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.filter.FilterManager; +import bisq.core.offer.Offer; +import bisq.core.payment.payload.AssetsAccountPayload; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.asset.xmr.XmrProofInfo; +import bisq.core.trade.asset.xmr.XmrProofResult; +import bisq.core.trade.asset.xmr.XmrTransferProofService; +import bisq.core.trade.closed.ClosedTradableManager; +import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.user.Preferences; +import bisq.core.btc.setup.WalletsSetup; + +import bisq.network.p2p.P2PService; + +import bisq.common.app.DevEnv; + +import org.bitcoinj.core.Coin; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import lombok.extern.slf4j.Slf4j; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +@Singleton +public class AutoConfirmationManager { + + private final FilterManager filterManager; + private final Preferences preferences; + private final XmrTransferProofService xmrTransferProofService; + private final AccountAgeWitnessService accountAgeWitnessService; + private final ClosedTradableManager closedTradableManager; + private final FailedTradesManager failedTradesManager; + private final P2PService p2PService; + private final WalletsSetup walletsSetup; + private Map txProofResultsPending = new HashMap<>(); + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + AutoConfirmationManager(FilterManager filterManager, + Preferences preferences, + XmrTransferProofService xmrTransferProofService, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, + P2PService p2PService, + WalletsSetup walletsSetup, + AccountAgeWitnessService accountAgeWitnessService + ) { + this.filterManager = filterManager; + this.preferences = preferences; + this.xmrTransferProofService = xmrTransferProofService; + this.closedTradableManager = closedTradableManager; + this.failedTradesManager = failedTradesManager; + this.p2PService = p2PService; + this.walletsSetup = walletsSetup; + this.accountAgeWitnessService = accountAgeWitnessService; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void processCounterCurrencyExtraData(Trade trade, Stream activeTrades) { + String counterCurrencyExtraData = trade.getCounterCurrencyExtraData(); + if (counterCurrencyExtraData == null || counterCurrencyExtraData.isEmpty()) { + return; + } + + String txHash = trade.getCounterCurrencyTxId(); + if (txHash == null || txHash.isEmpty()) { + return; + } + + Contract contract = checkNotNull(trade.getContract(), "Contract must not be null"); + PaymentAccountPayload sellersPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); + if (!(sellersPaymentAccountPayload instanceof AssetsAccountPayload)) { + return; + } + AssetsAccountPayload sellersAssetsAccountPayload = (AssetsAccountPayload) sellersPaymentAccountPayload; + + if (!(trade instanceof SellerTrade)) { + return; + } + + // Take the safe option and don't begin auto confirmation if the app has not reached a high enough level + // of operation. In that case it will be left for the user to confirm the trade manually which is fine. + if (!p2PService.isBootstrapped()) { + return; + } + if (!walletsSetup.hasSufficientPeersForBroadcast()) { + return; + } + if (!walletsSetup.isDownloadComplete()) { + return; + } + + Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); + if (offer.getCurrencyCode().equals("XMR")) { + String txKey = counterCurrencyExtraData; + + if (!txHash.matches("[a-fA-F0-9]{64}") || !txKey.matches("[a-fA-F0-9]{64}")) { + log.error("Validation failed: txHash {} txKey {}", txHash, txKey); + return; + } + + // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with + // the same user (same address) and same amount. We check only for the txKey as a same txHash but different + // txKey is not possible to get a valid result at proof. + Stream failedAndOpenTrades = Stream.concat(activeTrades, failedTradesManager.getFailedTrades().stream()); + Stream closedTrades = closedTradableManager.getClosedTradables().stream() + .filter(tradable -> tradable instanceof Trade) + .map(tradable -> (Trade) tradable); + Stream allTrades = Stream.concat(failedAndOpenTrades, closedTrades); + + boolean txKeyUsedAtAnyOpenTrade = allTrades + .filter(t -> !t.getId().equals(trade.getId())) // ignore same trade + .anyMatch(t -> { + String extra = t.getCounterCurrencyExtraData(); + if (extra == null) { + return false; + } + + boolean alreadyUsed = extra.equals(txKey); + if (alreadyUsed) { + String message = "Peer used the XMR tx key already at another trade with trade ID " + + t.getId() + ". This might be a scam attempt."; + trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.TX_KEY_REUSED, message)); + } + return alreadyUsed; + }); + + if (txKeyUsedAtAnyOpenTrade && !DevEnv.isDevMode()) { + return; + } + + if (!preferences.getAutoConfirmSettings().enabled || this.isAutoConfDisabledByFilter()) { + trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.FEATURE_DISABLED, null)); + return; + } + Coin tradeAmount = trade.getTradeAmount(); + Coin tradeLimit = Coin.valueOf(preferences.getAutoConfirmSettings().tradeLimit); + if (tradeAmount.isGreaterThan(tradeLimit)) { + trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.FEATURE_DISABLED, null)); + log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", + tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); + return; + } + + String address = sellersAssetsAccountPayload.getAddress(); + // XMR satoshis have 12 decimal places vs. bitcoin's 8 + long amountXmr = offer.getVolumeByAmount(tradeAmount).getValue() * 10000L; + int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; + trade.setXmrProofResult(new XmrProofResult(0, confirmsRequired, XmrProofResult.State.TX_NOT_FOUND)); + List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; + txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address + for (String serviceAddress : serviceAddresses) { + XmrProofInfo xmrProofInfo = new XmrProofInfo( + txHash, + txKey, + address, + amountXmr, + trade.getDate(), + confirmsRequired, + serviceAddress); + xmrTransferProofService.requestProof(xmrProofInfo, + result -> { + if (!handleProofResult(result, trade)) + xmrTransferProofService.terminateRequest(xmrProofInfo); + }, + (errorMsg, throwable) -> { + log.warn(errorMsg); + } + ); + } + } + } + + private boolean handleProofResult(XmrProofResult result, Trade trade) { + boolean success = true; + boolean failure = false; + + // here we count the Trade's API results from all + // different serviceAddress and figure out when all have finished + int resultsCountdown = txProofResultsPending.getOrDefault(trade.getId(), 0); + if (resultsCountdown < 0) { // see failure scenario below + log.info("Ignoring stale API result [{}], tradeId {} due to previous error", + result.getState(), trade.getShortId()); + return failure; // terminate any pending responses + } + + if (trade.isPayoutPublished()) { + log.warn("Trade payout already published, shutting down all open API requests for this trade {}", + trade.getShortId()); + txProofResultsPending.remove(trade.getId()); + return failure; + } + + if (result.isPendingState()) { + log.info("Auto confirm received a {} message for tradeId {}, retry will happen automatically", + result.getState(), trade.getShortId()); + trade.setXmrProofResult(result); // this updates the GUI with the status.. + // Repeating the requests is handled in XmrTransferProofRequester + return success; + } + + if (result.isSuccessState()) { + resultsCountdown -= 1; + log.info("Received a {} message, remaining proofs needed: {}, tradeId {}", + result.getState(), resultsCountdown, trade.getShortId()); + if (resultsCountdown > 0) { + txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count + return success; // not all APIs have confirmed yet + } + // we've received the final PROOF_OK, all good here. + txProofResultsPending.remove(trade.getId()); + trade.setXmrProofResult(result); // this updates the GUI with the status.. + log.info("Auto confirm was successful, transitioning trade {} to next step...", trade.getShortId()); + if (!trade.isPayoutPublished()) { + // note that this state can also be triggered by auto confirmation feature + trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); + } + accountAgeWitnessService.maybeSignWitness(trade); + // transition the trade to step 4: + ((SellerTrade) trade).onFiatPaymentReceived(() -> { }, + errorMessage -> { }); + return success; + } + + // error case. any validation error from XmrProofRequester or XmrProofInfo.check + // the following error codes will end up here: + // CONNECTION_FAIL, API_FAILURE, API_INVALID, TX_KEY_REUSED, TX_HASH_INVALID, + // TX_KEY_INVALID, ADDRESS_INVALID, AMOUNT_NOT_MATCHING, TRADE_DATE_NOT_MATCHING + log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", + result.getState(), trade.getShortId()); + trade.setXmrProofResult(result); // this updates the GUI with the status.. + resultsCountdown = -1; // signal all API requestors to cease + txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count + return failure; + } + + private boolean isAutoConfDisabledByFilter() { + return filterManager.getFilter() != null && + filterManager.getFilter().isDisableAutoConf(); + } +} diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index f4b2fc223c0..984e109c04b 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -38,6 +38,7 @@ import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; +import bisq.core.trade.asset.xmr.XmrProofResult; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; @@ -158,6 +159,7 @@ public enum State { SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG(Phase.FIAT_SENT), // #################### Phase FIAT_RECEIVED + // note that this state can also be triggered by auto confirmation feature SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT(Phase.FIAT_RECEIVED), // #################### Phase PAYOUT_PAID @@ -428,12 +430,24 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt private long refreshInterval; private static final long MAX_REFRESH_INTERVAL = 4 * ChronoUnit.HOURS.getDuration().toMillis(); - // Added in v1.3.7 + // Added after v1.3.7 // We use that for the XMR txKey but want to keep it generic to be flexible for other payment methods or assets. @Getter @Setter private String counterCurrencyExtraData; + // xmrProofResult is not persisted yet + @Getter + @Nullable + private transient XmrProofResult xmrProofResult; + + public void setXmrProofResult(XmrProofResult xmrProofResult) { + this.xmrProofResult = xmrProofResult; + xmrProofResultProperty.setValue(xmrProofResult); + } + @Getter + // This observable property can be used for UI to show a notification to user of the XMR proof status + transient final private ObjectProperty xmrProofResultProperty = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization @@ -609,6 +623,7 @@ public void init(P2PService p2PService, User user, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, + AutoConfirmationManager autoConfirmationManager, TradeStatisticsManager tradeStatisticsManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -628,6 +643,7 @@ public void init(P2PService p2PService, user, filterManager, accountAgeWitnessService, + autoConfirmationManager, tradeStatisticsManager, arbitratorManager, mediatorManager, @@ -1170,6 +1186,8 @@ public String toString() { ",\n takerPaymentAccountId='" + takerPaymentAccountId + '\'' + ",\n errorMessage='" + errorMessage + '\'' + ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' + + ",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' + + ",\n xmrProofResult='" + xmrProofResult + '\'' + ",\n chatMessages=" + chatMessages + ",\n txFee=" + txFee + ",\n takerFee=" + takerFee + diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index ee89b122166..3a7cd125b30 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -21,7 +21,6 @@ import bisq.core.btc.exceptions.AddressEntryException; import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.model.AddressEntry; -import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.wallet.BsqWalletService; import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.TradeWalletService; @@ -35,15 +34,10 @@ import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; import bisq.core.offer.availability.OfferAvailabilityModel; -import bisq.core.payment.payload.AssetsAccountPayload; -import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.provider.price.PriceFeedService; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.asset.xmr.XmrProofResult; -import bisq.core.trade.asset.xmr.XmrProofResultWithTradeId; -import bisq.core.trade.asset.xmr.XmrTransferProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; @@ -52,7 +46,6 @@ import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.core.user.Preferences; import bisq.core.user.User; import bisq.core.util.Validator; @@ -86,10 +79,8 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty; -import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleLongProperty; -import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; @@ -117,8 +108,6 @@ import javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkNotNull; - public class TradeManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(TradeManager.class); @@ -137,6 +126,7 @@ public class TradeManager implements PersistedDataHost { private final TradeStatisticsManager tradeStatisticsManager; private final ReferralIdService referralIdService; private final AccountAgeWitnessService accountAgeWitnessService; + private final AutoConfirmationManager autoConfirmationManager; private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; @@ -155,14 +145,8 @@ public class TradeManager implements PersistedDataHost { @Getter private final ObservableList tradesWithoutDepositTx = FXCollections.observableArrayList(); private final DumpDelayedPayoutTx dumpDelayedPayoutTx; - private final XmrTransferProofService xmrTransferProofService; - private final WalletsSetup walletsSetup; - private final Preferences preferences; @Getter private final boolean allowFaultyDelayedTxs; - // This observable property can be used for UI to show a notification to user in case a XMR txKey was reused. - @Getter - private final ObjectProperty proofResultWithTradeIdProperty = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -184,6 +168,7 @@ public TradeManager(User user, TradeStatisticsManager tradeStatisticsManager, ReferralIdService referralIdService, AccountAgeWitnessService accountAgeWitnessService, + AutoConfirmationManager autoConfirmationManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, @@ -191,9 +176,6 @@ public TradeManager(User user, ClockWatcher clockWatcher, Storage> storage, DumpDelayedPayoutTx dumpDelayedPayoutTx, - XmrTransferProofService xmrTransferProofService, - WalletsSetup walletsSetup, - Preferences preferences, @Named(Config.ALLOW_FAULTY_DELAYED_TXS) boolean allowFaultyDelayedTxs) { this.user = user; this.keyRing = keyRing; @@ -209,15 +191,13 @@ public TradeManager(User user, this.tradeStatisticsManager = tradeStatisticsManager; this.referralIdService = referralIdService; this.accountAgeWitnessService = accountAgeWitnessService; + this.autoConfirmationManager = autoConfirmationManager; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; this.daoFacade = daoFacade; this.clockWatcher = clockWatcher; this.dumpDelayedPayoutTx = dumpDelayedPayoutTx; - this.xmrTransferProofService = xmrTransferProofService; - this.walletsSetup = walletsSetup; - this.preferences = preferences; this.allowFaultyDelayedTxs = allowFaultyDelayedTxs; tradableListStorage = storage; @@ -456,6 +436,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo user, filterManager, accountAgeWitnessService, + autoConfirmationManager, tradeStatisticsManager, arbitratorManager, mediatorManager, @@ -875,122 +856,4 @@ else if (now.after(halfTradePeriodDate)) public void persistTrades() { tradableList.persist(); } - - public void processCounterCurrencyExtraData(Trade trade) { - String counterCurrencyExtraData = trade.getCounterCurrencyExtraData(); - if (counterCurrencyExtraData == null || counterCurrencyExtraData.isEmpty()) { - return; - } - - String txHash = trade.getCounterCurrencyTxId(); - if (txHash == null || txHash.isEmpty()) { - return; - } - - Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); - PaymentAccountPayload sellersPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); - if (!(sellersPaymentAccountPayload instanceof AssetsAccountPayload)) { - return; - } - AssetsAccountPayload sellersAssetsAccountPayload = (AssetsAccountPayload) sellersPaymentAccountPayload; - - if (!(trade instanceof SellerTrade)) { - return; - } - - Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); - if (offer.getCurrencyCode().equals("XMR")) { - String txKey = counterCurrencyExtraData; - - // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with - // the same user (same address) and same amount. We check only for the txKey as a same txHash but different - // txKey is not possible to get a valid result at proof. - Stream failedAndOpenTrades = Stream.concat(tradableList.stream(), failedTradesManager.getFailedTrades().stream()); - Stream closedTrades = closedTradableManager.getClosedTradables().stream() - .filter(tradable -> tradable instanceof Trade) - .map(tradable -> (Trade) tradable); - Stream allTrades = Stream.concat(failedAndOpenTrades, closedTrades); - boolean txKeyUsedAtAnyOpenTrade = allTrades - .filter(t -> !t.getId().equals(trade.getId())) // ignore same trade - .anyMatch(t -> { - String extra = t.getCounterCurrencyExtraData(); - if (extra == null) { - return false; - } - - boolean alreadyUsed = extra.equals(txKey); - if (alreadyUsed) { - String message = "Peer used the XMR tx key already at another trade with trade ID " + - t.getId() + ". This might be a scam attempt."; - log.warn(message); - proofResultWithTradeIdProperty.set(new XmrProofResultWithTradeId(XmrProofResult.TX_KEY_REUSED, trade.getId())); - } - return alreadyUsed; - }); - - if (txKeyUsedAtAnyOpenTrade) { - return; - } - - if (preferences.isAutoConfirmXmr()) { - String address = sellersAssetsAccountPayload.getAddress(); - //TODO for dev testing - address = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub"; - // 8.90259736 is dev test value - long amount = (long) Float.parseFloat("8.90259736") * 100000000; // todo check XMR denomination - xmrTransferProofService.requestProof(trade.getId(), - trade.getDate(), - txHash, - txKey, - address, - amount, - result -> { - switch (result) { - case TX_NOT_CONFIRMED: - // Repeating the requests is handled in XmrTransferProofRequester - break; - case PROOF_OK: - if (!p2PService.isBootstrapped()) { - return; - } - - if (!walletsSetup.hasSufficientPeersForBroadcast()) { - return; - } - - if (!walletsSetup.isDownloadComplete()) { - return; - } - - if (!trade.isPayoutPublished()) { - trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); - } - - accountAgeWitnessService.maybeSignWitness(trade); - - ((SellerTrade) trade).onFiatPaymentReceived(() -> { - }, errorMessage -> { - }); - break; - case UNKNOWN_ERROR: - case TX_KEY_REUSED: - case TX_NEVER_FOUND: - case TX_HASH_INVALID: - case TX_KEY_INVALID: - case ADDRESS_INVALID: - case AMOUNT_NOT_MATCHING: - case PROOF_FAILED: - default: - log.error("Case not handled. " + result); - break; - } - - proofResultWithTradeIdProperty.set(new XmrProofResultWithTradeId(result, trade.getId())); - }, - (errorMsg, throwable) -> { - log.warn(errorMsg); - }); - } - } - } } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java new file mode 100644 index 00000000000..06c0ac58d29 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java @@ -0,0 +1,170 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.asset.xmr; + +import bisq.asset.CryptoNoteAddressValidator; + +import bisq.common.app.DevEnv; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import java.util.Date; + +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Value +public class XmrProofInfo { + private final String txHash; + private final String txKey; + private final String recipientAddress; + private final long amount; + private final Date tradeDate; + private final int confirmsRequired; + private final String serviceAddress; + + public XmrProofInfo( + String txHash, + String txKey, + String recipientAddress, + long amount, + Date tradeDate, + int confirmsRequired, + String serviceAddress) { + this.txHash = txHash; + this.txKey = txKey; + this.recipientAddress = recipientAddress; + this.amount = amount; + this.tradeDate = tradeDate; + this.confirmsRequired = confirmsRequired; + this.serviceAddress = serviceAddress; + } + + // something to uniquely identify this object by + public String getKey() { + return txHash + "|" + serviceAddress; + } + + public XmrProofResult checkApiResponse(String jsonTxt) { + try { + JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); + if (json == null) + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Empty json"); + // there should always be "data" and "status" at the top level + if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing data / status fields"); + JsonObject jsonData = json.get("data").getAsJsonObject(); + String jsonStatus = json.get("status").getAsString(); + if (jsonStatus.matches("fail")) { + // the API returns "fail" until the transaction has successfully reached the mempool. + // we return TX_NOT_FOUND which will cause a retry later + return new XmrProofResult(XmrProofResult.State.TX_NOT_FOUND, null); + } else if (!jsonStatus.matches("success")) { + return new XmrProofResult(XmrProofResult.State.API_FAILURE, "Unhandled status value"); + } + + // validate that the address matches + JsonElement jsonAddress = jsonData.get("address"); + if (jsonAddress == null) { + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing address field"); + } else { + String expectedAddressHex = CryptoNoteAddressValidator.convertToRawHex(this.recipientAddress); + if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { + log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); + return new XmrProofResult(XmrProofResult.State.ADDRESS_INVALID, null); + } + } + + // validate that the txhash matches + JsonElement jsonTxHash = jsonData.get("tx_hash"); + if (jsonTxHash == null) { + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing tx_hash field"); + } else { + if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { + log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); + return new XmrProofResult(XmrProofResult.State.TX_HASH_INVALID, null); + } + } + + // validate that the txkey matches + JsonElement jsonViewkey = jsonData.get("viewkey"); + if (jsonViewkey == null) { + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing viewkey field"); + } else { + if (!jsonViewkey.getAsString().equalsIgnoreCase(this.txKey)) { + log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), txKey); + return new XmrProofResult(XmrProofResult.State.TX_KEY_INVALID, null); + } + } + + // validate that the txDate matches within tolerance + // (except that in dev mode we let this check pass anyway) + JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); + if (jsonTimestamp == null) { + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing tx_timestamp field"); + } else { + long tradeDateSeconds = tradeDate.getTime() / 1000; + long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); + if (difference > 60 * 60 * 24 && !DevEnv.isDevMode()) { // accept up to 1 day difference + log.warn("tx_timestamp {}, tradeDate: {}, difference {}", + jsonTimestamp.getAsLong(), tradeDateSeconds, difference); + return new XmrProofResult(XmrProofResult.State.TRADE_DATE_NOT_MATCHING, null); + } + } + + // calculate how many confirms are still needed + int confirmations = 0; + JsonElement jsonConfs = jsonData.get("tx_confirmations"); + if (jsonConfs == null) { + return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing tx_confirmations field"); + } else { + confirmations = jsonConfs.getAsInt(); + log.info("Confirmations: {}, xmr txid: {}", confirmations, txHash); + } + + // iterate through the list of outputs, one of them has to match the amount we are trying to verify. + // check that the "match" field is true as well as validating the amount value + // (except that in dev mode we allow any amount as valid) + JsonArray jsonOutputs = jsonData.get("outputs").getAsJsonArray(); + for (int i = 0; i < jsonOutputs.size(); i++) { + JsonObject out = jsonOutputs.get(i).getAsJsonObject(); + if (out.get("match").getAsBoolean()) { + long jsonAmount = out.get("amount").getAsLong(); + if (jsonAmount == amount || DevEnv.isDevMode()) { // any amount ok in dev mode + if (confirmations < confirmsRequired) + // we return TX_NOT_CONFIRMED which will cause a retry later + return new XmrProofResult(confirmations, confirmsRequired, XmrProofResult.State.TX_NOT_CONFIRMED); + else + return new XmrProofResult(confirmations, confirmsRequired, XmrProofResult.State.PROOF_OK); + } + } + } + + // reaching this point means there was no matching amount + return new XmrProofResult(XmrProofResult.State.AMOUNT_NOT_MATCHING, null); + + } catch (JsonParseException | NullPointerException e) { + return new XmrProofResult(XmrProofResult.State.API_INVALID, e.toString()); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java index d431826d36e..1487ff7cc73 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java @@ -17,15 +17,79 @@ package bisq.core.trade.asset.xmr; -public enum XmrProofResult { - TX_NOT_CONFIRMED, - PROOF_OK, - UNKNOWN_ERROR, - TX_KEY_REUSED, - TX_NEVER_FOUND, - TX_HASH_INVALID, - TX_KEY_INVALID, - ADDRESS_INVALID, - AMOUNT_NOT_MATCHING, - PROOF_FAILED +import bisq.core.locale.Res; + +import javax.annotation.Nullable; + +import lombok.Value; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Value +public class XmrProofResult { + public enum State { + FEATURE_DISABLED, + TX_NOT_FOUND, + TX_NOT_CONFIRMED, + PROOF_OK, + CONNECTION_FAIL, + API_FAILURE, + API_INVALID, + TX_KEY_REUSED, + TX_HASH_INVALID, + TX_KEY_INVALID, + ADDRESS_INVALID, + AMOUNT_NOT_MATCHING, + TRADE_DATE_NOT_MATCHING; + } + + private final int confirmCount; + private final int confirmsRequired; + private final State state; + + public XmrProofResult(int confirmCount, int confirmsRequired, State state) { + this.confirmCount = confirmCount; + this.confirmsRequired = confirmsRequired; + this.state = state; + } + + // alternate constructor for error scenarios + public XmrProofResult(State state, @Nullable String errorMsg) { + this.confirmCount = 0; + this.confirmsRequired = 0; + this.state = state; + if (isErrorState()) + log.error(errorMsg != null ? errorMsg : state.toString()); + } + + public String getTextStatus() { + switch (state) { + case TX_NOT_CONFIRMED: + return Res.get("portfolio.pending.autoConfirmPending") + + " " + confirmCount + + "/" + confirmsRequired; + case TX_NOT_FOUND: + return Res.get("portfolio.pending.autoConfirmTxNotFound"); + case FEATURE_DISABLED: + return Res.get("portfolio.pending.autoConfirmDisabled"); + case PROOF_OK: + return Res.get("portfolio.pending.autoConfirmSuccess"); + default: + // any other statuses we display the enum name + return this.state.toString(); + } + } + + public boolean isPendingState() { + return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); + } + + public boolean isSuccessState() { + return (state == State.PROOF_OK); + } + + public boolean isErrorState() { + return (!isPendingState() && !isSuccessState()); + } + } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java deleted file mode 100644 index 8e01a189f4d..00000000000 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResultWithTradeId.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.asset.xmr; - -import lombok.Value; - -@Value -public class XmrProofResultWithTradeId { - private final XmrProofResult xmrProofResult; - private final String tradeId; - - public XmrProofResultWithTradeId(XmrProofResult xmrProofResult, String tradeId) { - this.xmrProofResult = xmrProofResult; - this.tradeId = tradeId; - } -} diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java index 9b538195dd3..1f104740da9 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java @@ -17,19 +17,18 @@ package bisq.core.trade.asset.xmr; +import bisq.network.Socks5ProxyProvider; + import bisq.common.UserThread; import bisq.common.app.Version; import bisq.common.handlers.FaultHandler; import bisq.common.util.Utilities; -import javax.inject.Singleton; - import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -38,23 +37,18 @@ import org.jetbrains.annotations.NotNull; @Slf4j -@Singleton -class XmrTransferProofRequester { +public class XmrTransferProofRequester { private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( - "XmrTransferProofService", 3, 5, 10 * 60); + "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; - private final Date tradeDate; - private final String txHash; - private final String txKey; - private final String recipientAddress; - private final long amount; + private final XmrProofInfo xmrProofInfo; private final Consumer resultHandler; private final FaultHandler faultHandler; - + private boolean terminated; private long firstRequest; - //todo dev settings - private long REPEAT_REQUEST_SEC = TimeUnit.SECONDS.toMillis(5); + // these settings are not likely to change and therefore not put into Config + private long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); private long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); @@ -62,113 +56,74 @@ class XmrTransferProofRequester { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTransferProofRequester(XmrTxProofHttpClient httpClient, - Date tradeDate, - String txHash, - String txKey, - String recipientAddress, - long amount, + XmrTransferProofRequester(Socks5ProxyProvider socks5ProxyProvider, + XmrProofInfo xmrProofInfo, Consumer resultHandler, FaultHandler faultHandler) { - this.httpClient = httpClient; - this.tradeDate = tradeDate; - this.txHash = txHash; - this.txKey = txKey; - this.recipientAddress = recipientAddress; - this.amount = amount; + this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); + this.httpClient.setBaseUrl("http://" + xmrProofInfo.getServiceAddress()); + if (xmrProofInfo.getServiceAddress().matches("^192.*|^localhost.*")) { + log.info("Ignoring Socks5 proxy for local net address: {}", xmrProofInfo.getServiceAddress()); + this.httpClient.setIgnoreSocks5Proxy(true); + } + this.xmrProofInfo = xmrProofInfo; this.resultHandler = resultHandler; this.faultHandler = faultHandler; + this.terminated = false; firstRequest = System.currentTimeMillis(); } - /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// - public void request() { - // todo dev test address for a real tx proof - /* - txID: 5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802 - txKey: f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906 - address: 85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub - ammount : 8.90259736 XMR - */ + // used by the service to abort further automatic retries + public void stop() { + terminated = true; + } + public void request() { + if (terminated) { + // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls + // this scenario may happen if a re-request is scheduled from the callback below + log.info("Request() aborted, this object has been terminated: {}", httpClient.toString()); + return; + } ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("XmrTransferProofRequest-" + this.toString()); - String param = "/api/outputs?txhash=" + txHash + - "&address=" + recipientAddress + - "&viewkey=" + txKey + + Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getKey()); + String param = "/api/outputs?txhash=" + xmrProofInfo.getTxHash() + + "&address=" + xmrProofInfo.getRecipientAddress() + + "&viewkey=" + xmrProofInfo.getTxKey() + "&txprove=1"; + log.info(httpClient.toString()); + log.info(param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - Thread.sleep(3000); - - // - - return parseResult(json); + log.info(json); + return xmrProofInfo.checkApiResponse(json); }); Futures.addCallback(future, new FutureCallback<>() { public void onSuccess(XmrProofResult result) { - if (result == XmrProofResult.TX_NOT_CONFIRMED && System.currentTimeMillis() - firstRequest < MAX_REQUEST_PERIOD) { - UserThread.runAfter(() -> request(), REPEAT_REQUEST_SEC); - } else { - UserThread.execute(() -> resultHandler.accept(result)); + if (terminated) { + log.info("API terminated from higher level: {}", httpClient.toString()); + return; + } + if (System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD) { + log.warn("We have tried this service API for too long, giving up: {}", httpClient.toString()); + return; + } + if (result.isPendingState()) { + UserThread.runAfter(() -> request(), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); } + UserThread.execute(() -> resultHandler.accept(result)); } public void onFailure(@NotNull Throwable throwable) { String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; faultHandler.handleFault(errorMessage, throwable); + UserThread.execute(() -> resultHandler.accept( + new XmrProofResult(XmrProofResult.State.CONNECTION_FAIL, errorMessage))); } }); } - - private XmrProofResult parseResult(String json) { - - // Avoid Codacy warning by using amount temporarily... - log.info("json " + json); - log.info("amount " + amount); - log.info("tradeDate " + tradeDate); - - // TODO parse json - // TODO need service to check diff. error conditions - // TODO check amount - - // TODO check if date of tx is after tradeDate (allow some tolerance to avoid clock sync issues). Otherwise a - // scammer could send an old txKey from a prev trade with same amount before seller has updated to new feature, - // thus the check for duplication would not detect the scam. - - return XmrProofResult.PROOF_OK; - // check recipientAddress and amount - // json example (verify if that json is up to date before using it for dev) -/* - -{ - "data": { - "address": "42f18fc61586554095b0799b5c4b6f00cdeb26a93b20540d366932c6001617b75db35109fbba7d5f275fef4b9c49e0cc1c84b219ec6ff652fda54f89f7f63c88", - "outputs": [ - { - "amount": 34980000000000, - "match": true, - "output_idx": 0, - "output_pubkey": "35d7200229e725c2bce0da3a2f20ef0720d242ecf88bfcb71eff2025c2501fdb" - }, - { - "amount": 0, - "match": false, - "output_idx": 1, - "output_pubkey": "44efccab9f9b42e83c12da7988785d6c4eb3ec6e7aa2ae1234e2f0f7cb9ed6dd" - } - ], - "tx_hash": "17049bc5f2d9fbca1ce8dae443bbbbed2fc02f1ee003ffdd0571996905faa831", - "tx_prove": false, - "viewkey": "f359631075708155cc3d92a32b75a7d02a5dcf27756707b47a2b31b21c389501" - }, - "status": "success" -} - -*/ - } } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java index 473cf01583b..854a7d7bc0a 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java @@ -17,65 +17,69 @@ package bisq.core.trade.asset.xmr; +import bisq.network.Socks5ProxyProvider; + import bisq.common.handlers.FaultHandler; import javax.inject.Inject; -import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + /** * Manages the XMR transfers proof requests for multiple trades. */ @Slf4j public class XmrTransferProofService { - private final XmrTxProofHttpClient httpClient; private Map map = new HashMap<>(); + private Socks5ProxyProvider socks5ProxyProvider; @Inject - public XmrTransferProofService(XmrTxProofHttpClient httpClient) { - this.httpClient = httpClient; - //this.httpClient.setBaseUrl("http://139.59.140.37:8081"); - this.httpClient.setBaseUrl("http://127.0.0.1:8081"); - this.httpClient.setIgnoreSocks5Proxy(false); + public XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { + socks5ProxyProvider = provider; } - public void requestProof(String tradeId, - Date tradeDate, - String txHash, - String txKey, - String recipientAddress, - long amount, + public void requestProof(XmrProofInfo xmrProofInfo, Consumer resultHandler, FaultHandler faultHandler) { - if (map.containsKey(tradeId)) { - log.warn("We started a proof request for trade with ID {} already", tradeId); + String key = xmrProofInfo.getKey(); + if (map.containsKey(key)) { + log.warn("We started a proof request for trade with ID {} already", key); return; } + log.info("requesting tx proof for " + key); - XmrTransferProofRequester requester = new XmrTransferProofRequester(httpClient, - tradeDate, - txHash, - txKey, - recipientAddress, - amount, + XmrTransferProofRequester requester = new XmrTransferProofRequester( + socks5ProxyProvider, + xmrProofInfo, result -> { - cleanup(tradeId); + if (result.isSuccessState()) + cleanup(key); resultHandler.accept(result); }, (errorMsg, throwable) -> { - cleanup(tradeId); + cleanup(key); faultHandler.handleFault(errorMsg, throwable); }); - map.put(tradeId, requester); + map.put(key, requester); requester.request(); } - private void cleanup(String tradeId) { - map.remove(tradeId); + public void terminateRequest(XmrProofInfo xmrProofInfo) { + String key = xmrProofInfo.getKey(); + XmrTransferProofRequester requester = map.getOrDefault(key, null); + if (requester != null) { + log.info("Terminating API request for {}", key); + requester.stop(); + cleanup(key); + } + } + private void cleanup(String identifier) { + map.remove(identifier); } } diff --git a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java index b25bcd0ebe2..416c7d74c22 100644 --- a/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/messages/CounterCurrencyTransferStartedMessage.java @@ -42,7 +42,7 @@ public final class CounterCurrencyTransferStartedMessage extends TradeMessage im @Nullable private final String counterCurrencyTxId; - // Added in v1.3.7 + // Added after v1.3.7 // We use that for the XMR txKey but want to keep it generic to be flexible for data of other payment methods or assets. @Nullable private String counterCurrencyExtraData; diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index 27317d7569c..ed590b307c5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -33,6 +33,7 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; +import bisq.core.trade.AutoConfirmationManager; import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; @@ -89,6 +90,7 @@ public class ProcessModel implements Model, PersistablePayload { transient private User user; transient private FilterManager filterManager; transient private AccountAgeWitnessService accountAgeWitnessService; + transient private AutoConfirmationManager autoConfirmationManager; transient private TradeStatisticsManager tradeStatisticsManager; transient private ArbitratorManager arbitratorManager; transient private MediatorManager mediatorManager; @@ -245,6 +247,7 @@ public void onAllServicesInitialized(Offer offer, User user, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, + AutoConfirmationManager autoConfirmationManager, TradeStatisticsManager tradeStatisticsManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -263,6 +266,7 @@ public void onAllServicesInitialized(Offer offer, this.user = user; this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; + this.autoConfirmationManager = autoConfirmationManager; this.tradeStatisticsManager = tradeStatisticsManager; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 2469774c254..ba8ba8b8f77 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,7 +58,8 @@ protected void run() { String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getTradeManager().processCounterCurrencyExtraData(trade); + processModel.getAutoConfirmationManager().processCounterCurrencyExtraData( + trade, processModel.getTradeManager().getTradableList().stream()); } processModel.removeMailboxMessageAfterProcessing(trade); diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java new file mode 100644 index 00000000000..b2420c47714 --- /dev/null +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -0,0 +1,67 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.user; + +import bisq.common.proto.persistable.PersistablePayload; + +import com.google.protobuf.Message; + +import java.util.ArrayList; +import java.util.List; + +public final class AutoConfirmSettings implements PersistablePayload { + public final boolean enabled; + public final int requiredConfirmations; + public final long tradeLimit; + public final List serviceAddresses; + public final String currencyCode; + + public AutoConfirmSettings(boolean enabled, + int requiredConfirmations, + long tradeLimit, + List serviceAddresses, + String currencyCode) { + this.enabled = enabled; + this.requiredConfirmations = requiredConfirmations; + this.tradeLimit = tradeLimit; + this.serviceAddresses = serviceAddresses; + this.currencyCode = currencyCode; + } + + @Override + public Message toProtoMessage() { + return protobuf.AutoConfirmSettings.newBuilder() + .setEnabled(enabled) + .setRequiredConfirmations(requiredConfirmations) + .setTradeLimit(tradeLimit) + .addAllServiceAddresses(serviceAddresses) + .setCurrencyCode(currencyCode) + .build(); + } + + public static AutoConfirmSettings fromProto(protobuf.AutoConfirmSettings proto) { + List serviceAddresses = proto.getServiceAddressesList().isEmpty() ? + new ArrayList<>() : new ArrayList<>(proto.getServiceAddressesList()); + return new AutoConfirmSettings( + proto.getEnabled(), + proto.getRequiredConfirmations(), + proto.getTradeLimit(), + serviceAddresses, + proto.getCurrencyCode()); + } +} diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 89e9fd2fd36..1e9f44496d5 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -39,6 +39,8 @@ import bisq.common.storage.Storage; import bisq.common.util.Utilities; +import org.bitcoinj.core.Coin; + import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -120,6 +122,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid new BlockChainExplorer("bsq.bisq.cc (@m52go)", "https://bsq.bisq.cc/tx.html?tx=", "https://bsq.bisq.cc/Address.html?addr=") )); + // list of XMR proof providers : this list will be used if no preference has been set + public static final List DEFAULT_XMR_PROOF_PROVIDERS = new ArrayList<> (Arrays.asList( + "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); + public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; @@ -394,11 +400,35 @@ public void setTacAcceptedV120(boolean tacAccepted) { persist(); } - public void setAutoConfirmXmr(boolean autoConfirmXmr) { - prefPayload.setAutoConfirmXmr(autoConfirmXmr); + // AutoConfirmSettings is currently only used for one coin: XMR. Although it could + // potentially in the future be used for others too. In the interest of flexibility + // we store it as a list in the proto definition, but in practical terms the + // application is not coded to handle more than one entry. For now this API + // to get/set AutoConfirmSettings is the gatekeeper. If in the future we adapt + // the application to manage more than one altcoin AutoConfirmSettings then + // this API will need to incorporate lookup by coin. + public AutoConfirmSettings getAutoConfirmSettings() { + if (prefPayload.getAutoConfirmSettingsList().size() == 0) { + // default values for AutoConfirmSettings when persisted payload is empty: + prefPayload.getAutoConfirmSettingsList().add(new AutoConfirmSettings( + false, 5, Coin.valueOf(10000000).value, DEFAULT_XMR_PROOF_PROVIDERS, "XMR")); + } + return prefPayload.getAutoConfirmSettingsList().get(0); + } + + public void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings) { + // see above comment regarding only one entry in this list currently + prefPayload.getAutoConfirmSettingsList().clear(); + prefPayload.getAutoConfirmSettingsList().add(autoConfirmSettings); persist(); } + public void setAutoConfServiceAddresses(List serviceAddresses) { + AutoConfirmSettings x = this.getAutoConfirmSettings(); + this.setAutoConfirmSettings(new AutoConfirmSettings( + x.enabled, x.requiredConfirmations, x.tradeLimit, serviceAddresses, x.currencyCode)); + } + private void persist() { if (initialReadDone) storage.queueUpForSave(prefPayload); @@ -971,6 +1001,6 @@ private interface ExcludesDelegateMethods { void setTacAcceptedV120(boolean tacAccepted); - void setAutoConfirmXmr(boolean autoConfirmXmr); + void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings); } } diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 6fc38377b5a..9894612f7bc 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -127,8 +127,8 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnve private int blockNotifyPort; private boolean tacAcceptedV120; - // Added with 1.3.7 false be default - private boolean autoConfirmXmr; + // Added after 1.3.7 + private List autoConfirmSettingsList = new ArrayList<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -190,7 +190,10 @@ public Message toProtoMessage() { .setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto) .setBlockNotifyPort(blockNotifyPort) .setTacAcceptedV120(tacAcceptedV120) - .setAutoConfirmXmr(autoConfirmXmr); + .addAllAutoConfirmSettings(autoConfirmSettingsList.stream() + .map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage())) + .collect(Collectors.toList())); + Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage())); Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); @@ -279,6 +282,10 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co proto.getBuyerSecurityDepositAsPercentForCrypto(), proto.getBlockNotifyPort(), proto.getTacAcceptedV120(), - proto.getAutoConfirmXmr()); + proto.getAutoConfirmSettingsList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getAutoConfirmSettingsList().stream() + .map(AutoConfirmSettings::fromProto) + .collect(Collectors.toList())) + ); } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 2dd4b1a1c47..b23d2ef9982 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -569,6 +569,11 @@ portfolio.pending.step2_buyer.startPayment=Start payment portfolio.pending.step2_seller.waitPaymentStarted=Wait until payment has started portfolio.pending.step3_buyer.waitPaymentArrived=Wait until payment arrived portfolio.pending.step3_seller.confirmPaymentReceived=Confirm payment received +portfolio.pending.step3_seller.autoConfirmStatus=Auto-confirm status +portfolio.pending.autoConfirmTxNotFound=Transaction not found +portfolio.pending.autoConfirmPending=Pending +portfolio.pending.autoConfirmDisabled=Disabled +portfolio.pending.autoConfirmSuccess=Auto-Confirmed portfolio.pending.step5.completed=Completed portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for at least one blockchain confirmation before starting the payment. @@ -637,7 +642,11 @@ portfolio.pending.step2_buyer.fasterPaymentsHolderNameInfo=Some banks might veri portfolio.pending.step2_buyer.confirmStart.headline=Confirm that you have started the payment portfolio.pending.step2_buyer.confirmStart.msg=Did you initiate the {0} payment to your trading partner? portfolio.pending.step2_buyer.confirmStart.yes=Yes, I have started the payment - +portfolio.pending.step2_buyer.confirmStart.warningTitle=You have not provided proof of payment +portfolio.pending.step2_buyer.confirmStart.warning=You have not entered the transaction ID and the transaction key.\n\n\ + By not providing this data the peer cannot use the auto confirm feature to release the BTC as soon the XMR has been received.\n\ + Beside that, Bisq requires that the sender of the XMR transaction is able to provide this information to the mediator or arbitrator in case of a dispute. +portfolio.pending.step2_buyer.confirmStart.warningButton=Ignore and continue anyway portfolio.pending.step2_seller.waitPayment.headline=Wait for payment portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information portfolio.pending.step2_seller.waitPayment.msg=The deposit transaction has at least one blockchain confirmation.\nYou need to wait until the BTC buyer starts the {0} payment. @@ -718,9 +727,8 @@ portfolio.pending.step3_seller.amountToReceive=Amount to receive portfolio.pending.step3_seller.yourAddress=Your {0} address portfolio.pending.step3_seller.buyersAddress=Buyers {0} address portfolio.pending.step3_seller.yourAccount=Your trading account -portfolio.pending.step3_seller.xmrTxHash=Tx hash -portfolio.pending.step3_seller.xmrTxKey=Tx private key -portfolio.pending.step3_seller.xmrTxVerificationError=The XMR transfer validation for trade with ID ''{0}'' failed.\nReason: ''{1}'' +portfolio.pending.step3_seller.xmrTxHash=Transaction ID +portfolio.pending.step3_seller.xmrTxKey=Transaction key portfolio.pending.step3_seller.buyersAccount=Buyers trading account portfolio.pending.step3_seller.confirmReceipt=Confirm payment receipt portfolio.pending.step3_seller.buyerStartedPayment=The BTC buyer has started the {0} payment.\n{1} @@ -1054,7 +1062,11 @@ setting.preferences.explorer=Bitcoin block explorer setting.preferences.explorer.bsq=BSQ block explorer setting.preferences.deviation=Max. deviation from market price setting.preferences.avoidStandbyMode=Avoid standby mode -setting.preferences.autoConfirmXMR=Use XMR tx proof tool +setting.preferences.autoConfirmXMR=XMR auto-confirm +setting.preferences.autoConfirmEnabled=Enabled +setting.preferences.autoConfirmRequiredConfirmations=Required confirmations +setting.preferences.autoConfirmMaxTradeSize=Max trade size (BTC) +setting.preferences.autoConfirmServiceAddresses=Service addresses setting.preferences.deviationToLarge=Values higher than {0}% are not allowed. setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte) setting.preferences.useCustomValue=Use custom value @@ -1232,7 +1244,15 @@ setting.about.shortcuts.sendFilter=Set Filter (privileged activity) setting.about.shortcuts.sendPrivateNotification=Send private notification to peer (privileged activity) setting.about.shortcuts.sendPrivateNotification.value=Open peer info at avatar or dispute and press: {0} - +setting.info.headline=New XMR auto-confirm Feature +setting.info.msg=When selling BTC for XMR you can use the auto-confirm feature to verify that the correct amount of \ + XMR was sent to your wallet so that Bisq can automatically mark the trade as complete, making trades quicker for everyone.\n\n\ + Auto-confirm checks the XMR transaction on at least 2 XMR explorer nodes using the private transaction key provided \ + by the XMR sender. By default, Bisq uses explorer nodes run by Bisq contributors, but we recommend running your \ + own XMR explorer node for maximum privacy and security.\n\n\ + You can also set the maximum amount of BTC per trade to auto-confirm as well as the number of required \ + confirmations here in Settings.\n\n\ + See more details (including how to set up your own explorer node) on the Bisq wiki: [.....] #################################################################### # Account #################################################################### @@ -2432,6 +2452,7 @@ filterWindow.priceRelayNode=Filtered price relay nodes (comma sep. onion address filterWindow.btcNode=Filtered Bitcoin nodes (comma sep. addresses + port) filterWindow.preventPublicBtcNetwork=Prevent usage of public Bitcoin network filterWindow.disableDao=Disable DAO +filterWindow.disableAutoConf=Disable auto-confirmation (altcoins) filterWindow.disableDaoBelowVersion=Min. version required for DAO filterWindow.disableTradeBelowVersion=Min. version required for trading filterWindow.add=Add filter @@ -2493,8 +2514,8 @@ showWalletDataWindow.walletData=Wallet data showWalletDataWindow.includePrivKeys=Include private keys setXMRTxKeyWindow.headline=Prove sending of XMR -setXMRTxKeyWindow.txHash=Transaction hash -setXMRTxKeyWindow.txKey=Tx private key +setXMRTxKeyWindow.txHash=Transaction ID +setXMRTxKeyWindow.txKey=Transaction key # We do not translate the tac because of the legal nature. We would need translations checked by lawyers # in each language which is too expensive atm. diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java new file mode 100644 index 00000000000..cf71971846b --- /dev/null +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java @@ -0,0 +1,153 @@ +package bisq.core.trade.asset.xmr; + +import java.time.Instant; + +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class XmrProofInfoTest { + private XmrProofInfo xmrProofInfo; + private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae"; + private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; + private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f"; + private String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF"; + + @Before + public void prepareMocksAndObjects() { + + long amount = 100000000000L; + Date tradeDate = Date.from(Instant.now()); + int confirmsRequired = 10; + String serviceAddress = "127.0.0.1:8081"; + + xmrProofInfo = new XmrProofInfo( + txHash, + txKey, + recipientAddress, + amount, + tradeDate, + confirmsRequired, + serviceAddress); + } + + @Test + public void testKey() { + assertTrue(xmrProofInfo.getKey().contains(xmrProofInfo.getTxHash())); + assertTrue(xmrProofInfo.getKey().contains(xmrProofInfo.getServiceAddress())); + assertFalse(xmrProofInfo.getKey().contains(xmrProofInfo.getRecipientAddress())); + } + + @Test + public void testJsonRoot() { + // checking what happens when bad input is provided + assertTrue(xmrProofInfo.checkApiResponse( + "invalid json data").getState() == XmrProofResult.State.API_INVALID); + assertTrue(xmrProofInfo.checkApiResponse( + "").getState() == XmrProofResult.State.API_INVALID); + assertTrue(xmrProofInfo.checkApiResponse( + "[]").getState() == XmrProofResult.State.API_INVALID); + assertTrue(xmrProofInfo.checkApiResponse( + "{}").getState() == XmrProofResult.State.API_INVALID); + } + + @Test + public void testJsonTopLevel() { + // testing the top level fields: data and status + assertTrue(xmrProofInfo.checkApiResponse( + "{'data':{'title':''},'status':'fail'}" ) + .getState() == XmrProofResult.State.TX_NOT_FOUND); + assertTrue(xmrProofInfo.checkApiResponse( + "{'data':{'title':''},'missingstatus':'success'}" ) + .getState() == XmrProofResult.State.API_INVALID); + assertTrue(xmrProofInfo.checkApiResponse( + "{'missingdata':{'title':''},'status':'success'}" ) + .getState() == XmrProofResult.State.API_INVALID); + } + + @Test + public void testJsonAddress() { + assertTrue(xmrProofInfo.checkApiResponse( + "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) + .getState() == XmrProofResult.State.API_INVALID); + assertTrue(xmrProofInfo.checkApiResponse( + "{'data':{'address':'e957dac7'},'status':'success'}" ) + .getState() == XmrProofResult.State.ADDRESS_INVALID); + } + + @Test + public void testJsonTxHash() { + String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; + assertTrue(xmrProofInfo.checkApiResponse(missing_tx_hash).getState() + == XmrProofResult.State.API_INVALID); + + String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; + assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_hash).getState() + == XmrProofResult.State.TX_HASH_INVALID); + } + + @Test + public void testJsonTxKey() { + String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; + assertTrue(xmrProofInfo.checkApiResponse(missing_tx_key).getState() + == XmrProofResult.State.API_INVALID); + + String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "', " + + "'viewkey':'cdce04'}, 'status':'success'}"; + assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_key).getState() + == XmrProofResult.State.TX_KEY_INVALID); + } + + @Test + public void testJsonTxTimestamp() { + String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "'," + + "'viewkey':'" + txKey + "'}, 'status':'success'}"; + assertTrue(xmrProofInfo.checkApiResponse(missing_tx_timestamp).getState() + == XmrProofResult.State.API_INVALID); + + String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "', " + + "'viewkey':'" + txKey + "'," + + "'tx_timestamp':'12345'}, 'status':'success'}"; + assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_timestamp).getState() + == XmrProofResult.State.TRADE_DATE_NOT_MATCHING); + } + + @Test + public void testJsonTxConfirmation() { + long epochDate = Instant.now().toEpochMilli() / 1000; + String outputs = "'outputs':[" + + "{'amount':100000000000,'match':true,'output_idx':0,'output_pubkey':'972a2c9178876f1fae4ecd22f9d7c132a12706db8ffb5d1f223f9aa8ced75b61'}," + + "{'amount':0,'match':false,'output_idx':1,'output_pubkey':'658330d2d56c74aca3b40900c56cd0f0111e2876be677ade493d06d539a1bab0'}],"; + String json = "{'status':'success', 'data':{" + + "'address':'" + recipientAddressHex + "', " + + outputs + + "'tx_confirmations':777, " + + "'tx_hash':'" + txHash + "', " + + "'viewkey':'" + txKey + "', " + + "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + + "}"; + assertTrue(xmrProofInfo.checkApiResponse(json).getState() + == XmrProofResult.State.PROOF_OK); + json = json.replaceFirst("777", "0"); + assertTrue(xmrProofInfo.checkApiResponse(json).getState() + == XmrProofResult.State.TX_NOT_CONFIRMED); + json = json.replaceFirst("100000000000", "100000000001"); + assertTrue(xmrProofInfo.checkApiResponse(json).getState() + == XmrProofResult.State.AMOUNT_NOT_MATCHING); + } + + @Test + public void testJsonFail() { + String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; + assertTrue(xmrProofInfo.checkApiResponse(failedJson).getState() + == XmrProofResult.State.API_INVALID); + } +} diff --git a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java index 67d09bb6586..d64576bf5d8 100644 --- a/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java +++ b/core/src/test/java/bisq/core/user/UserPayloadModelVOTest.java @@ -59,7 +59,8 @@ public void testRoundtripFull() { Lists.newArrayList(), Lists.newArrayList(), Lists.newArrayList(), - Lists.newArrayList())); + Lists.newArrayList(), + false)); vo.setRegisteredArbitrator(ArbitratorTest.getArbitratorMock()); vo.setRegisteredMediator(MediatorTest.getMediatorMock()); vo.setAcceptedArbitrators(Lists.newArrayList(ArbitratorTest.getArbitratorMock())); diff --git a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java index c340c36ecdd..455e6cda19f 100644 --- a/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java +++ b/core/src/test/java/bisq/core/util/FeeReceiverSelectorTest.java @@ -102,6 +102,6 @@ private static Filter filterWithReceivers(List btcFeeReceiverAddresses) null, null, null, null, false, null, false, null, null, null, null, null, - btcFeeReceiverAddresses); + btcFeeReceiverAddresses, false); } } diff --git a/desktop/src/main/java/bisq/desktop/bisq.css b/desktop/src/main/java/bisq/desktop/bisq.css index dc2e9ed7c86..e9ab97167cf 100644 --- a/desktop/src/main/java/bisq/desktop/bisq.css +++ b/desktop/src/main/java/bisq/desktop/bisq.css @@ -436,6 +436,10 @@ tree-table-view:focused { -fx-pref-width: 30; } +.jfx-badge.autoconf .badge-pane { + -fx-pref-width: 100; +} + .jfx-badge .badge-pane .label { -fx-font-weight: bold; -fx-font-size: 0.692em; diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java index de1084a2fb4..d172c7d7358 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainView.java +++ b/desktop/src/main/java/bisq/desktop/main/MainView.java @@ -193,6 +193,8 @@ protected void initialize() { daoButtonWithBadge.getStyleClass().add("new"); JFXBadge accountButtonWithBadge = new JFXBadge(accountButton); accountButtonWithBadge.getStyleClass().add("new"); + JFXBadge settingsButtonWithBadge = new JFXBadge(settingsButton); + settingsButtonWithBadge.getStyleClass().add("new"); Locale locale = GlobalSettings.getLocale(); DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getNumberInstance(locale); @@ -323,7 +325,7 @@ protected Tooltip computeValue() { primaryNav.getStyleClass().add("nav-primary"); HBox.setHgrow(primaryNav, Priority.SOMETIMES); - HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButton, + HBox secondaryNav = new HBox(supportButtonWithBadge, getNavigationSpacer(), settingsButtonWithBadge, getNavigationSpacer(), accountButtonWithBadge, getNavigationSpacer(), daoButtonWithBadge); secondaryNav.getStyleClass().add("nav-secondary"); HBox.setHgrow(secondaryNav, Priority.SOMETIMES); @@ -369,6 +371,7 @@ protected Tooltip computeValue() { setupBadge(supportButtonWithBadge, model.getNumOpenSupportTickets(), model.getShowOpenSupportTicketsNotification()); setupBadge(daoButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowDaoUpdatesNotification()); setupBadge(accountButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowAccountUpdatesNotification()); + setupBadge(settingsButtonWithBadge, new SimpleStringProperty(Res.get("shared.new")), model.getShowSettingsUpdatesNotification()); navigation.addListener(viewPath -> { if (viewPath.size() != 2 || viewPath.indexOf(MainView.class) != 0) diff --git a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java index da37762ac3a..6bda1d4126c 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainViewModel.java +++ b/desktop/src/main/java/bisq/desktop/main/MainViewModel.java @@ -31,6 +31,7 @@ import bisq.desktop.main.overlays.windows.WalletPasswordWindow; import bisq.desktop.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow; import bisq.desktop.main.presentation.AccountPresentation; +import bisq.desktop.main.presentation.SettingsPresentation; import bisq.desktop.main.presentation.DaoPresentation; import bisq.desktop.main.presentation.MarketPricePresentation; import bisq.desktop.main.shared.PriceFeedComboBoxItem; @@ -108,6 +109,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener { private final MarketPricePresentation marketPricePresentation; private final DaoPresentation daoPresentation; private final AccountPresentation accountPresentation; + private final SettingsPresentation settingsPresentation; private final P2PService p2PService; private final TradeManager tradeManager; @Getter @@ -150,7 +152,9 @@ public MainViewModel(BisqSetup bisqSetup, SupportTicketsPresentation supportTicketsPresentation, MarketPricePresentation marketPricePresentation, DaoPresentation daoPresentation, - AccountPresentation accountPresentation, P2PService p2PService, + AccountPresentation accountPresentation, + SettingsPresentation settingsPresentation, + P2PService p2PService, TradeManager tradeManager, Preferences preferences, PrivateNotificationManager privateNotificationManager, @@ -173,6 +177,7 @@ public MainViewModel(BisqSetup bisqSetup, this.marketPricePresentation = marketPricePresentation; this.daoPresentation = daoPresentation; this.accountPresentation = accountPresentation; + this.settingsPresentation = settingsPresentation; this.p2PService = p2PService; this.tradeManager = tradeManager; this.preferences = preferences; @@ -249,6 +254,7 @@ public void onSetupComplete() { marketPricePresentation.setup(); daoPresentation.setup(); accountPresentation.setup(); + settingsPresentation.setup(); if (DevEnv.isDevMode()) { preferences.setShowOwnOffersInOfferBook(true); @@ -658,6 +664,10 @@ public BooleanProperty getShowAccountUpdatesNotification() { return accountPresentation.getShowAccountUpdatesNotification(); } + public BooleanProperty getShowSettingsUpdatesNotification() { + return settingsPresentation.getShowSettingsUpdatesNotification(); + } + private void maybeAddNewTradeProtocolLaunchWindowToQueue() { String newTradeProtocolWithAccountSigningLaunchPopupKey = "newTradeProtocolWithAccountSigningLaunchPopup"; if (DontShowAgainLookup.showAgain(newTradeProtocolWithAccountSigningLaunchPopupKey)) { diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java index 50dbf04f182..d1e9872daca 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/FilterWindow.java @@ -136,6 +136,7 @@ private void addContent() { InputTextField btcNodesInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.btcNode")); CheckBox preventPublicBtcNetworkCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.preventPublicBtcNetwork")); CheckBox disableDaoCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.disableDao")); + CheckBox disableAutoConfCheckBox = addLabelCheckBox(gridPane, ++rowIndex, Res.get("filterWindow.disableAutoConf")); InputTextField disableDaoBelowVersionInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.disableDaoBelowVersion")); InputTextField disableTradeBelowVersionInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("filterWindow.disableTradeBelowVersion")); @@ -157,6 +158,7 @@ private void addContent() { preventPublicBtcNetworkCheckBox.setSelected(filter.isPreventPublicBtcNetwork()); disableDaoCheckBox.setSelected(filter.isDisableDao()); + disableAutoConfCheckBox.setSelected(filter.isDisableAutoConf()); disableDaoBelowVersionInputTextField.setText(filter.getDisableDaoBelowVersion()); disableTradeBelowVersionInputTextField.setText(filter.getDisableTradeBelowVersion()); } @@ -180,7 +182,8 @@ private void addContent() { readAsList(mediatorsInputTextField), readAsList(refundAgentsInputTextField), readAsList(bannedSignerPubKeysInputTextField), - readAsList(btcFeeReceiverAddressesInputTextField) + readAsList(btcFeeReceiverAddressesInputTextField), + disableAutoConfCheckBox.isSelected() ), keyInputTextField.getText()) ) diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index 8b68eea6c18..f9668638e97 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -19,11 +19,10 @@ import bisq.desktop.components.InputTextField; import bisq.desktop.main.overlays.Overlay; +import bisq.desktop.util.validation.RegexValidator; import bisq.core.locale.Res; -import bisq.common.UserThread; - import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; @@ -31,12 +30,10 @@ import javafx.geometry.HPos; import javafx.geometry.Insets; -import java.util.concurrent.TimeUnit; - import javax.annotation.Nullable; +import static bisq.common.app.DevEnv.isDevMode; import static bisq.desktop.util.FormBuilder.addInputTextField; -import static javafx.beans.binding.Bindings.createBooleanBinding; public class SetXmrTxKeyWindow extends Overlay { @@ -56,9 +53,16 @@ public void show() { addContent(); addButtons(); - actionButton.disableProperty().bind(createBooleanBinding(() -> - txHashInputTextField.getText().isEmpty() || txKeyInputTextField.getText().isEmpty(), - txHashInputTextField.textProperty(), txKeyInputTextField.textProperty())); + RegexValidator regexValidator = new RegexValidator(); + regexValidator.setPattern("[a-fA-F0-9]{64}"); + regexValidator.setErrorMessage("Input must be a 32 byte hexadeximal number"); + txHashInputTextField.setValidator(regexValidator); + txKeyInputTextField.setValidator(regexValidator); + if (isDevMode()) { + // pre-populate the fields with test data when in dev mode + txHashInputTextField.setText("e8dcd8160aee016d8a0d9c480355d65773dc577313a0af8237c35f9d997b01c0"); + txKeyInputTextField.setText("300fa18ff99b32ff097d75c64d62732bdb486af8c225f558ee48c5f777f9b509"); + } applyStyles(); display(); @@ -91,11 +95,5 @@ public String getTxKey() { private void addContent() { txHashInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txHash"), 10); txKeyInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txKey")); - - UserThread.runAfter(() -> { - //todo: remove dev test data - txHashInputTextField.setText("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802"); - txKeyInputTextField.setText("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906"); - }, 200, TimeUnit.MILLISECONDS); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 9bd9bbaa248..69a5b17160f 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -53,6 +53,7 @@ import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.desktop.util.DisplayUtils; import bisq.desktop.util.Layout; +import bisq.desktop.util.Transitions; import bisq.core.locale.CurrencyUtil; import bisq.core.locale.Res; @@ -89,6 +90,7 @@ import org.fxmisc.easybind.Subscription; import java.util.List; +import java.util.concurrent.TimeUnit; import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabel; import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; @@ -458,13 +460,19 @@ private void onPaymentStarted() { Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); if (offer.getCurrencyCode().equals("XMR")) { SetXmrTxKeyWindow setXmrTxKeyWindow = new SetXmrTxKeyWindow(); - setXmrTxKeyWindow.actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) + setXmrTxKeyWindow + .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) .onAction(() -> { String txKey = setXmrTxKeyWindow.getTxKey(); String txHash = setXmrTxKeyWindow.getTxHash(); - trade.setCounterCurrencyExtraData(txKey); - trade.setCounterCurrencyTxId(txHash); - showConfirmPaymentStartedPopup(); + if (txKey.length() == 64 && txHash.length() == 64) { + trade.setCounterCurrencyExtraData(txKey); + trade.setCounterCurrencyTxId(txHash); + showConfirmPaymentStartedPopup(); + } else { + UserThread.runAfter(() -> + showProofWarningPopup(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + } }) .closeButtonText(Res.get("shared.cancel")) .onClose(setXmrTxKeyWindow::hide) @@ -475,6 +483,18 @@ private void onPaymentStarted() { } } + private void showProofWarningPopup() { + Popup popup = new Popup(); + popup.headLine(Res.get("portfolio.pending.step2_buyer.confirmStart.warningTitle")) + .confirmation(Res.get("portfolio.pending.step2_buyer.confirmStart.warning")) + .width(700) + .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.warningButton")) + .onAction(this::showConfirmPaymentStartedPopup) + .closeButtonText(Res.get("shared.cancel")) + .onClose(popup::hide) + .show(); + } + private void showConfirmPaymentStartedPopup() { String key = "confirmPaymentStarted"; if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 1f3a05ed6e9..57fbcb49776 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -51,6 +51,8 @@ import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; +import com.jfoenix.controls.JFXBadge; + import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; @@ -58,6 +60,7 @@ import javafx.scene.layout.Priority; import javafx.geometry.Insets; +import javafx.geometry.Pos; import org.spongycastle.crypto.params.KeyParameter; @@ -103,9 +106,22 @@ public void deactivate() { protected void addContent() { gridPane.getColumnConstraints().get(1).setHgrow(Priority.SOMETIMES); - addTitledGroupBg(gridPane, gridRow, 5, Res.get("portfolio.pending.step5_buyer.groupTitle"), 0); - addCompactTopLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.TWICE_FIRST_ROW_DISTANCE); + TitledGroupBg completedTradeLabel = new TitledGroupBg(); + completedTradeLabel.setText(Res.get("portfolio.pending.step5_buyer.groupTitle")); + + JFXBadge autoConfBadge = new JFXBadge(new Label(""), Pos.BASELINE_RIGHT); + autoConfBadge.setText(Res.get("portfolio.pending.autoConfirmSuccess")); + autoConfBadge.getStyleClass().add("autoconf"); + HBox hBox2 = new HBox(1, completedTradeLabel, autoConfBadge); + GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); + gridPane.getChildren().add(hBox2); + GridPane.setRowSpan(hBox2, 5); + if (trade.getXmrProofResult() == null || !trade.getXmrProofResult().isSuccessState()) { + autoConfBadge.setVisible(false); + } + + addCompactTopLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.TWICE_FIRST_ROW_DISTANCE); addCompactTopLabelTextField(gridPane, ++gridRow, getFiatTradeAmountLabel(), model.getFiatVolume()); addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.refunded"), model.getSecurityDeposit()); addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.tradeFee"), model.getTradeFee()); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index c2da474a49f..29a9402ffd5 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -42,7 +42,7 @@ import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.asset.xmr.XmrProofResultWithTradeId; +import bisq.core.trade.asset.xmr.XmrProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -76,8 +76,8 @@ public class SellerStep3View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; - private final ChangeListener xmrProofResultWithTradeIdListener; - + private TextFieldWithCopyIcon autoConfirmStatusField; + private final ChangeListener xmrProofResultListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -86,8 +86,9 @@ public class SellerStep3View extends TradeStepView { public SellerStep3View(PendingTradesViewModel model) { super(model); - xmrProofResultWithTradeIdListener = (observable, oldValue, newValue) -> { - processXmrProofResult(newValue); + // we listen for updates on the trade xmrProofResult field + xmrProofResultListener = (observable, oldValue, newValue) -> { + autoConfirmStatusField.setText(newValue.getTextStatus()); }; } @@ -149,8 +150,15 @@ public void activate() { } }); - model.dataModel.tradeManager.getProofResultWithTradeIdProperty().addListener(xmrProofResultWithTradeIdListener); - processXmrProofResult(model.dataModel.tradeManager.getProofResultWithTradeIdProperty().get()); + // we listen for updates on the trade xmrProofResult field + if (autoConfirmStatusField != null) { + trade.getXmrProofResultProperty().addListener(xmrProofResultListener); + // display the initial value, or FEATURE_DISABLED if there is none + XmrProofResult xmrProofResult = trade.getXmrProofResult(); + if (xmrProofResult == null) + xmrProofResult = new XmrProofResult(0, 0, XmrProofResult.State.FEATURE_DISABLED); + autoConfirmStatusField.setText(xmrProofResult.getTextStatus()); + } } @Override @@ -167,8 +175,7 @@ public void deactivate() { if (timeoutTimer != null) timeoutTimer.stop(); - model.dataModel.tradeManager.getProofResultWithTradeIdProperty().removeListener(xmrProofResultWithTradeIdListener); - + trade.getXmrProofResultProperty().removeListener(xmrProofResultListener); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -221,6 +228,12 @@ protected void addContent() { GridPane.setRowSpan(titledGroupBg, 4); } + if (isBlockChain && trade.getOffer().getCurrencyCode().equalsIgnoreCase("XMR")) { + autoConfirmStatusField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, + Res.get("portfolio.pending.step3_seller.autoConfirmStatus"), + "", Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE).second; + } + TextFieldWithCopyIcon myPaymentDetailsTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow, 0, myTitle, myPaymentDetails).second; myPaymentDetailsTextField.setMouseTransparent(false); @@ -393,7 +406,7 @@ else if (paymentAccountPayload instanceof F2FAccountPayload) } private void confirmPaymentReceived() { - // confirmButton.setDisable(true); + log.info("User pressed the [Confirm payment receipt] button for Trade {}", trade.getShortId()); busyAnimation.play(); statusLabel.setText(Res.get("shared.sendingConfirmation")); if (!trade.isPayoutPublished()) @@ -435,47 +448,4 @@ else if (paymentAccountPayload instanceof SepaInstantAccountPayload) protected void deactivatePaymentButtons(boolean isDisabled) { confirmButton.setDisable(isDisabled); } - - private void processXmrProofResult(XmrProofResultWithTradeId result) { - if (result == null) { - return; - } - - Trade trade = model.dataModel.getTrade(); - if (trade == null) { - return; - } - - if (result.getTradeId().equals(trade.getId())) { - boolean hasFailed; - switch (result.getXmrProofResult()) { - case TX_NOT_CONFIRMED: - case PROOF_OK: - hasFailed = false; - break; - case UNKNOWN_ERROR: - case TX_KEY_REUSED: - case TX_NEVER_FOUND: - case TX_HASH_INVALID: - case TX_KEY_INVALID: - case ADDRESS_INVALID: - case AMOUNT_NOT_MATCHING: - case PROOF_FAILED: - default: - hasFailed = true; - break; - } - - if (hasFailed) { - // We don't show yet translated messages for the diff. error cases but the ENUM name. - new Popup().warning(Res.get("portfolio.pending.step3_seller.xmrTxVerificationError", - result.getTradeId(), - result.getXmrProofResult().toString())) - .width(800) - .show(); - } - } - } } - - diff --git a/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java b/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java new file mode 100644 index 00000000000..73508b05386 --- /dev/null +++ b/desktop/src/main/java/bisq/desktop/main/presentation/SettingsPresentation.java @@ -0,0 +1,63 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.desktop.main.presentation; + +import bisq.core.user.Preferences; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; + +import javafx.collections.MapChangeListener; + + +@Singleton +public class SettingsPresentation { + + public static final String SETTINGS_NEWS = "settingsNews"; + + private Preferences preferences; + + private final SimpleBooleanProperty showNotification = new SimpleBooleanProperty(false); + + @Inject + public SettingsPresentation(Preferences preferences) { + + this.preferences = preferences; + + preferences.getDontShowAgainMapAsObservable().addListener((MapChangeListener) change -> { + if (change.getKey().equals(SETTINGS_NEWS)) { + showNotification.set(!change.wasAdded()); + } + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public + /////////////////////////////////////////////////////////////////////////////////////////// + + public BooleanProperty getShowSettingsUpdatesNotification() { + return showNotification; + } + + public void setup() { + showNotification.set(preferences.showAgain(SETTINGS_NEWS)); + } +} diff --git a/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java b/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java index 523b74dc5be..119566e467b 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/SettingsView.java @@ -24,11 +24,14 @@ import bisq.desktop.common.view.View; import bisq.desktop.common.view.ViewLoader; import bisq.desktop.main.MainView; +import bisq.desktop.main.overlays.popups.Popup; +import bisq.desktop.main.presentation.SettingsPresentation; import bisq.desktop.main.settings.about.AboutView; import bisq.desktop.main.settings.network.NetworkSettingsView; import bisq.desktop.main.settings.preferences.PreferencesView; import bisq.core.locale.Res; +import bisq.core.user.Preferences; import javax.inject.Inject; @@ -46,13 +49,15 @@ public class SettingsView extends ActivatableView { Tab preferencesTab, networkTab, aboutTab; private final ViewLoader viewLoader; private final Navigation navigation; + private Preferences preferences; private Navigation.Listener navigationListener; private ChangeListener tabChangeListener; @Inject - public SettingsView(CachingViewLoader viewLoader, Navigation navigation) { + public SettingsView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) { this.viewLoader = viewLoader; this.navigation = navigation; + this.preferences = preferences; } @Override @@ -82,6 +87,15 @@ else if (newValue == aboutTab) @Override protected void activate() { + // Hide new badge if user saw this section + preferences.dontShowAgain(SettingsPresentation.SETTINGS_NEWS, true); + String key = "autoConfirmInfo"; + new Popup() + .headLine(Res.get("setting.info.headline")) + .backgroundInfo(Res.get("setting.info.msg")) + .dontShowAgainId(key) + .show(); + root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener); navigation.addListener(navigationListener); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 40904fd3512..bfc34192660 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -44,10 +44,12 @@ import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.provider.fee.FeeService; +import bisq.core.user.AutoConfirmSettings; import bisq.core.user.BlockChainExplorer; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; import bisq.core.util.ParsingUtils; +import bisq.core.util.coin.CoinFormatter; import bisq.core.util.validation.IntegerValidator; import bisq.common.UserThread; @@ -93,6 +95,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import static bisq.desktop.util.FormBuilder.*; @@ -100,6 +103,7 @@ @FxmlView public class PreferencesView extends ActivatableViewAndModel { + private final CoinFormatter formatter; private ComboBox blockChainExplorerComboBox; private ComboBox bsqBlockChainExplorerComboBox; private ComboBox userLanguageComboBox; @@ -109,14 +113,16 @@ public class PreferencesView extends ActivatableViewAndModel transactionFeeFocusedListener; + private ChangeListener autoConfFocusOutListener; private final Preferences preferences; private final FeeService feeService; //private final ReferralIdService referralIdService; @@ -141,7 +147,8 @@ public class PreferencesView extends ActivatableViewAndModel tradeCurrencies; private InputTextField deviationInputTextField; private ChangeListener deviationListener, ignoreTradersListListener, ignoreDustThresholdListener, - /*referralIdListener,*/ rpcUserListener, rpcPwListener, blockNotifyPortListener; + /*referralIdListener,*/ rpcUserListener, rpcPwListener, blockNotifyPortListener, + autoConfRequiredConfirmationsListener, autoConfTradeLimitListener, autoConfServiceAddressListener; private ChangeListener deviationFocusedListener; private ChangeListener useCustomFeeCheckboxListener; private ChangeListener transactionFeeChangeListener; @@ -160,11 +167,13 @@ public PreferencesView(PreferencesViewModel model, FilterManager filterManager, DaoFacade daoFacade, Config config, + @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, @Named(Config.RPC_USER) String rpcUser, @Named(Config.RPC_PASSWORD) String rpcPassword, @Named(Config.RPC_BLOCK_NOTIFICATION_PORT) int rpcBlockNotificationPort, @Named(Config.STORAGE_DIR) File storageDir) { super(model); + this.formatter = formatter; this.preferences = preferences; this.feeService = feeService; this.assetService = assetService; @@ -192,11 +201,12 @@ public void initialize() { allFiatCurrencies.removeAll(fiatCurrencies); initializeGeneralOptions(); - initializeSeparator(); - initializeDisplayCurrencies(); initializeDisplayOptions(); if (DevEnv.isDaoActivated()) initializeDaoOptions(); + initializeSeparator(); + initializeAutoConfirmOptions(); + initializeDisplayCurrencies(); } @@ -210,6 +220,7 @@ protected void activate() { activateGeneralOptions(); activateDisplayCurrencies(); activateDisplayPreferences(); + activateAutoConfirmPreferences(); if (DevEnv.isDaoActivated()) activateDaoPreferences(); } @@ -219,6 +230,7 @@ protected void deactivate() { deactivateGeneralOptions(); deactivateDisplayCurrencies(); deactivateDisplayPreferences(); + deactivateAutoConfirmPreferences(); if (DevEnv.isDaoActivated()) deactivateDaoPreferences(); } @@ -228,7 +240,7 @@ protected void deactivate() { /////////////////////////////////////////////////////////////////////////////////////////// private void initializeGeneralOptions() { - int titledGroupBgRowSpan = displayStandbyModeFeature ? 10 : 9; + int titledGroupBgRowSpan = displayStandbyModeFeature ? 9 : 8; TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general")); GridPane.setColumnSpan(titledGroupBg, 1); @@ -363,8 +375,6 @@ private void initializeGeneralOptions() { } }; - autoConfirmXmr = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.autoConfirmXmr")); - if (displayStandbyModeFeature) { // AvoidStandbyModeService feature works only on OSX & Windows avoidStandbyMode = addSlideToggleButton(root, ++gridRow, @@ -383,17 +393,15 @@ private void initializeSeparator() { } private void initializeDisplayCurrencies() { - int displayCurrenciesGridRowIndex = 0; - TitledGroupBg titledGroupBg = addTitledGroupBg(root, displayCurrenciesGridRowIndex, 9, - Res.get("setting.preferences.currenciesInList")); + TitledGroupBg titledGroupBg = addTitledGroupBg(root, displayCurrenciesGridRowIndex, 8, + Res.get("setting.preferences.currenciesInList"), Layout.GROUP_DISTANCE); GridPane.setColumnIndex(titledGroupBg, 2); GridPane.setColumnSpan(titledGroupBg, 2); - preferredTradeCurrencyComboBox = addComboBox(root, displayCurrenciesGridRowIndex++, Res.get("setting.preferences.prefCurrency"), - Layout.FIRST_ROW_DISTANCE); + Layout.FIRST_ROW_AND_GROUP_DISTANCE); GridPane.setColumnIndex(preferredTradeCurrencyComboBox, 2); preferredTradeCurrencyComboBox.setConverter(new StringConverter<>() { @@ -584,6 +592,8 @@ public CryptoCurrency fromString(String s) { return null; } }); + + displayCurrenciesGridRowIndex += listRowSpan; } private void initializeDisplayOptions() { @@ -603,7 +613,7 @@ private void initializeDisplayOptions() { } private void initializeDaoOptions() { - daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 2, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE); + daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 3, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE); resyncDaoFromResourcesButton = addButton(root, gridRow, Res.get("setting.preferences.dao.resyncFromResources.label"), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE); resyncDaoFromResourcesButton.setMaxWidth(Double.MAX_VALUE); GridPane.setHgrow(resyncDaoFromResourcesButton, Priority.ALWAYS); @@ -640,6 +650,29 @@ private void initializeDaoOptions() { }; } + private void initializeAutoConfirmOptions() { + GridPane subGrid = new GridPane(); + GridPane.setHgrow(subGrid, Priority.ALWAYS); + root.add(subGrid, 2, displayCurrenciesGridRowIndex, 2, 10); + addTitledGroupBg(subGrid, 0, 4, Res.get("setting.preferences.autoConfirmXMR"), 0); + int localRowIndex = 0; + autoConfirmXmr = addSlideToggleButton(subGrid, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); + autoConfRequiredConfirmations = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); + autoConfTradeLimit = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); + autoConfServiceAddress = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmServiceAddresses")); + GridPane.setHgrow(autoConfServiceAddress, Priority.ALWAYS); + displayCurrenciesGridRowIndex += 4; + + autoConfFocusOutListener = (observable, oldValue, newValue) -> { + if (oldValue && !newValue) { + log.info("Service address focus out, check and re-display default option"); + if (autoConfServiceAddress.getText().length() == 0) { + autoConfServiceAddress.setText(String.join(", ", + preferences.getAutoConfirmSettings().serviceAddresses)); + } + } + }; + } /////////////////////////////////////////////////////////////////////////////////////////// // Activate @@ -826,9 +859,6 @@ private void activateDisplayPreferences() { } else { preferences.setUseStandbyMode(false); } - - autoConfirmXmr.setSelected(preferences.isAutoConfirmXmr()); - autoConfirmXmr.setOnAction(e -> preferences.setAutoConfirmXmr(autoConfirmXmr.isSelected())); } private void activateDaoPreferences() { @@ -895,9 +925,65 @@ private void activateDaoPreferences() { blockNotifyPortTextField.textProperty().addListener(blockNotifyPortListener); } + private void activateAutoConfirmPreferences() { + AutoConfirmSettings init = preferences.getAutoConfirmSettings(); + autoConfirmXmr.setSelected(init.enabled); + autoConfRequiredConfirmations.setText(String.valueOf(init.requiredConfirmations)); + autoConfTradeLimit.setText(formatter.formatCoin(Coin.valueOf(init.tradeLimit))); + autoConfServiceAddress.setText(String.join(", ", init.serviceAddresses)); + + autoConfirmXmr.setOnAction(e -> { + boolean enabled = autoConfirmXmr.isSelected(); + AutoConfirmSettings x = preferences.getAutoConfirmSettings(); + preferences.setAutoConfirmSettings( + new AutoConfirmSettings(enabled, x.requiredConfirmations, x.tradeLimit, x.serviceAddresses, x.currencyCode)); + }); + + autoConfServiceAddress.setValidator(GUIUtil.addressRegexValidator()); + autoConfServiceAddress.setErrorMessage(Res.get("validation.invalidAddressList")); + autoConfServiceAddressListener = (observable, oldValue, newValue) -> { + if (GUIUtil.addressRegexValidator().validate(newValue).isValid && !newValue.equals(oldValue)) { + List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); + // revert to default service providers when user empties the list + if (serviceAddresses.size() == 1 && serviceAddresses.get(0).length() == 0) + serviceAddresses = Preferences.DEFAULT_XMR_PROOF_PROVIDERS; + preferences.setAutoConfServiceAddresses(serviceAddresses); + } + }; + + IntegerValidator validator = new IntegerValidator(); + validator.setMinValue(1); validator.setMaxValue(10000); + autoConfRequiredConfirmations.setValidator(validator); + autoConfRequiredConfirmationsListener = (observable, oldValue, newValue) -> { + try { + int value = Integer.parseInt(newValue); + if (!newValue.equals(oldValue)) { + AutoConfirmSettings x = preferences.getAutoConfirmSettings(); + preferences.setAutoConfirmSettings( + new AutoConfirmSettings(x.enabled, value, x.tradeLimit, x.serviceAddresses, x.currencyCode)); + } + } catch (Throwable ignore) { + } + }; + autoConfTradeLimitListener = (observable, oldValue, newValue) -> { + try { + Coin amountAsCoin = ParsingUtils.parseToCoin(newValue, formatter); + AutoConfirmSettings x = preferences.getAutoConfirmSettings(); + preferences.setAutoConfirmSettings( + new AutoConfirmSettings(x.enabled, x.requiredConfirmations, amountAsCoin.value, x.serviceAddresses, x.currencyCode)); + } catch (Throwable ignore) { + } + }; + + autoConfRequiredConfirmations.textProperty().addListener(autoConfRequiredConfirmationsListener); + autoConfTradeLimit.textProperty().addListener(autoConfTradeLimitListener); + autoConfServiceAddress.textProperty().addListener(autoConfServiceAddressListener); + autoConfServiceAddress.focusedProperty().addListener(autoConfFocusOutListener); + } + private void updateDaoFields() { boolean isDaoFullNode = isDaoFullNodeToggleButton.isSelected(); - GridPane.setRowSpan(daoOptionsTitledGroupBg, isDaoFullNode ? 5 : 2); + GridPane.setRowSpan(daoOptionsTitledGroupBg, isDaoFullNode ? 6 : 3); rpcUserTextField.setVisible(isDaoFullNode); rpcUserTextField.setManaged(isDaoFullNode); rpcPwTextField.setVisible(isDaoFullNode); @@ -953,8 +1039,6 @@ private void deactivateDisplayPreferences() { if (displayStandbyModeFeature) { avoidStandbyMode.setOnAction(null); } - - autoConfirmXmr.setOnAction(null); } private void deactivateDaoPreferences() { @@ -965,4 +1049,12 @@ private void deactivateDaoPreferences() { rpcPwTextField.textProperty().removeListener(rpcPwListener); blockNotifyPortTextField.textProperty().removeListener(blockNotifyPortListener); } + + private void deactivateAutoConfirmPreferences() { + autoConfirmXmr.setOnAction(null); + autoConfRequiredConfirmations.textProperty().removeListener(autoConfRequiredConfirmationsListener); + autoConfTradeLimit.textProperty().removeListener(autoConfTradeLimitListener); + autoConfServiceAddress.textProperty().removeListener(autoConfServiceAddressListener); + autoConfServiceAddress.focusedProperty().removeListener(autoConfFocusOutListener); + } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 84468dc86ab..9650ad78bf7 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -639,6 +639,7 @@ message Filter { repeated string refundAgents = 18; repeated string bannedSignerPubKeys = 19; repeated string btc_fee_receiver_addresses = 20; + bool disable_auto_conf = 21; } // not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older @@ -1547,7 +1548,7 @@ message PreferencesPayload { int32 block_notify_port = 53; int32 css_theme = 54; bool tac_accepted_v120 = 55; - bool auto_confirm_xmr = 56; + repeated AutoConfirmSettings auto_confirm_settings = 56; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -2031,6 +2032,14 @@ message TradeCurrency { } } +message AutoConfirmSettings { + bool enabled = 1; + int32 required_confirmations = 2; + int64 trade_limit = 3; + repeated string service_addresses = 4; + string currency_code = 5; +} + message CryptoCurrency { bool is_asset = 1; } From ac10d71f7854f97648fa508c1b1c992b0a87e2d7 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Thu, 27 Aug 2020 16:44:32 -0500 Subject: [PATCH 04/85] Add XMR proof result status code TRADE_LIMIT_EXCEEDED --- build.gradle | 8 ++++++++ .../java/bisq/core/trade/AutoConfirmationManager.java | 2 +- .../java/bisq/core/trade/asset/xmr/XmrProofResult.java | 1 + core/src/main/resources/i18n/displayStrings.properties | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cc3bb3d7c23..d6bba55863d 100644 --- a/build.gradle +++ b/build.gradle @@ -499,6 +499,14 @@ configure(project(':pricenode')) { test { useJUnitPlatform() + + // Disabled by default, since spot provider tests include connections to external API endpoints + // Can be enabled by adding -Dtest.pricenode.includeSpotProviderTests=true to the gradle command: + // ./gradlew test -Dtest.pricenode.includeSpotProviderTests=true + if (System.properties['test.pricenode.includeSpotProviderTests'] != 'true') { + project.logger.lifecycle('Pricenode: Skipping spot provider tests') + exclude 'bisq/price/spot/providers/**' + } } task stage { diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java index d9a9111b240..1f8c01a5eab 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java @@ -170,9 +170,9 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra Coin tradeAmount = trade.getTradeAmount(); Coin tradeLimit = Coin.valueOf(preferences.getAutoConfirmSettings().tradeLimit); if (tradeAmount.isGreaterThan(tradeLimit)) { - trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.FEATURE_DISABLED, null)); log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); + trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.TRADE_LIMIT_EXCEEDED, null)); return; } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java index 1487ff7cc73..0322a50cca3 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java @@ -40,6 +40,7 @@ public enum State { TX_KEY_INVALID, ADDRESS_INVALID, AMOUNT_NOT_MATCHING, + TRADE_LIMIT_EXCEEDED, TRADE_DATE_NOT_MATCHING; } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index f1d224071cc..4b59b5efaa1 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1250,7 +1250,7 @@ setting.info.msg=When selling BTC for XMR you can use the auto-confirm feature t own XMR explorer node for maximum privacy and security.\n\n\ You can also set the maximum amount of BTC per trade to auto-confirm as well as the number of required \ confirmations here in Settings.\n\n\ - See more details (including how to set up your own explorer node) on the Bisq wiki: [.....] + See more details (including how to set up your own explorer node) on the Bisq wiki: https://bisq.wiki/Auto-confirming_trades #################################################################### # Account #################################################################### From 967e0538d63cfbeeff8a536939ca6396d09d853a Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Thu, 27 Aug 2020 21:15:24 -0500 Subject: [PATCH 05/85] Rename XmrProofResult to AutoConfirmResult --- ...roofResult.java => AutoConfirmResult.java} | 8 ++-- .../core/trade/AutoConfirmationManager.java | 18 ++++----- core/src/main/java/bisq/core/trade/Trade.java | 16 ++++---- .../core/trade/asset/xmr/XmrProofInfo.java | 38 +++++++++--------- .../asset/xmr/XmrTransferProofRequester.java | 12 +++--- .../asset/xmr/XmrTransferProofService.java | 4 +- .../trade/asset/xmr/XmrProofInfoTest.java | 40 ++++++++++--------- .../steps/buyer/BuyerStep4View.java | 2 +- .../steps/seller/SellerStep3View.java | 22 +++++----- 9 files changed, 84 insertions(+), 76 deletions(-) rename core/src/main/java/bisq/core/trade/{asset/xmr/XmrProofResult.java => AutoConfirmResult.java} (92%) diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java similarity index 92% rename from core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java rename to core/src/main/java/bisq/core/trade/AutoConfirmResult.java index 0322a50cca3..1fed0adedbb 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofResult.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.asset.xmr; +package bisq.core.trade; import bisq.core.locale.Res; @@ -26,7 +26,7 @@ @Slf4j @Value -public class XmrProofResult { +public class AutoConfirmResult { public enum State { FEATURE_DISABLED, TX_NOT_FOUND, @@ -48,14 +48,14 @@ public enum State { private final int confirmsRequired; private final State state; - public XmrProofResult(int confirmCount, int confirmsRequired, State state) { + public AutoConfirmResult(int confirmCount, int confirmsRequired, State state) { this.confirmCount = confirmCount; this.confirmsRequired = confirmsRequired; this.state = state; } // alternate constructor for error scenarios - public XmrProofResult(State state, @Nullable String errorMsg) { + public AutoConfirmResult(State state, @Nullable String errorMsg) { this.confirmCount = 0; this.confirmsRequired = 0; this.state = state; diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java index 1f8c01a5eab..5c6c712380c 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java @@ -23,10 +23,10 @@ import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.asset.xmr.XmrProofInfo; -import bisq.core.trade.asset.xmr.XmrProofResult; import bisq.core.trade.asset.xmr.XmrTransferProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.AutoConfirmResult; import bisq.core.user.Preferences; import bisq.core.btc.setup.WalletsSetup; @@ -154,7 +154,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra if (alreadyUsed) { String message = "Peer used the XMR tx key already at another trade with trade ID " + t.getId() + ". This might be a scam attempt."; - trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.TX_KEY_REUSED, message)); + trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.TX_KEY_REUSED, message)); } return alreadyUsed; }); @@ -164,7 +164,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra } if (!preferences.getAutoConfirmSettings().enabled || this.isAutoConfDisabledByFilter()) { - trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.FEATURE_DISABLED, null)); + trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.FEATURE_DISABLED, null)); return; } Coin tradeAmount = trade.getTradeAmount(); @@ -172,7 +172,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra if (tradeAmount.isGreaterThan(tradeLimit)) { log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - trade.setXmrProofResult(new XmrProofResult(XmrProofResult.State.TRADE_LIMIT_EXCEEDED, null)); + trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.TRADE_LIMIT_EXCEEDED, null)); return; } @@ -180,7 +180,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra // XMR satoshis have 12 decimal places vs. bitcoin's 8 long amountXmr = offer.getVolumeByAmount(tradeAmount).getValue() * 10000L; int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; - trade.setXmrProofResult(new XmrProofResult(0, confirmsRequired, XmrProofResult.State.TX_NOT_FOUND)); + trade.setAutoConfirmResult(new AutoConfirmResult(0, confirmsRequired, AutoConfirmResult.State.TX_NOT_FOUND)); List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address for (String serviceAddress : serviceAddresses) { @@ -205,7 +205,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra } } - private boolean handleProofResult(XmrProofResult result, Trade trade) { + private boolean handleProofResult(AutoConfirmResult result, Trade trade) { boolean success = true; boolean failure = false; @@ -228,7 +228,7 @@ private boolean handleProofResult(XmrProofResult result, Trade trade) { if (result.isPendingState()) { log.info("Auto confirm received a {} message for tradeId {}, retry will happen automatically", result.getState(), trade.getShortId()); - trade.setXmrProofResult(result); // this updates the GUI with the status.. + trade.setAutoConfirmResult(result); // this updates the GUI with the status.. // Repeating the requests is handled in XmrTransferProofRequester return success; } @@ -243,7 +243,7 @@ private boolean handleProofResult(XmrProofResult result, Trade trade) { } // we've received the final PROOF_OK, all good here. txProofResultsPending.remove(trade.getId()); - trade.setXmrProofResult(result); // this updates the GUI with the status.. + trade.setAutoConfirmResult(result); // this updates the GUI with the status.. log.info("Auto confirm was successful, transitioning trade {} to next step...", trade.getShortId()); if (!trade.isPayoutPublished()) { // note that this state can also be triggered by auto confirmation feature @@ -262,7 +262,7 @@ private boolean handleProofResult(XmrProofResult result, Trade trade) { // TX_KEY_INVALID, ADDRESS_INVALID, AMOUNT_NOT_MATCHING, TRADE_DATE_NOT_MATCHING log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", result.getState(), trade.getShortId()); - trade.setXmrProofResult(result); // this updates the GUI with the status.. + trade.setAutoConfirmResult(result); // this updates the GUI with the status.. resultsCountdown = -1; // signal all API requestors to cease txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count return failure; diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 984e109c04b..3531e4773c2 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -38,7 +38,7 @@ import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.asset.xmr.XmrProofResult; +import bisq.core.trade.AutoConfirmResult; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; @@ -436,18 +436,18 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Setter private String counterCurrencyExtraData; - // xmrProofResult is not persisted yet + // autoConfirmResult is not persisted yet @Getter @Nullable - private transient XmrProofResult xmrProofResult; + private transient AutoConfirmResult autoConfirmResult; - public void setXmrProofResult(XmrProofResult xmrProofResult) { - this.xmrProofResult = xmrProofResult; - xmrProofResultProperty.setValue(xmrProofResult); + public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { + this.autoConfirmResult = autoConfirmResult; + autoConfirmResultProperty.setValue(autoConfirmResult); } @Getter // This observable property can be used for UI to show a notification to user of the XMR proof status - transient final private ObjectProperty xmrProofResultProperty = new SimpleObjectProperty<>(); + transient final private ObjectProperty autoConfirmResultProperty = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization @@ -1187,7 +1187,7 @@ public String toString() { ",\n errorMessage='" + errorMessage + '\'' + ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' + ",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' + - ",\n xmrProofResult='" + xmrProofResult + '\'' + + ",\n autoConfirmResult='" + autoConfirmResult + '\'' + ",\n chatMessages=" + chatMessages + ",\n txFee=" + txFee + ",\n takerFee=" + takerFee + diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java index 06c0ac58d29..0f42536c8f7 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java @@ -17,6 +17,8 @@ package bisq.core.trade.asset.xmr; +import bisq.core.trade.AutoConfirmResult; + import bisq.asset.CryptoNoteAddressValidator; import bisq.common.app.DevEnv; @@ -65,55 +67,55 @@ public String getKey() { return txHash + "|" + serviceAddress; } - public XmrProofResult checkApiResponse(String jsonTxt) { + public AutoConfirmResult checkApiResponse(String jsonTxt) { try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Empty json"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Empty json"); // there should always be "data" and "status" at the top level if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing data / status fields"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing data / status fields"); JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); if (jsonStatus.matches("fail")) { // the API returns "fail" until the transaction has successfully reached the mempool. // we return TX_NOT_FOUND which will cause a retry later - return new XmrProofResult(XmrProofResult.State.TX_NOT_FOUND, null); + return new AutoConfirmResult(AutoConfirmResult.State.TX_NOT_FOUND, null); } else if (!jsonStatus.matches("success")) { - return new XmrProofResult(XmrProofResult.State.API_FAILURE, "Unhandled status value"); + return new AutoConfirmResult(AutoConfirmResult.State.API_FAILURE, "Unhandled status value"); } // validate that the address matches JsonElement jsonAddress = jsonData.get("address"); if (jsonAddress == null) { - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing address field"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing address field"); } else { String expectedAddressHex = CryptoNoteAddressValidator.convertToRawHex(this.recipientAddress); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return new XmrProofResult(XmrProofResult.State.ADDRESS_INVALID, null); + return new AutoConfirmResult(AutoConfirmResult.State.ADDRESS_INVALID, null); } } // validate that the txhash matches JsonElement jsonTxHash = jsonData.get("tx_hash"); if (jsonTxHash == null) { - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing tx_hash field"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); } else { if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return new XmrProofResult(XmrProofResult.State.TX_HASH_INVALID, null); + return new AutoConfirmResult(AutoConfirmResult.State.TX_HASH_INVALID, null); } } // validate that the txkey matches JsonElement jsonViewkey = jsonData.get("viewkey"); if (jsonViewkey == null) { - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing viewkey field"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing viewkey field"); } else { if (!jsonViewkey.getAsString().equalsIgnoreCase(this.txKey)) { log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), txKey); - return new XmrProofResult(XmrProofResult.State.TX_KEY_INVALID, null); + return new AutoConfirmResult(AutoConfirmResult.State.TX_KEY_INVALID, null); } } @@ -121,14 +123,14 @@ public XmrProofResult checkApiResponse(String jsonTxt) { // (except that in dev mode we let this check pass anyway) JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); if (jsonTimestamp == null) { - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing tx_timestamp field"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_timestamp field"); } else { long tradeDateSeconds = tradeDate.getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); if (difference > 60 * 60 * 24 && !DevEnv.isDevMode()) { // accept up to 1 day difference log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return new XmrProofResult(XmrProofResult.State.TRADE_DATE_NOT_MATCHING, null); + return new AutoConfirmResult(AutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); } } @@ -136,7 +138,7 @@ public XmrProofResult checkApiResponse(String jsonTxt) { int confirmations = 0; JsonElement jsonConfs = jsonData.get("tx_confirmations"); if (jsonConfs == null) { - return new XmrProofResult(XmrProofResult.State.API_INVALID, "Missing tx_confirmations field"); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); } else { confirmations = jsonConfs.getAsInt(); log.info("Confirmations: {}, xmr txid: {}", confirmations, txHash); @@ -153,18 +155,18 @@ public XmrProofResult checkApiResponse(String jsonTxt) { if (jsonAmount == amount || DevEnv.isDevMode()) { // any amount ok in dev mode if (confirmations < confirmsRequired) // we return TX_NOT_CONFIRMED which will cause a retry later - return new XmrProofResult(confirmations, confirmsRequired, XmrProofResult.State.TX_NOT_CONFIRMED); + return new AutoConfirmResult(confirmations, confirmsRequired, AutoConfirmResult.State.TX_NOT_CONFIRMED); else - return new XmrProofResult(confirmations, confirmsRequired, XmrProofResult.State.PROOF_OK); + return new AutoConfirmResult(confirmations, confirmsRequired, AutoConfirmResult.State.PROOF_OK); } } } // reaching this point means there was no matching amount - return new XmrProofResult(XmrProofResult.State.AMOUNT_NOT_MATCHING, null); + return new AutoConfirmResult(AutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); } catch (JsonParseException | NullPointerException e) { - return new XmrProofResult(XmrProofResult.State.API_INVALID, e.toString()); + return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, e.toString()); } } } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java index 1f104740da9..71e908a8038 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java @@ -17,6 +17,8 @@ package bisq.core.trade.asset.xmr; +import bisq.core.trade.AutoConfirmResult; + import bisq.network.Socks5ProxyProvider; import bisq.common.UserThread; @@ -43,7 +45,7 @@ public class XmrTransferProofRequester { "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; private final XmrProofInfo xmrProofInfo; - private final Consumer resultHandler; + private final Consumer resultHandler; private final FaultHandler faultHandler; private boolean terminated; private long firstRequest; @@ -58,7 +60,7 @@ public class XmrTransferProofRequester { XmrTransferProofRequester(Socks5ProxyProvider socks5ProxyProvider, XmrProofInfo xmrProofInfo, - Consumer resultHandler, + Consumer resultHandler, FaultHandler faultHandler) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrProofInfo.getServiceAddress()); @@ -89,7 +91,7 @@ public void request() { log.info("Request() aborted, this object has been terminated: {}", httpClient.toString()); return; } - ListenableFuture future = executorService.submit(() -> { + ListenableFuture future = executorService.submit(() -> { Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getKey()); String param = "/api/outputs?txhash=" + xmrProofInfo.getTxHash() + "&address=" + xmrProofInfo.getRecipientAddress() + @@ -103,7 +105,7 @@ public void request() { }); Futures.addCallback(future, new FutureCallback<>() { - public void onSuccess(XmrProofResult result) { + public void onSuccess(AutoConfirmResult result) { if (terminated) { log.info("API terminated from higher level: {}", httpClient.toString()); return; @@ -122,7 +124,7 @@ public void onFailure(@NotNull Throwable throwable) { String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; faultHandler.handleFault(errorMessage, throwable); UserThread.execute(() -> resultHandler.accept( - new XmrProofResult(XmrProofResult.State.CONNECTION_FAIL, errorMessage))); + new AutoConfirmResult(AutoConfirmResult.State.CONNECTION_FAIL, errorMessage))); } }); } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java index 854a7d7bc0a..bf17586e1c1 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java @@ -17,6 +17,8 @@ package bisq.core.trade.asset.xmr; +import bisq.core.trade.AutoConfirmResult; + import bisq.network.Socks5ProxyProvider; import bisq.common.handlers.FaultHandler; @@ -45,7 +47,7 @@ public XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { } public void requestProof(XmrProofInfo xmrProofInfo, - Consumer resultHandler, + Consumer resultHandler, FaultHandler faultHandler) { String key = xmrProofInfo.getKey(); if (map.containsKey(key)) { diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java index cf71971846b..06750c1ab8a 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java @@ -1,5 +1,7 @@ package bisq.core.trade.asset.xmr; +import bisq.core.trade.AutoConfirmResult; + import java.time.Instant; import java.util.Date; @@ -46,13 +48,13 @@ public void testKey() { public void testJsonRoot() { // checking what happens when bad input is provided assertTrue(xmrProofInfo.checkApiResponse( - "invalid json data").getState() == XmrProofResult.State.API_INVALID); + "invalid json data").getState() == AutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( - "").getState() == XmrProofResult.State.API_INVALID); + "").getState() == AutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( - "[]").getState() == XmrProofResult.State.API_INVALID); + "[]").getState() == AutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( - "{}").getState() == XmrProofResult.State.API_INVALID); + "{}").getState() == AutoConfirmResult.State.API_INVALID); } @Test @@ -60,34 +62,34 @@ public void testJsonTopLevel() { // testing the top level fields: data and status assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'title':''},'status':'fail'}" ) - .getState() == XmrProofResult.State.TX_NOT_FOUND); + .getState() == AutoConfirmResult.State.TX_NOT_FOUND); assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'title':''},'missingstatus':'success'}" ) - .getState() == XmrProofResult.State.API_INVALID); + .getState() == AutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( "{'missingdata':{'title':''},'status':'success'}" ) - .getState() == XmrProofResult.State.API_INVALID); + .getState() == AutoConfirmResult.State.API_INVALID); } @Test public void testJsonAddress() { assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) - .getState() == XmrProofResult.State.API_INVALID); + .getState() == AutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'address':'e957dac7'},'status':'success'}" ) - .getState() == XmrProofResult.State.ADDRESS_INVALID); + .getState() == AutoConfirmResult.State.ADDRESS_INVALID); } @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(missing_tx_hash).getState() - == XmrProofResult.State.API_INVALID); + == AutoConfirmResult.State.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_hash).getState() - == XmrProofResult.State.TX_HASH_INVALID); + == AutoConfirmResult.State.TX_HASH_INVALID); } @Test @@ -95,13 +97,13 @@ public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(missing_tx_key).getState() - == XmrProofResult.State.API_INVALID); + == AutoConfirmResult.State.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_key).getState() - == XmrProofResult.State.TX_KEY_INVALID); + == AutoConfirmResult.State.TX_KEY_INVALID); } @Test @@ -110,14 +112,14 @@ public void testJsonTxTimestamp() { "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(missing_tx_timestamp).getState() - == XmrProofResult.State.API_INVALID); + == AutoConfirmResult.State.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_timestamp).getState() - == XmrProofResult.State.TRADE_DATE_NOT_MATCHING); + == AutoConfirmResult.State.TRADE_DATE_NOT_MATCHING); } @Test @@ -135,19 +137,19 @@ public void testJsonTxConfirmation() { "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == XmrProofResult.State.PROOF_OK); + == AutoConfirmResult.State.PROOF_OK); json = json.replaceFirst("777", "0"); assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == XmrProofResult.State.TX_NOT_CONFIRMED); + == AutoConfirmResult.State.TX_NOT_CONFIRMED); json = json.replaceFirst("100000000000", "100000000001"); assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == XmrProofResult.State.AMOUNT_NOT_MATCHING); + == AutoConfirmResult.State.AMOUNT_NOT_MATCHING); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; assertTrue(xmrProofInfo.checkApiResponse(failedJson).getState() - == XmrProofResult.State.API_INVALID); + == AutoConfirmResult.State.API_INVALID); } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 57fbcb49776..0a945a7b73f 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -117,7 +117,7 @@ protected void addContent() { GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); gridPane.getChildren().add(hBox2); GridPane.setRowSpan(hBox2, 5); - if (trade.getXmrProofResult() == null || !trade.getXmrProofResult().isSuccessState()) { + if (trade.getAutoConfirmResult() == null || !trade.getAutoConfirmResult().isSuccessState()) { autoConfBadge.setVisible(false); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 29a9402ffd5..13f3ae9c325 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -42,7 +42,7 @@ import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.asset.xmr.XmrProofResult; +import bisq.core.trade.AutoConfirmResult; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -77,7 +77,7 @@ public class SellerStep3View extends TradeStepView { private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; private TextFieldWithCopyIcon autoConfirmStatusField; - private final ChangeListener xmrProofResultListener; + private final ChangeListener autoConfirmResultListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -86,8 +86,8 @@ public class SellerStep3View extends TradeStepView { public SellerStep3View(PendingTradesViewModel model) { super(model); - // we listen for updates on the trade xmrProofResult field - xmrProofResultListener = (observable, oldValue, newValue) -> { + // we listen for updates on the trade autoConfirmResult field + autoConfirmResultListener = (observable, oldValue, newValue) -> { autoConfirmStatusField.setText(newValue.getTextStatus()); }; } @@ -150,14 +150,14 @@ public void activate() { } }); - // we listen for updates on the trade xmrProofResult field + // we listen for updates on the trade autoConfirmResult field if (autoConfirmStatusField != null) { - trade.getXmrProofResultProperty().addListener(xmrProofResultListener); + trade.getAutoConfirmResultProperty().addListener(autoConfirmResultListener); // display the initial value, or FEATURE_DISABLED if there is none - XmrProofResult xmrProofResult = trade.getXmrProofResult(); - if (xmrProofResult == null) - xmrProofResult = new XmrProofResult(0, 0, XmrProofResult.State.FEATURE_DISABLED); - autoConfirmStatusField.setText(xmrProofResult.getTextStatus()); + AutoConfirmResult autoConfirmResult = trade.getAutoConfirmResult(); + if (autoConfirmResult == null) + autoConfirmResult = new AutoConfirmResult(0, 0, AutoConfirmResult.State.FEATURE_DISABLED); + autoConfirmStatusField.setText(autoConfirmResult.getTextStatus()); } } @@ -175,7 +175,7 @@ public void deactivate() { if (timeoutTimer != null) timeoutTimer.stop(); - trade.getXmrProofResultProperty().removeListener(xmrProofResultListener); + trade.getAutoConfirmResultProperty().removeListener(autoConfirmResultListener); } /////////////////////////////////////////////////////////////////////////////////////////// From e3bf0735f76b17756853ca1fe15a7fb915edd652 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Fri, 28 Aug 2020 12:13:50 -0500 Subject: [PATCH 06/85] Persist the auto confirm state of Trade object Auto confirmed trades will be indicated as such on Trade details. --- .../bisq/core/trade/AutoConfirmResult.java | 39 ++++++++++++++++--- .../core/trade/AutoConfirmationManager.java | 2 +- core/src/main/java/bisq/core/trade/Trade.java | 6 +-- .../core/trade/asset/xmr/XmrProofInfo.java | 4 +- .../overlays/windows/TradeDetailsWindow.java | 6 ++- .../steps/seller/SellerStep3View.java | 2 +- proto/src/main/proto/pb.proto | 18 +++++++++ 7 files changed, 63 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java index 1fed0adedbb..c2b0c573eaa 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java @@ -19,6 +19,8 @@ import bisq.core.locale.Res; +import bisq.common.proto.ProtoUtil; + import javax.annotation.Nullable; import lombok.Value; @@ -42,23 +44,38 @@ public enum State { AMOUNT_NOT_MATCHING, TRADE_LIMIT_EXCEEDED, TRADE_DATE_NOT_MATCHING; + + public static AutoConfirmResult.State fromProto(protobuf.Trade.AutoConfirmResult result) { + return ProtoUtil.enumFromProto(AutoConfirmResult.State.class, result.name()); + } + + public static protobuf.Trade.AutoConfirmResult toProtoMessage(AutoConfirmResult.State result) { + return protobuf.Trade.AutoConfirmResult.valueOf(result.name()); + } } - private final int confirmCount; - private final int confirmsRequired; private final State state; + private final transient int confirmCount; + private final transient int confirmsRequired; + + public AutoConfirmResult(State state) { + this.state = state; + this.confirmCount = 0; + this.confirmsRequired = 0; + } - public AutoConfirmResult(int confirmCount, int confirmsRequired, State state) { + // alternate constructor for showing confirmation progress information + public AutoConfirmResult(State state, int confirmCount, int confirmsRequired) { + this.state = state; this.confirmCount = confirmCount; this.confirmsRequired = confirmsRequired; - this.state = state; } // alternate constructor for error scenarios public AutoConfirmResult(State state, @Nullable String errorMsg) { + this.state = state; this.confirmCount = 0; this.confirmsRequired = 0; - this.state = state; if (isErrorState()) log.error(errorMsg != null ? errorMsg : state.toString()); } @@ -93,4 +110,16 @@ public boolean isErrorState() { return (!isPendingState() && !isSuccessState()); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTOBUF + /////////////////////////////////////////////////////////////////////////////////////////// + + public protobuf.Trade.AutoConfirmResult toProtoMessage() { + return State.toProtoMessage(state); + } + + public static AutoConfirmResult fromProto(protobuf.Trade.AutoConfirmResult proto) { + return new AutoConfirmResult(AutoConfirmResult.State.fromProto(proto)); + } } diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java index 5c6c712380c..a3ad0fbd993 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java @@ -180,7 +180,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra // XMR satoshis have 12 decimal places vs. bitcoin's 8 long amountXmr = offer.getVolumeByAmount(tradeAmount).getValue() * 10000L; int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; - trade.setAutoConfirmResult(new AutoConfirmResult(0, confirmsRequired, AutoConfirmResult.State.TX_NOT_FOUND)); + trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.TX_NOT_FOUND)); List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address for (String serviceAddress : serviceAddresses) { diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 3531e4773c2..eb9782abe36 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -38,7 +38,6 @@ import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.AutoConfirmResult; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; @@ -436,10 +435,9 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Setter private String counterCurrencyExtraData; - // autoConfirmResult is not persisted yet @Getter @Nullable - private transient AutoConfirmResult autoConfirmResult; + private AutoConfirmResult autoConfirmResult; public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { this.autoConfirmResult = autoConfirmResult; @@ -560,6 +558,7 @@ public Message toProtoMessage() { Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState))); Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes))); Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); + Optional.ofNullable(autoConfirmResult).ifPresent(e -> builder.setAutoConfirmResult(autoConfirmResult.toProtoMessage())); return builder.build(); } @@ -594,6 +593,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); + trade.setAutoConfirmResult(AutoConfirmResult.fromProto(protobuf.Trade.AutoConfirmResult.valueOf(proto.getAutoConfirmResultValue()))); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java index 0f42536c8f7..ee79c440e7b 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java @@ -155,9 +155,9 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { if (jsonAmount == amount || DevEnv.isDevMode()) { // any amount ok in dev mode if (confirmations < confirmsRequired) // we return TX_NOT_CONFIRMED which will cause a retry later - return new AutoConfirmResult(confirmations, confirmsRequired, AutoConfirmResult.State.TX_NOT_CONFIRMED); + return new AutoConfirmResult(AutoConfirmResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); else - return new AutoConfirmResult(confirmations, confirmsRequired, AutoConfirmResult.State.PROOF_OK); + return new AutoConfirmResult(AutoConfirmResult.State.PROOF_OK, confirmations, confirmsRequired); } } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 87c1e139201..d2babf89230 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -159,8 +159,10 @@ private void addContent() { DisplayUtils.formatVolumeWithCode(trade.getTradeVolume())); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), FormattingUtils.formatPrice(trade.getTradePrice())); - addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), - Res.get(offer.getPaymentMethod().getId())); + String methodText = Res.get(offer.getPaymentMethod().getId()); + if (trade.getAutoConfirmResult() != null && trade.getAutoConfirmResult().isSuccessState()) + methodText += " (" + trade.getAutoConfirmResult().getTextStatus() + ")"; + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), methodText); // second group rows = 6; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 13f3ae9c325..c459c7ff3aa 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -156,7 +156,7 @@ public void activate() { // display the initial value, or FEATURE_DISABLED if there is none AutoConfirmResult autoConfirmResult = trade.getAutoConfirmResult(); if (autoConfirmResult == null) - autoConfirmResult = new AutoConfirmResult(0, 0, AutoConfirmResult.State.FEATURE_DISABLED); + autoConfirmResult = new AutoConfirmResult(AutoConfirmResult.State.FEATURE_DISABLED); autoConfirmStatusField.setText(autoConfirmResult.getTextStatus()); } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 9650ad78bf7..1614e170b05 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1351,6 +1351,23 @@ message Trade { TRADE_PERIOD_OVER = 3; } + enum AutoConfirmResult { + FEATURE_DISABLED = 0; + TX_NOT_FOUND = 1; + TX_NOT_CONFIRMED = 2; + PROOF_OK = 3; + CONNECTION_FAIL = 4; + API_FAILURE = 5; + API_INVALID = 6; + TX_KEY_REUSED = 7; + TX_HASH_INVALID = 8; + TX_KEY_INVALID = 9; + ADDRESS_INVALID = 10; + AMOUNT_NOT_MATCHING = 11; + TRADE_LIMIT_EXCEEDED = 12; + TRADE_DATE_NOT_MATCHING = 13; + } + Offer offer = 1; ProcessModel process_model = 2; string taker_fee_tx_id = 3; @@ -1388,6 +1405,7 @@ message Trade { RefundResultState refund_result_state = 35; int64 last_refresh_request_date = 36; string counter_currency_extra_data = 37; + AutoConfirmResult auto_confirm_result = 38; } message BuyerAsMakerTrade { From cccc6de595c0deaa564241d46573251740608d5a Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Sat, 29 Aug 2020 10:21:03 -0500 Subject: [PATCH 07/85] Default to clearnet XMR proof providers when in dev mode --- core/src/main/java/bisq/core/user/Preferences.java | 14 +++++++++++--- .../main/settings/preferences/PreferencesView.java | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 1e9f44496d5..284d61f581a 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -33,6 +33,7 @@ import bisq.network.p2p.network.BridgeAddressProvider; +import bisq.common.app.DevEnv; import bisq.common.config.BaseCurrencyNetwork; import bisq.common.config.Config; import bisq.common.proto.persistable.PersistedDataHost; @@ -123,8 +124,15 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid )); // list of XMR proof providers : this list will be used if no preference has been set - public static final List DEFAULT_XMR_PROOF_PROVIDERS = new ArrayList<> (Arrays.asList( - "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); + public static final List getDefaultXmrProofProviders() { + if (DevEnv.isDevMode()) { + return new ArrayList<>(Arrays.asList( + "78.47.61.90:8081")); + } else { + return new ArrayList<>(Arrays.asList( + "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); + } + } public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true; @@ -411,7 +419,7 @@ public AutoConfirmSettings getAutoConfirmSettings() { if (prefPayload.getAutoConfirmSettingsList().size() == 0) { // default values for AutoConfirmSettings when persisted payload is empty: prefPayload.getAutoConfirmSettingsList().add(new AutoConfirmSettings( - false, 5, Coin.valueOf(10000000).value, DEFAULT_XMR_PROOF_PROVIDERS, "XMR")); + false, 5, Coin.valueOf(10000000).value, getDefaultXmrProofProviders(), "XMR")); } return prefPayload.getAutoConfirmSettingsList().get(0); } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index bfc34192660..7f80fdc27f5 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -946,7 +946,7 @@ private void activateAutoConfirmPreferences() { List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); // revert to default service providers when user empties the list if (serviceAddresses.size() == 1 && serviceAddresses.get(0).length() == 0) - serviceAddresses = Preferences.DEFAULT_XMR_PROOF_PROVIDERS; + serviceAddresses = Preferences.getDefaultXmrProofProviders(); preferences.setAutoConfServiceAddresses(serviceAddresses); } }; From d9d0814064c214d79da64ca90a23aeaf7e331360 Mon Sep 17 00:00:00 2001 From: jmacxx <47253594+jmacxx@users.noreply.github.com> Date: Sun, 30 Aug 2020 11:52:04 -0500 Subject: [PATCH 08/85] Re-word the XMR account creation message per @m52go --- .../resources/i18n/displayStrings.properties | 37 +++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4b59b5efaa1..f40bbeda2e9 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1250,7 +1250,7 @@ setting.info.msg=When selling BTC for XMR you can use the auto-confirm feature t own XMR explorer node for maximum privacy and security.\n\n\ You can also set the maximum amount of BTC per trade to auto-confirm as well as the number of required \ confirmations here in Settings.\n\n\ - See more details (including how to set up your own explorer node) on the Bisq wiki: https://bisq.wiki/Auto-confirming_trades + See more details (including how to set up your own explorer node) on the Bisq wiki: https://bisq.wiki/Trading_Monero#Auto-confirming_trades #################################################################### # Account #################################################################### @@ -1332,35 +1332,18 @@ mediator or arbitrator in case of a dispute.\n\n\ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process visit ArQmA discord channel (https://discord.gg/s9BQpJT) \ or the ArQmA forum (https://labs.arqma.com) to find more information. -account.altcoin.popup.xmr.msg=Trading XMR on Bisq requires that you understand and fulfill \ -the following requirements:\n\n\ -Prove payments: since Monero is a private coin, some transaction details aren't publicly available \ -in the blockchain, and, in case of a dispute, the mediator or arbitrator needs them to check if the \ -transaction was really made. In Bisq, the sender of the XMR transaction is the one responsible for \ -providing this information to the mediator or arbitrator in case of a dispute. In order to do that, \ -you must send XMR using a wallet that provides the information required to prove the payment was made, \ -which includes:\n\n\ +account.altcoin.popup.xmr.msg=Trading XMR on Bisq requires that you understand the following requirement:\n\n\ +If selling XMR, you must be able to provide the following information to a mediator or arbitrator in case of a dispute:\n\ - the transaction key (Tx Key, Tx Secret Key or Tx Private Key)\n\ - the transaction ID (Tx ID or Tx Hash)\n\ - the destination address (recipient's address)\n\n\ -This information can be found in the official Monero GUI & CLI wallets, MyMonero, and Exodus (desktop) \ -as well as in Cake Wallet, MyMonero, and Monerujo (mobile), in the following locations:\n\n\ -- Monero GUI: go to Transactions tab\n\ -- Monero CLI: use the command get_tx_key TXID. The flag store-tx-info must be enabled (enabled by default in new versions)\n\ -- Other wallets: go to Transactions history and search for Transaction key (Tx key or Secret key) and the destination address \ -in a sent transaction. Save recipient address option must be enabled in Cake Wallet settings.\n\n\ -If you are using a wallet different from the mentioned above, please be sure you can access those three pieces of information.\ -Since the transaction key and the destination address are stored in the Monero wallet software, and they cannot be recovered \ -in the Monero blockchain, you should never delete or restore your Monero wallet before a Bisq trade is completed. Failure to \ -provide the above data will result in losing the dispute case.\n\n\ -Check payments: with those three pieces of information, the verification that a quantity of Monero was sent to a specific \ -address can be accomplished the following way:\n\n\ -- Monero GUI: change wallet to Advanced mode and go to Advanced > Prove/check > Check Transaction\n\ -- Monero CLI: use the command check_tx_key TXID TXKEY ADDRESS\n\ -- XMR checktx tool (https://xmr.llcoins.net/checktx.html)\n\ -- Explore Monero website (https://www.exploremonero.com/receipt)\n\n\ -If you are still not sure about this process, visit (https://www.getmonero.org/resources/user-guides/prove-payment.html) \ -to find more information or ask a question on the Monero support subreddit (https://www.reddit.com/r/monerosupport/). +See the wiki for details on where to find this information on popular Monero wallets:\n\ +https://bisq.wiki/Trading_Monero#Proving_payments\n\n\ +Failure to provide the required transaction data will result in losing disputes.\n\n\ +Also note that Bisq now offers automatic confirming for XMR transactions to make trades quicker,\ +but you need to enable it in Settings.\n\n\ +See the wiki for more about the auto-confirm feature:\n\ +https://bisq.wiki/Trading_Monero#Auto-confirming_trades # suppress inspection "TrailingSpacesInProperty" account.altcoin.popup.msr.msg=Trading MSR on Bisq requires that you understand and fulfill \ the following requirements:\n\n\ From 36e2f71f1b843d663c9a238d48ac6e88bd4dadd2 Mon Sep 17 00:00:00 2001 From: James Cox <47253594+jmacxx@users.noreply.github.com> Date: Sun, 30 Aug 2020 12:39:05 -0500 Subject: [PATCH 09/85] Update core/src/main/resources/i18n/displayStrings.properties Co-authored-by: m52go --- core/src/main/resources/i18n/displayStrings.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index f40bbeda2e9..5e4de239096 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1340,7 +1340,7 @@ If selling XMR, you must be able to provide the following information to a media See the wiki for details on where to find this information on popular Monero wallets:\n\ https://bisq.wiki/Trading_Monero#Proving_payments\n\n\ Failure to provide the required transaction data will result in losing disputes.\n\n\ -Also note that Bisq now offers automatic confirming for XMR transactions to make trades quicker,\ +Also note that Bisq now offers automatic confirming for XMR transactions to make trades quicker, \ but you need to enable it in Settings.\n\n\ See the wiki for more about the auto-confirm feature:\n\ https://bisq.wiki/Trading_Monero#Auto-confirming_trades From ca9f61ecde1a6517621c001afc7adc71ec6531d0 Mon Sep 17 00:00:00 2001 From: James Cox <47253594+jmacxx@users.noreply.github.com> Date: Sun, 30 Aug 2020 12:39:23 -0500 Subject: [PATCH 10/85] Update core/src/main/resources/i18n/displayStrings.properties Co-authored-by: m52go --- core/src/main/resources/i18n/displayStrings.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 5e4de239096..0497bb1a5d2 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -1332,7 +1332,7 @@ mediator or arbitrator in case of a dispute.\n\n\ There is no payment ID required, just the normal public address.\n\ If you are not sure about that process visit ArQmA discord channel (https://discord.gg/s9BQpJT) \ or the ArQmA forum (https://labs.arqma.com) to find more information. -account.altcoin.popup.xmr.msg=Trading XMR on Bisq requires that you understand the following requirement:\n\n\ +account.altcoin.popup.xmr.msg=Trading XMR on Bisq requires that you understand the following requirement.\n\n\ If selling XMR, you must be able to provide the following information to a mediator or arbitrator in case of a dispute:\n\ - the transaction key (Tx Key, Tx Secret Key or Tx Private Key)\n\ - the transaction ID (Tx ID or Tx Hash)\n\ From 3e728c69f707790e35d96ddc081fcee5fdc586d4 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 14:39:44 -0500 Subject: [PATCH 11/85] - Remove AutoConfirmResult enum from protobuf and add a AutoConfirmResult instead which stores the stateName. [1] - Adjust protobuf methods - Add UNDEFINED to AutoConfirmResult.State to support cases where we get no data to set the enum. - Add NO_MATCH_FOUND (used in follow up commits) - Refactoring: Improve constructors [1] Enums in protobuf are not well supported. They are global so an enum with name (e.g. State) inside Trade conflicts with another enum inside Message with the same name. So they do not reflect encapsulation in the class like in java. We moved over time to the strategy to use strings (from enum.name()) instead of the enum, avoiding also cumbersome fromProto and toProto code and being more flexible with updates. The autoConfirmResultState enum inside Trade was a bit confusing to me as it was a different structure as in the java code. We try to mirror the structure as far as possible. --- .../bisq/core/trade/AutoConfirmResult.java | 68 ++++++++++--------- core/src/main/java/bisq/core/trade/Trade.java | 6 +- proto/src/main/proto/pb.proto | 21 ++---- 3 files changed, 45 insertions(+), 50 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java index c2b0c573eaa..29f6bce8ac8 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java @@ -21,15 +21,16 @@ import bisq.common.proto.ProtoUtil; -import javax.annotation.Nullable; - import lombok.Value; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + @Slf4j @Value public class AutoConfirmResult { public enum State { + UNDEFINED, FEATURE_DISABLED, TX_NOT_FOUND, TX_NOT_CONFIRMED, @@ -41,27 +42,25 @@ public enum State { TX_HASH_INVALID, TX_KEY_INVALID, ADDRESS_INVALID, + NO_MATCH_FOUND, AMOUNT_NOT_MATCHING, TRADE_LIMIT_EXCEEDED, - TRADE_DATE_NOT_MATCHING; - - public static AutoConfirmResult.State fromProto(protobuf.Trade.AutoConfirmResult result) { - return ProtoUtil.enumFromProto(AutoConfirmResult.State.class, result.name()); - } - - public static protobuf.Trade.AutoConfirmResult toProtoMessage(AutoConfirmResult.State result) { - return protobuf.Trade.AutoConfirmResult.valueOf(result.name()); - } + TRADE_DATE_NOT_MATCHING } + // Only state gets persisted private final State state; + private final transient int confirmCount; private final transient int confirmsRequired; + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + public AutoConfirmResult(State state) { - this.state = state; - this.confirmCount = 0; - this.confirmsRequired = 0; + this(state, 0, 0); } // alternate constructor for showing confirmation progress information @@ -73,13 +72,33 @@ public AutoConfirmResult(State state, int confirmCount, int confirmsRequired) { // alternate constructor for error scenarios public AutoConfirmResult(State state, @Nullable String errorMsg) { - this.state = state; - this.confirmCount = 0; - this.confirmsRequired = 0; - if (isErrorState()) + this(state, 0, 0); + + if (isErrorState()) { log.error(errorMsg != null ? errorMsg : state.toString()); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTOBUF + /////////////////////////////////////////////////////////////////////////////////////////// + + public protobuf.AutoConfirmResult toProtoMessage() { + return protobuf.AutoConfirmResult.newBuilder().setStateName(state.name()).build(); + } + + public static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto) { + AutoConfirmResult.State state = ProtoUtil.enumFromProto(AutoConfirmResult.State.class, proto.getStateName()); + return state != null ? new AutoConfirmResult(state) : new AutoConfirmResult(State.UNDEFINED); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + public String getTextStatus() { switch (state) { case TX_NOT_CONFIRMED: @@ -109,17 +128,4 @@ public boolean isSuccessState() { public boolean isErrorState() { return (!isPendingState() && !isSuccessState()); } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTOBUF - /////////////////////////////////////////////////////////////////////////////////////////// - - public protobuf.Trade.AutoConfirmResult toProtoMessage() { - return State.toProtoMessage(state); - } - - public static AutoConfirmResult fromProto(protobuf.Trade.AutoConfirmResult proto) { - return new AutoConfirmResult(AutoConfirmResult.State.fromProto(proto)); - } } diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index eb9782abe36..5ca75ca76dc 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -439,14 +439,16 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Nullable private AutoConfirmResult autoConfirmResult; - public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { + void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { this.autoConfirmResult = autoConfirmResult; autoConfirmResultProperty.setValue(autoConfirmResult); } + @Getter // This observable property can be used for UI to show a notification to user of the XMR proof status transient final private ObjectProperty autoConfirmResultProperty = new SimpleObjectProperty<>(); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialization /////////////////////////////////////////////////////////////////////////////////////////// @@ -593,7 +595,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); - trade.setAutoConfirmResult(AutoConfirmResult.fromProto(protobuf.Trade.AutoConfirmResult.valueOf(proto.getAutoConfirmResultValue()))); + trade.setAutoConfirmResult(AutoConfirmResult.fromProto(proto.getAutoConfirmResult())); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 51791d4d8c3..7b96a55f857 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1356,23 +1356,6 @@ message Trade { TRADE_PERIOD_OVER = 3; } - enum AutoConfirmResult { - FEATURE_DISABLED = 0; - TX_NOT_FOUND = 1; - TX_NOT_CONFIRMED = 2; - PROOF_OK = 3; - CONNECTION_FAIL = 4; - API_FAILURE = 5; - API_INVALID = 6; - TX_KEY_REUSED = 7; - TX_HASH_INVALID = 8; - TX_KEY_INVALID = 9; - ADDRESS_INVALID = 10; - AMOUNT_NOT_MATCHING = 11; - TRADE_LIMIT_EXCEEDED = 12; - TRADE_DATE_NOT_MATCHING = 13; - } - Offer offer = 1; ProcessModel process_model = 2; string taker_fee_tx_id = 3; @@ -1413,6 +1396,10 @@ message Trade { AutoConfirmResult auto_confirm_result = 38; } +message AutoConfirmResult { + string stateName = 1; // name of AutoConfirmResult.State enum +} + message BuyerAsMakerTrade { Trade trade = 1; } From 008ae93b88bfb9011be7e42cf6e3041977b99e53 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 14:40:21 -0500 Subject: [PATCH 12/85] Add null check for name --- common/src/main/java/bisq/common/proto/ProtoUtil.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/bisq/common/proto/ProtoUtil.java b/common/src/main/java/bisq/common/proto/ProtoUtil.java index 6fd7b0f2e5d..da3cf029776 100644 --- a/common/src/main/java/bisq/common/proto/ProtoUtil.java +++ b/common/src/main/java/bisq/common/proto/ProtoUtil.java @@ -66,6 +66,10 @@ public static byte[] byteArrayOrNullFromProto(ByteString proto) { */ @Nullable public static > E enumFromProto(Class enumType, String name) { + if (name == null) { + return null; + } + E result = Enums.getIfPresent(enumType, name).orNull(); if (result == null) { log.error("Invalid value for enum " + enumType.getSimpleName() + ": " + name); @@ -77,7 +81,8 @@ public static > E enumFromProto(Class enumType, String name return result; } - public static Iterable collectionToProto(Collection collection, Class messageType) { + public static Iterable collectionToProto(Collection collection, + Class messageType) { return collection.stream() .map(e -> { final Message message = e.toProtoMessage(); @@ -92,7 +97,8 @@ public static Iterable collectionToProto(Collection Iterable collectionToProto(Collection collection, Function extra) { + public static Iterable collectionToProto(Collection collection, + Function extra) { return collection.stream().map(o -> extra.apply(o.toProtoMessage())).collect(Collectors.toList()); } } From 2dbc4645ec38d069bc5d3661506d7604f44311f1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 14:42:52 -0500 Subject: [PATCH 13/85] - Change tolerance from 1 day to 2 hours. - Add case if no match is found -> NO_MATCH_FOUND. - Add test case for NO_MATCH_FOUND - Add curley brackets to one liners - Log improvements --- .../core/trade/asset/xmr/XmrProofInfo.java | 21 ++++++++++++++----- .../trade/asset/xmr/XmrProofInfoTest.java | 6 ++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java index ee79c440e7b..64a852a8c24 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java @@ -30,6 +30,7 @@ import com.google.gson.JsonParseException; import java.util.Date; +import java.util.concurrent.TimeUnit; import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -70,11 +71,13 @@ public String getKey() { public AutoConfirmResult checkApiResponse(String jsonTxt) { try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); - if (json == null) + if (json == null) { return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Empty json"); + } // there should always be "data" and "status" at the top level - if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) + if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing data / status fields"); + } JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); if (jsonStatus.matches("fail")) { @@ -127,7 +130,8 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { } else { long tradeDateSeconds = tradeDate.getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); - if (difference > 60 * 60 * 24 && !DevEnv.isDevMode()) { // accept up to 1 day difference + // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync + if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); return new AutoConfirmResult(AutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); @@ -135,22 +139,24 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { } // calculate how many confirms are still needed - int confirmations = 0; + int confirmations; JsonElement jsonConfs = jsonData.get("tx_confirmations"); if (jsonConfs == null) { return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); } else { confirmations = jsonConfs.getAsInt(); - log.info("Confirmations: {}, xmr txid: {}", confirmations, txHash); + log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); } // iterate through the list of outputs, one of them has to match the amount we are trying to verify. // check that the "match" field is true as well as validating the amount value // (except that in dev mode we allow any amount as valid) JsonArray jsonOutputs = jsonData.get("outputs").getAsJsonArray(); + boolean anyMatchFound = false; for (int i = 0; i < jsonOutputs.size(); i++) { JsonObject out = jsonOutputs.get(i).getAsJsonObject(); if (out.get("match").getAsBoolean()) { + anyMatchFound = true; long jsonAmount = out.get("amount").getAsLong(); if (jsonAmount == amount || DevEnv.isDevMode()) { // any amount ok in dev mode if (confirmations < confirmsRequired) @@ -162,6 +168,11 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { } } + // None of the outputs had a match entry + if (!anyMatchFound) { + return new AutoConfirmResult(AutoConfirmResult.State.NO_MATCH_FOUND, null); + } + // reaching this point means there was no matching amount return new AutoConfirmResult(AutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java index 06750c1ab8a..ab8d2a1b845 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java @@ -144,6 +144,12 @@ public void testJsonTxConfirmation() { json = json.replaceFirst("100000000000", "100000000001"); assertTrue(xmrProofInfo.checkApiResponse(json).getState() == AutoConfirmResult.State.AMOUNT_NOT_MATCHING); + + // Revert change of amount + json = json.replaceFirst("100000000001", "100000000000"); + json = json.replaceFirst("'match':true", "'match':false"); + assertTrue(xmrProofInfo.checkApiResponse(json).getState() + == AutoConfirmResult.State.NO_MATCH_FOUND); } @Test From 07a761255dfaa2ad42d19403d141565226c56808 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 14:45:05 -0500 Subject: [PATCH 14/85] - Make REPEAT_REQUEST_PERIOD and MAX_REQUEST_PERIOD static - Replace httpClient.toString() with httpClient.getBaseUrl() as toString would deliver too much for those use cases. - Add @Nullable - Log improvements --- .../asset/xmr/XmrTransferProofRequester.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java index 71e908a8038..53b8293b1fe 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java @@ -38,8 +38,13 @@ import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; + @Slf4j public class XmrTransferProofRequester { + // these settings are not likely to change and therefore not put into Config + private static long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); + private static long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofRequester", 3, 5, 10 * 60); @@ -47,18 +52,16 @@ public class XmrTransferProofRequester { private final XmrProofInfo xmrProofInfo; private final Consumer resultHandler; private final FaultHandler faultHandler; + private boolean terminated; private long firstRequest; - // these settings are not likely to change and therefore not put into Config - private long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); - private long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTransferProofRequester(Socks5ProxyProvider socks5ProxyProvider, + XmrTransferProofRequester(@Nullable Socks5ProxyProvider socks5ProxyProvider, XmrProofInfo xmrProofInfo, Consumer resultHandler, FaultHandler faultHandler) { @@ -88,7 +91,7 @@ public void request() { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls // this scenario may happen if a re-request is scheduled from the callback below - log.info("Request() aborted, this object has been terminated: {}", httpClient.toString()); + log.info("Request() aborted, this object has been terminated. Service: {}", httpClient.getBaseUrl()); return; } ListenableFuture future = executorService.submit(() -> { @@ -97,21 +100,21 @@ public void request() { "&address=" + xmrProofInfo.getRecipientAddress() + "&viewkey=" + xmrProofInfo.getTxKey() + "&txprove=1"; - log.info(httpClient.toString()); - log.info(param); + log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - log.info(json); - return xmrProofInfo.checkApiResponse(json); + AutoConfirmResult autoConfirmResult = xmrProofInfo.checkApiResponse(json); + log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); + return autoConfirmResult; }); Futures.addCallback(future, new FutureCallback<>() { public void onSuccess(AutoConfirmResult result) { if (terminated) { - log.info("API terminated from higher level: {}", httpClient.toString()); + log.info("API terminated from higher level: {}", httpClient.getBaseUrl()); return; } if (System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD) { - log.warn("We have tried this service API for too long, giving up: {}", httpClient.toString()); + log.warn("We have tried requesting from {} for too long, giving up.", httpClient.getBaseUrl()); return; } if (result.isPendingState()) { From 2f7b24d1c8e2fe1641214c020ff13ed7ad76d09c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 14:46:04 -0500 Subject: [PATCH 15/85] - Add @Nullable - Add curly brackets to one liners - Log improvements --- .../trade/asset/xmr/XmrTransferProofService.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java index bf17586e1c1..976fc703036 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java @@ -39,6 +39,7 @@ @Slf4j public class XmrTransferProofService { private Map map = new HashMap<>(); + @Nullable private Socks5ProxyProvider socks5ProxyProvider; @Inject @@ -51,17 +52,18 @@ public void requestProof(XmrProofInfo xmrProofInfo, FaultHandler faultHandler) { String key = xmrProofInfo.getKey(); if (map.containsKey(key)) { - log.warn("We started a proof request for trade with ID {} already", key); + log.warn("We started a proof request for key {} already", key); return; } - log.info("requesting tx proof for " + key); + log.info("requesting tx proof with key {}", key); XmrTransferProofRequester requester = new XmrTransferProofRequester( socks5ProxyProvider, xmrProofInfo, result -> { - if (result.isSuccessState()) + if (result.isSuccessState()) { cleanup(key); + } resultHandler.accept(result); }, (errorMsg, throwable) -> { @@ -76,12 +78,13 @@ public void terminateRequest(XmrProofInfo xmrProofInfo) { String key = xmrProofInfo.getKey(); XmrTransferProofRequester requester = map.getOrDefault(key, null); if (requester != null) { - log.info("Terminating API request for {}", key); + log.info("Terminating API request for request with key {}", key); requester.stop(); cleanup(key); } } - private void cleanup(String identifier) { - map.remove(identifier); + + private void cleanup(String key) { + map.remove(key); } } From 2f1566bb065404ed6dbb021bc3eff1155a3b1b01 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 14:48:15 -0500 Subject: [PATCH 16/85] Add NO_MATCH_FOUND in comment --- .../main/java/bisq/core/trade/AutoConfirmationManager.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java index a3ad0fbd993..6f9f2bce8b3 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java @@ -18,6 +18,7 @@ package bisq.core.trade; import bisq.core.account.witness.AccountAgeWitnessService; +import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; import bisq.core.offer.Offer; import bisq.core.payment.payload.AssetsAccountPayload; @@ -26,9 +27,7 @@ import bisq.core.trade.asset.xmr.XmrTransferProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; -import bisq.core.trade.AutoConfirmResult; import bisq.core.user.Preferences; -import bisq.core.btc.setup.WalletsSetup; import bisq.network.p2p.P2PService; @@ -259,7 +258,7 @@ private boolean handleProofResult(AutoConfirmResult result, Trade trade) { // error case. any validation error from XmrProofRequester or XmrProofInfo.check // the following error codes will end up here: // CONNECTION_FAIL, API_FAILURE, API_INVALID, TX_KEY_REUSED, TX_HASH_INVALID, - // TX_KEY_INVALID, ADDRESS_INVALID, AMOUNT_NOT_MATCHING, TRADE_DATE_NOT_MATCHING + // TX_KEY_INVALID, ADDRESS_INVALID, NO_MATCH_FOUND, AMOUNT_NOT_MATCHING, TRADE_DATE_NOT_MATCHING log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", result.getState(), trade.getShortId()); trade.setAutoConfirmResult(result); // this updates the GUI with the status.. From ed5078c0f15589b2d75c5e87da643417200d33b8 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 15:45:47 -0500 Subject: [PATCH 17/85] Add abstract AutoConfirmResult class to get better support if we want to add auto confirm for other currencies in the future. The generic part is only used where we would have issues with backward compatibility like in the protobuf objects. Most of the current classes are kept XMR specific and could be generalized once we add other assets, but that would be an internal refactoring without breaking any network or storage data. I think it would be premature to go further here as we don't know the details of other use cases. I added the methods used from clients to AutoConfirmResult, not sure if the API is well defined by that, but as said that could become subject of a future refactoring once another auto confirm feature gets added. Goal of that refactoring was to avoid that we need more fields for trade and the the UI would have to deal with lots of switch cases based on currency. Sorry that is a larger commit, would have been hard to break up... --- .../bisq/core/trade/AutoConfirmResult.java | 120 ++++------------ core/src/main/java/bisq/core/trade/Trade.java | 11 +- .../java/bisq/core/trade/TradeManager.java | 8 +- .../bisq/core/trade/XmrAutoConfirmResult.java | 136 ++++++++++++++++++ ...r.java => XmrAutoConfirmationManager.java} | 42 +++--- .../core/trade/asset/xmr/XmrProofInfo.java | 40 +++--- .../asset/xmr/XmrTransferProofRequester.java | 14 +- .../asset/xmr/XmrTransferProofService.java | 4 +- .../core/trade/protocol/ProcessModel.java | 8 +- ...CounterCurrencyTransferStartedMessage.java | 2 +- .../trade/asset/xmr/XmrProofInfoTest.java | 42 +++--- .../overlays/windows/TradeDetailsWindow.java | 4 +- .../steps/buyer/BuyerStep4View.java | 2 +- .../steps/seller/SellerStep3View.java | 7 +- proto/src/main/proto/pb.proto | 2 +- 15 files changed, 262 insertions(+), 180 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java rename core/src/main/java/bisq/core/trade/{AutoConfirmationManager.java => XmrAutoConfirmationManager.java} (88%) diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java index 29f6bce8ac8..bed7463d0f9 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/AutoConfirmResult.java @@ -17,66 +17,31 @@ package bisq.core.trade; -import bisq.core.locale.Res; - -import bisq.common.proto.ProtoUtil; - -import lombok.Value; -import lombok.extern.slf4j.Slf4j; +import lombok.EqualsAndHashCode; +import lombok.Getter; import javax.annotation.Nullable; -@Slf4j -@Value -public class AutoConfirmResult { - public enum State { - UNDEFINED, - FEATURE_DISABLED, - TX_NOT_FOUND, - TX_NOT_CONFIRMED, - PROOF_OK, - CONNECTION_FAIL, - API_FAILURE, - API_INVALID, - TX_KEY_REUSED, - TX_HASH_INVALID, - TX_KEY_INVALID, - ADDRESS_INVALID, - NO_MATCH_FOUND, - AMOUNT_NOT_MATCHING, - TRADE_LIMIT_EXCEEDED, - TRADE_DATE_NOT_MATCHING - } - - // Only state gets persisted - private final State state; - - private final transient int confirmCount; - private final transient int confirmsRequired; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - public AutoConfirmResult(State state) { - this(state, 0, 0); - } - - // alternate constructor for showing confirmation progress information - public AutoConfirmResult(State state, int confirmCount, int confirmsRequired) { - this.state = state; - this.confirmCount = confirmCount; - this.confirmsRequired = confirmsRequired; +/** + * Base class for AutoConfirm implementations + */ +@EqualsAndHashCode +@Getter +public abstract class AutoConfirmResult { + + public static AutoConfirmResult fromCurrencyCode(String currencyCode) { + switch (currencyCode) { + case "XMR": + return new XmrAutoConfirmResult(); + default: + return null; + } } - // alternate constructor for error scenarios - public AutoConfirmResult(State state, @Nullable String errorMsg) { - this(state, 0, 0); + private final String stateName; - if (isErrorState()) { - log.error(errorMsg != null ? errorMsg : state.toString()); - } + protected AutoConfirmResult(String stateName) { + this.stateName = stateName; } @@ -84,48 +49,25 @@ public AutoConfirmResult(State state, @Nullable String errorMsg) { // PROTOBUF /////////////////////////////////////////////////////////////////////////////////////////// - public protobuf.AutoConfirmResult toProtoMessage() { - return protobuf.AutoConfirmResult.newBuilder().setStateName(state.name()).build(); + // We use fromProto as kind of factory method to get the specific AutoConfirmResult + @Nullable + static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { + switch (currencyCode) { + case "XMR": + return XmrAutoConfirmResult.fromProto(proto); + default: + return null; + } } - public static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto) { - AutoConfirmResult.State state = ProtoUtil.enumFromProto(AutoConfirmResult.State.class, proto.getStateName()); - return state != null ? new AutoConfirmResult(state) : new AutoConfirmResult(State.UNDEFINED); - - } + public abstract protobuf.AutoConfirmResult toProtoMessage(); /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// - public String getTextStatus() { - switch (state) { - case TX_NOT_CONFIRMED: - return Res.get("portfolio.pending.autoConfirmPending") - + " " + confirmCount - + "/" + confirmsRequired; - case TX_NOT_FOUND: - return Res.get("portfolio.pending.autoConfirmTxNotFound"); - case FEATURE_DISABLED: - return Res.get("portfolio.pending.autoConfirmDisabled"); - case PROOF_OK: - return Res.get("portfolio.pending.autoConfirmSuccess"); - default: - // any other statuses we display the enum name - return this.state.toString(); - } - } - - public boolean isPendingState() { - return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); - } + abstract public boolean isSuccessState(); - public boolean isSuccessState() { - return (state == State.PROOF_OK); - } - - public boolean isErrorState() { - return (!isPendingState() && !isSuccessState()); - } + abstract public String getTextStatus(); } diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 5ca75ca76dc..dc765d52003 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -435,7 +435,10 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Setter private String counterCurrencyExtraData; - @Getter + public AutoConfirmResult getAutoConfirmResult() { + return autoConfirmResult != null ? autoConfirmResult : AutoConfirmResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); + } + @Nullable private AutoConfirmResult autoConfirmResult; @@ -595,7 +598,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); - trade.setAutoConfirmResult(AutoConfirmResult.fromProto(proto.getAutoConfirmResult())); + trade.setAutoConfirmResult(AutoConfirmResult.fromProto(proto.getAutoConfirmResult(), checkNotNull(trade.getOffer()).getCurrencyCode())); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) @@ -625,7 +628,7 @@ public void init(P2PService p2PService, User user, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, - AutoConfirmationManager autoConfirmationManager, + XmrAutoConfirmationManager xmrAutoConfirmationManager, TradeStatisticsManager tradeStatisticsManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -645,7 +648,7 @@ public void init(P2PService p2PService, user, filterManager, accountAgeWitnessService, - autoConfirmationManager, + xmrAutoConfirmationManager, tradeStatisticsManager, arbitratorManager, mediatorManager, diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 3a7cd125b30..70ecd216c84 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -126,7 +126,7 @@ public class TradeManager implements PersistedDataHost { private final TradeStatisticsManager tradeStatisticsManager; private final ReferralIdService referralIdService; private final AccountAgeWitnessService accountAgeWitnessService; - private final AutoConfirmationManager autoConfirmationManager; + private final XmrAutoConfirmationManager xmrAutoConfirmationManager; private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; @@ -168,7 +168,7 @@ public TradeManager(User user, TradeStatisticsManager tradeStatisticsManager, ReferralIdService referralIdService, AccountAgeWitnessService accountAgeWitnessService, - AutoConfirmationManager autoConfirmationManager, + XmrAutoConfirmationManager xmrAutoConfirmationManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, @@ -191,7 +191,7 @@ public TradeManager(User user, this.tradeStatisticsManager = tradeStatisticsManager; this.referralIdService = referralIdService; this.accountAgeWitnessService = accountAgeWitnessService; - this.autoConfirmationManager = autoConfirmationManager; + this.xmrAutoConfirmationManager = xmrAutoConfirmationManager; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; @@ -436,7 +436,7 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo user, filterManager, accountAgeWitnessService, - autoConfirmationManager, + xmrAutoConfirmationManager, tradeStatisticsManager, arbitratorManager, mediatorManager, diff --git a/core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java new file mode 100644 index 00000000000..4bce7bedf1a --- /dev/null +++ b/core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java @@ -0,0 +1,136 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade; + +import bisq.core.locale.Res; + +import bisq.common.proto.ProtoUtil; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +@Slf4j +@Getter +@EqualsAndHashCode(callSuper = true) +public class XmrAutoConfirmResult extends AutoConfirmResult { + public enum State { + UNDEFINED, + FEATURE_DISABLED, + TX_NOT_FOUND, + TX_NOT_CONFIRMED, + PROOF_OK, + CONNECTION_FAIL, + API_FAILURE, + API_INVALID, + TX_KEY_REUSED, + TX_HASH_INVALID, + TX_KEY_INVALID, + ADDRESS_INVALID, + NO_MATCH_FOUND, + AMOUNT_NOT_MATCHING, + TRADE_LIMIT_EXCEEDED, + TRADE_DATE_NOT_MATCHING + } + + private final State state; + private final transient int confirmCount; + private final transient int confirmsRequired; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + + public XmrAutoConfirmResult() { + this(State.UNDEFINED, 0, 0); + } + + public XmrAutoConfirmResult(State state) { + this(state, 0, 0); + } + + // alternate constructor for showing confirmation progress information + public XmrAutoConfirmResult(State state, int confirmCount, int confirmsRequired) { + super(state.name()); + this.state = state; + this.confirmCount = confirmCount; + this.confirmsRequired = confirmsRequired; + } + + // alternate constructor for error scenarios + public XmrAutoConfirmResult(State state, @Nullable String errorMsg) { + this(state, 0, 0); + + if (isErrorState()) { + log.error(errorMsg != null ? errorMsg : state.toString()); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTOBUF + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public protobuf.AutoConfirmResult toProtoMessage() { + return protobuf.AutoConfirmResult.newBuilder().setStateName(state.name()).build(); + } + + public static XmrAutoConfirmResult fromProto(protobuf.AutoConfirmResult proto) { + XmrAutoConfirmResult.State state = ProtoUtil.enumFromProto(XmrAutoConfirmResult.State.class, proto.getStateName()); + return state != null ? new XmrAutoConfirmResult(state) : new XmrAutoConfirmResult(State.UNDEFINED); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + public String getTextStatus() { + switch (state) { + case TX_NOT_CONFIRMED: + return Res.get("portfolio.pending.autoConfirmPending") + + " " + confirmCount + + "/" + confirmsRequired; + case TX_NOT_FOUND: + return Res.get("portfolio.pending.autoConfirmTxNotFound"); + case FEATURE_DISABLED: + return Res.get("portfolio.pending.autoConfirmDisabled"); + case PROOF_OK: + return Res.get("portfolio.pending.autoConfirmSuccess"); + default: + // any other statuses we display the enum name + return this.state.toString(); + } + } + + public boolean isPendingState() { + return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); + } + + public boolean isSuccessState() { + return (state == State.PROOF_OK); + } + + public boolean isErrorState() { + return (!isPendingState() && !isSuccessState()); + } +} diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/XmrAutoConfirmationManager.java similarity index 88% rename from core/src/main/java/bisq/core/trade/AutoConfirmationManager.java rename to core/src/main/java/bisq/core/trade/XmrAutoConfirmationManager.java index 6f9f2bce8b3..67a6bbeeab8 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/XmrAutoConfirmationManager.java @@ -20,6 +20,7 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; +import bisq.core.monetary.Volume; import bisq.core.offer.Offer; import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; @@ -49,7 +50,7 @@ @Slf4j @Singleton -public class AutoConfirmationManager { +public class XmrAutoConfirmationManager { private final FilterManager filterManager; private final Preferences preferences; @@ -66,15 +67,15 @@ public class AutoConfirmationManager { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - AutoConfirmationManager(FilterManager filterManager, - Preferences preferences, - XmrTransferProofService xmrTransferProofService, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager, - P2PService p2PService, - WalletsSetup walletsSetup, - AccountAgeWitnessService accountAgeWitnessService - ) { + XmrAutoConfirmationManager(FilterManager filterManager, + Preferences preferences, + XmrTransferProofService xmrTransferProofService, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, + P2PService p2PService, + WalletsSetup walletsSetup, + AccountAgeWitnessService accountAgeWitnessService + ) { this.filterManager = filterManager; this.preferences = preferences; this.xmrTransferProofService = xmrTransferProofService; @@ -153,7 +154,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra if (alreadyUsed) { String message = "Peer used the XMR tx key already at another trade with trade ID " + t.getId() + ". This might be a scam attempt."; - trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.TX_KEY_REUSED, message)); + trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_REUSED, message)); } return alreadyUsed; }); @@ -163,23 +164,24 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra } if (!preferences.getAutoConfirmSettings().enabled || this.isAutoConfDisabledByFilter()) { - trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.FEATURE_DISABLED, null)); + trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.FEATURE_DISABLED, null)); return; } Coin tradeAmount = trade.getTradeAmount(); Coin tradeLimit = Coin.valueOf(preferences.getAutoConfirmSettings().tradeLimit); - if (tradeAmount.isGreaterThan(tradeLimit)) { + if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.TRADE_LIMIT_EXCEEDED, null)); + trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_LIMIT_EXCEEDED, null)); return; } String address = sellersAssetsAccountPayload.getAddress(); // XMR satoshis have 12 decimal places vs. bitcoin's 8 - long amountXmr = offer.getVolumeByAmount(tradeAmount).getValue() * 10000L; + Volume volume = offer.getVolumeByAmount(tradeAmount); + long amountXmr = volume != null ? volume.getValue() * 10000L : 0L; int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; - trade.setAutoConfirmResult(new AutoConfirmResult(AutoConfirmResult.State.TX_NOT_FOUND)); + trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND)); List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address for (String serviceAddress : serviceAddresses) { @@ -204,7 +206,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra } } - private boolean handleProofResult(AutoConfirmResult result, Trade trade) { + private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { boolean success = true; boolean failure = false; @@ -250,8 +252,10 @@ private boolean handleProofResult(AutoConfirmResult result, Trade trade) { } accountAgeWitnessService.maybeSignWitness(trade); // transition the trade to step 4: - ((SellerTrade) trade).onFiatPaymentReceived(() -> { }, - errorMessage -> { }); + ((SellerTrade) trade).onFiatPaymentReceived(() -> { + }, + errorMessage -> { + }); return success; } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java index 64a852a8c24..ebbbee968ba 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java @@ -17,7 +17,7 @@ package bisq.core.trade.asset.xmr; -import bisq.core.trade.AutoConfirmResult; +import bisq.core.trade.XmrAutoConfirmResult; import bisq.asset.CryptoNoteAddressValidator; @@ -68,57 +68,57 @@ public String getKey() { return txHash + "|" + serviceAddress; } - public AutoConfirmResult checkApiResponse(String jsonTxt) { + public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Empty json"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Empty json"); } // there should always be "data" and "status" at the top level if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing data / status fields"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing data / status fields"); } JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); if (jsonStatus.matches("fail")) { // the API returns "fail" until the transaction has successfully reached the mempool. // we return TX_NOT_FOUND which will cause a retry later - return new AutoConfirmResult(AutoConfirmResult.State.TX_NOT_FOUND, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND, null); } else if (!jsonStatus.matches("success")) { - return new AutoConfirmResult(AutoConfirmResult.State.API_FAILURE, "Unhandled status value"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_FAILURE, "Unhandled status value"); } // validate that the address matches JsonElement jsonAddress = jsonData.get("address"); if (jsonAddress == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing address field"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing address field"); } else { String expectedAddressHex = CryptoNoteAddressValidator.convertToRawHex(this.recipientAddress); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return new AutoConfirmResult(AutoConfirmResult.State.ADDRESS_INVALID, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.ADDRESS_INVALID, null); } } // validate that the txhash matches JsonElement jsonTxHash = jsonData.get("tx_hash"); if (jsonTxHash == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); } else { if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return new AutoConfirmResult(AutoConfirmResult.State.TX_HASH_INVALID, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_HASH_INVALID, null); } } // validate that the txkey matches JsonElement jsonViewkey = jsonData.get("viewkey"); if (jsonViewkey == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing viewkey field"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing viewkey field"); } else { if (!jsonViewkey.getAsString().equalsIgnoreCase(this.txKey)) { log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), txKey); - return new AutoConfirmResult(AutoConfirmResult.State.TX_KEY_INVALID, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_INVALID, null); } } @@ -126,7 +126,7 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { // (except that in dev mode we let this check pass anyway) JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); if (jsonTimestamp == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_timestamp field"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_timestamp field"); } else { long tradeDateSeconds = tradeDate.getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); @@ -134,7 +134,7 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return new AutoConfirmResult(AutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); } } @@ -142,7 +142,7 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { int confirmations; JsonElement jsonConfs = jsonData.get("tx_confirmations"); if (jsonConfs == null) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); } else { confirmations = jsonConfs.getAsInt(); log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); @@ -161,23 +161,23 @@ public AutoConfirmResult checkApiResponse(String jsonTxt) { if (jsonAmount == amount || DevEnv.isDevMode()) { // any amount ok in dev mode if (confirmations < confirmsRequired) // we return TX_NOT_CONFIRMED which will cause a retry later - return new AutoConfirmResult(AutoConfirmResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); else - return new AutoConfirmResult(AutoConfirmResult.State.PROOF_OK, confirmations, confirmsRequired); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.PROOF_OK, confirmations, confirmsRequired); } } } // None of the outputs had a match entry if (!anyMatchFound) { - return new AutoConfirmResult(AutoConfirmResult.State.NO_MATCH_FOUND, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.NO_MATCH_FOUND, null); } // reaching this point means there was no matching amount - return new AutoConfirmResult(AutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); } catch (JsonParseException | NullPointerException e) { - return new AutoConfirmResult(AutoConfirmResult.State.API_INVALID, e.toString()); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, e.toString()); } } } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java index 53b8293b1fe..1003e05429e 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java @@ -17,7 +17,7 @@ package bisq.core.trade.asset.xmr; -import bisq.core.trade.AutoConfirmResult; +import bisq.core.trade.XmrAutoConfirmResult; import bisq.network.Socks5ProxyProvider; @@ -50,7 +50,7 @@ public class XmrTransferProofRequester { "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; private final XmrProofInfo xmrProofInfo; - private final Consumer resultHandler; + private final Consumer resultHandler; private final FaultHandler faultHandler; private boolean terminated; @@ -63,7 +63,7 @@ public class XmrTransferProofRequester { XmrTransferProofRequester(@Nullable Socks5ProxyProvider socks5ProxyProvider, XmrProofInfo xmrProofInfo, - Consumer resultHandler, + Consumer resultHandler, FaultHandler faultHandler) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrProofInfo.getServiceAddress()); @@ -94,7 +94,7 @@ public void request() { log.info("Request() aborted, this object has been terminated. Service: {}", httpClient.getBaseUrl()); return; } - ListenableFuture future = executorService.submit(() -> { + ListenableFuture future = executorService.submit(() -> { Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getKey()); String param = "/api/outputs?txhash=" + xmrProofInfo.getTxHash() + "&address=" + xmrProofInfo.getRecipientAddress() + @@ -102,13 +102,13 @@ public void request() { "&txprove=1"; log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - AutoConfirmResult autoConfirmResult = xmrProofInfo.checkApiResponse(json); + XmrAutoConfirmResult autoConfirmResult = xmrProofInfo.checkApiResponse(json); log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); return autoConfirmResult; }); Futures.addCallback(future, new FutureCallback<>() { - public void onSuccess(AutoConfirmResult result) { + public void onSuccess(XmrAutoConfirmResult result) { if (terminated) { log.info("API terminated from higher level: {}", httpClient.getBaseUrl()); return; @@ -127,7 +127,7 @@ public void onFailure(@NotNull Throwable throwable) { String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; faultHandler.handleFault(errorMessage, throwable); UserThread.execute(() -> resultHandler.accept( - new AutoConfirmResult(AutoConfirmResult.State.CONNECTION_FAIL, errorMessage))); + new XmrAutoConfirmResult(XmrAutoConfirmResult.State.CONNECTION_FAIL, errorMessage))); } }); } diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java index 976fc703036..151463d81e9 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java @@ -17,7 +17,7 @@ package bisq.core.trade.asset.xmr; -import bisq.core.trade.AutoConfirmResult; +import bisq.core.trade.XmrAutoConfirmResult; import bisq.network.Socks5ProxyProvider; @@ -48,7 +48,7 @@ public XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { } public void requestProof(XmrProofInfo xmrProofInfo, - Consumer resultHandler, + Consumer resultHandler, FaultHandler faultHandler) { String key = xmrProofInfo.getKey(); if (map.containsKey(key)) { diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index ed590b307c5..51e0e084297 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -33,10 +33,10 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.AutoConfirmationManager; import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.XmrAutoConfirmationManager; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; @@ -90,7 +90,7 @@ public class ProcessModel implements Model, PersistablePayload { transient private User user; transient private FilterManager filterManager; transient private AccountAgeWitnessService accountAgeWitnessService; - transient private AutoConfirmationManager autoConfirmationManager; + transient private XmrAutoConfirmationManager xmrAutoConfirmationManager; transient private TradeStatisticsManager tradeStatisticsManager; transient private ArbitratorManager arbitratorManager; transient private MediatorManager mediatorManager; @@ -247,7 +247,7 @@ public void onAllServicesInitialized(Offer offer, User user, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, - AutoConfirmationManager autoConfirmationManager, + XmrAutoConfirmationManager xmrAutoConfirmationManager, TradeStatisticsManager tradeStatisticsManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -266,7 +266,7 @@ public void onAllServicesInitialized(Offer offer, this.user = user; this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; - this.autoConfirmationManager = autoConfirmationManager; + this.xmrAutoConfirmationManager = xmrAutoConfirmationManager; this.tradeStatisticsManager = tradeStatisticsManager; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index ba8ba8b8f77..2faa9fc3353 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,7 +58,7 @@ protected void run() { String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getAutoConfirmationManager().processCounterCurrencyExtraData( + processModel.getXmrAutoConfirmationManager().processCounterCurrencyExtraData( trade, processModel.getTradeManager().getTradableList().stream()); } processModel.removeMailboxMessageAfterProcessing(trade); diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java index ab8d2a1b845..427a5cb9baa 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java @@ -1,6 +1,6 @@ package bisq.core.trade.asset.xmr; -import bisq.core.trade.AutoConfirmResult; +import bisq.core.trade.XmrAutoConfirmResult; import java.time.Instant; @@ -48,13 +48,13 @@ public void testKey() { public void testJsonRoot() { // checking what happens when bad input is provided assertTrue(xmrProofInfo.checkApiResponse( - "invalid json data").getState() == AutoConfirmResult.State.API_INVALID); + "invalid json data").getState() == XmrAutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( - "").getState() == AutoConfirmResult.State.API_INVALID); + "").getState() == XmrAutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( - "[]").getState() == AutoConfirmResult.State.API_INVALID); + "[]").getState() == XmrAutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( - "{}").getState() == AutoConfirmResult.State.API_INVALID); + "{}").getState() == XmrAutoConfirmResult.State.API_INVALID); } @Test @@ -62,34 +62,34 @@ public void testJsonTopLevel() { // testing the top level fields: data and status assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'title':''},'status':'fail'}" ) - .getState() == AutoConfirmResult.State.TX_NOT_FOUND); + .getState() == XmrAutoConfirmResult.State.TX_NOT_FOUND); assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'title':''},'missingstatus':'success'}" ) - .getState() == AutoConfirmResult.State.API_INVALID); + .getState() == XmrAutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( "{'missingdata':{'title':''},'status':'success'}" ) - .getState() == AutoConfirmResult.State.API_INVALID); + .getState() == XmrAutoConfirmResult.State.API_INVALID); } @Test public void testJsonAddress() { assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) - .getState() == AutoConfirmResult.State.API_INVALID); + .getState() == XmrAutoConfirmResult.State.API_INVALID); assertTrue(xmrProofInfo.checkApiResponse( "{'data':{'address':'e957dac7'},'status':'success'}" ) - .getState() == AutoConfirmResult.State.ADDRESS_INVALID); + .getState() == XmrAutoConfirmResult.State.ADDRESS_INVALID); } @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(missing_tx_hash).getState() - == AutoConfirmResult.State.API_INVALID); + == XmrAutoConfirmResult.State.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_hash).getState() - == AutoConfirmResult.State.TX_HASH_INVALID); + == XmrAutoConfirmResult.State.TX_HASH_INVALID); } @Test @@ -97,13 +97,13 @@ public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(missing_tx_key).getState() - == AutoConfirmResult.State.API_INVALID); + == XmrAutoConfirmResult.State.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_key).getState() - == AutoConfirmResult.State.TX_KEY_INVALID); + == XmrAutoConfirmResult.State.TX_KEY_INVALID); } @Test @@ -112,14 +112,14 @@ public void testJsonTxTimestamp() { "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(missing_tx_timestamp).getState() - == AutoConfirmResult.State.API_INVALID); + == XmrAutoConfirmResult.State.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_timestamp).getState() - == AutoConfirmResult.State.TRADE_DATE_NOT_MATCHING); + == XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING); } @Test @@ -137,25 +137,25 @@ public void testJsonTxConfirmation() { "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == AutoConfirmResult.State.PROOF_OK); + == XmrAutoConfirmResult.State.PROOF_OK); json = json.replaceFirst("777", "0"); assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == AutoConfirmResult.State.TX_NOT_CONFIRMED); + == XmrAutoConfirmResult.State.TX_NOT_CONFIRMED); json = json.replaceFirst("100000000000", "100000000001"); assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == AutoConfirmResult.State.AMOUNT_NOT_MATCHING); + == XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); assertTrue(xmrProofInfo.checkApiResponse(json).getState() - == AutoConfirmResult.State.NO_MATCH_FOUND); + == XmrAutoConfirmResult.State.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; assertTrue(xmrProofInfo.checkApiResponse(failedJson).getState() - == AutoConfirmResult.State.API_INVALID); + == XmrAutoConfirmResult.State.API_INVALID); } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index d2babf89230..9e7d6a2cea3 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -39,7 +39,6 @@ import bisq.network.p2p.NodeAddress; import bisq.common.UserThread; -import bisq.common.util.Utilities; import org.bitcoinj.core.Utils; @@ -160,8 +159,9 @@ private void addContent() { addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), FormattingUtils.formatPrice(trade.getTradePrice())); String methodText = Res.get(offer.getPaymentMethod().getId()); - if (trade.getAutoConfirmResult() != null && trade.getAutoConfirmResult().isSuccessState()) + if (trade.getAutoConfirmResult().isSuccessState()) { methodText += " (" + trade.getAutoConfirmResult().getTextStatus() + ")"; + } addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), methodText); // second group diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 0a945a7b73f..83a5decfdd7 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -117,7 +117,7 @@ protected void addContent() { GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); gridPane.getChildren().add(hBox2); GridPane.setRowSpan(hBox2, 5); - if (trade.getAutoConfirmResult() == null || !trade.getAutoConfirmResult().isSuccessState()) { + if (!trade.getAutoConfirmResult().isSuccessState()) { autoConfBadge.setVisible(false); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index c459c7ff3aa..eb805a76a3b 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -40,9 +40,9 @@ import bisq.core.payment.payload.SepaInstantAccountPayload; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; +import bisq.core.trade.AutoConfirmResult; import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.AutoConfirmResult; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -154,10 +154,7 @@ public void activate() { if (autoConfirmStatusField != null) { trade.getAutoConfirmResultProperty().addListener(autoConfirmResultListener); // display the initial value, or FEATURE_DISABLED if there is none - AutoConfirmResult autoConfirmResult = trade.getAutoConfirmResult(); - if (autoConfirmResult == null) - autoConfirmResult = new AutoConfirmResult(AutoConfirmResult.State.FEATURE_DISABLED); - autoConfirmStatusField.setText(autoConfirmResult.getTextStatus()); + autoConfirmStatusField.setText(trade.getAutoConfirmResult().getTextStatus()); } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 7b96a55f857..10a27fd3757 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1397,7 +1397,7 @@ message Trade { } message AutoConfirmResult { - string stateName = 1; // name of AutoConfirmResult.State enum + string stateName = 1; } message BuyerAsMakerTrade { From e5aee1ca0768c33c6141219c1760baf1913adb26 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 15:50:15 -0500 Subject: [PATCH 18/85] No functionality has been changed by that refactoring, just moved classes and renamed package and adjusted access modifiers. --- core/src/main/java/bisq/core/trade/Trade.java | 4 +++- core/src/main/java/bisq/core/trade/TradeManager.java | 1 + .../bisq/core/trade/{ => autoconf}/AutoConfirmResult.java | 6 ++++-- .../trade/{ => autoconf/xmr}/XmrAutoConfirmResult.java | 3 ++- .../{ => autoconf/xmr}/XmrAutoConfirmationManager.java | 7 ++++--- .../core/trade/{asset => autoconf}/xmr/XmrProofInfo.java | 4 +--- .../{asset => autoconf}/xmr/XmrTransferProofRequester.java | 4 +--- .../{asset => autoconf}/xmr/XmrTransferProofService.java | 4 +--- .../{asset => autoconf}/xmr/XmrTxProofHttpClient.java | 2 +- .../main/java/bisq/core/trade/protocol/ProcessModel.java | 2 +- .../java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java | 3 ++- .../pendingtrades/steps/seller/SellerStep3View.java | 2 +- 12 files changed, 22 insertions(+), 20 deletions(-) rename core/src/main/java/bisq/core/trade/{ => autoconf}/AutoConfirmResult.java (91%) rename core/src/main/java/bisq/core/trade/{ => autoconf/xmr}/XmrAutoConfirmResult.java (98%) rename core/src/main/java/bisq/core/trade/{ => autoconf/xmr}/XmrAutoConfirmationManager.java (98%) rename core/src/main/java/bisq/core/trade/{asset => autoconf}/xmr/XmrProofInfo.java (99%) rename core/src/main/java/bisq/core/trade/{asset => autoconf}/xmr/XmrTransferProofRequester.java (98%) rename core/src/main/java/bisq/core/trade/{asset => autoconf}/xmr/XmrTransferProofService.java (97%) rename core/src/main/java/bisq/core/trade/{asset => autoconf}/xmr/XmrTxProofHttpClient.java (96%) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index dc765d52003..0393a9d6f89 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -38,6 +38,8 @@ import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; +import bisq.core.trade.autoconf.AutoConfirmResult; +import bisq.core.trade.autoconf.xmr.XmrAutoConfirmationManager; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; @@ -442,7 +444,7 @@ public AutoConfirmResult getAutoConfirmResult() { @Nullable private AutoConfirmResult autoConfirmResult; - void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { + public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { this.autoConfirmResult = autoConfirmResult; autoConfirmResultProperty.setValue(autoConfirmResult); } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 70ecd216c84..cf81ecd3dd8 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -38,6 +38,7 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; +import bisq.core.trade.autoconf.xmr.XmrAutoConfirmationManager; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; diff --git a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java similarity index 91% rename from core/src/main/java/bisq/core/trade/AutoConfirmResult.java rename to core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java index bed7463d0f9..640fc245400 100644 --- a/core/src/main/java/bisq/core/trade/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java @@ -15,7 +15,9 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.autoconf; + +import bisq.core.trade.autoconf.xmr.XmrAutoConfirmResult; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -51,7 +53,7 @@ protected AutoConfirmResult(String stateName) { // We use fromProto as kind of factory method to get the specific AutoConfirmResult @Nullable - static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { + public static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { switch (currencyCode) { case "XMR": return XmrAutoConfirmResult.fromProto(proto); diff --git a/core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java similarity index 98% rename from core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index 4bce7bedf1a..0fe7e91dd1a 100644 --- a/core/src/main/java/bisq/core/trade/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -15,9 +15,10 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.autoconf.xmr; import bisq.core.locale.Res; +import bisq.core.trade.autoconf.AutoConfirmResult; import bisq.common.proto.ProtoUtil; diff --git a/core/src/main/java/bisq/core/trade/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java similarity index 98% rename from core/src/main/java/bisq/core/trade/XmrAutoConfirmationManager.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index 67a6bbeeab8..1b0c405199f 100644 --- a/core/src/main/java/bisq/core/trade/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade; +package bisq.core.trade.autoconf.xmr; import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; @@ -24,8 +24,9 @@ import bisq.core.offer.Offer; import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.asset.xmr.XmrProofInfo; -import bisq.core.trade.asset.xmr.XmrTransferProofService; +import bisq.core.trade.Contract; +import bisq.core.trade.SellerTrade; +import bisq.core.trade.Trade; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.user.Preferences; diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java similarity index 99% rename from core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index ebbbee968ba..3a196d8fb66 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -15,9 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.asset.xmr; - -import bisq.core.trade.XmrAutoConfirmResult; +package bisq.core.trade.autoconf.xmr; import bisq.asset.CryptoNoteAddressValidator; diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java similarity index 98% rename from core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java index 1003e05429e..e6836ed60f5 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java @@ -15,9 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.asset.xmr; - -import bisq.core.trade.XmrAutoConfirmResult; +package bisq.core.trade.autoconf.xmr; import bisq.network.Socks5ProxyProvider; diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java similarity index 97% rename from core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 151463d81e9..2afafb3423f 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -15,9 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.asset.xmr; - -import bisq.core.trade.XmrAutoConfirmResult; +package bisq.core.trade.autoconf.xmr; import bisq.network.Socks5ProxyProvider; diff --git a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java similarity index 96% rename from core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java index d270a39a2ce..700dfa7a794 100644 --- a/core/src/main/java/bisq/core/trade/asset/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.asset.xmr; +package bisq.core.trade.autoconf.xmr; import bisq.network.Socks5ProxyProvider; import bisq.network.http.HttpClient; diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index 51e0e084297..f3148f26bc7 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -36,7 +36,7 @@ import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.XmrAutoConfirmationManager; +import bisq.core.trade.autoconf.xmr.XmrAutoConfirmationManager; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java index 427a5cb9baa..6da3b92cf60 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java @@ -1,6 +1,7 @@ package bisq.core.trade.asset.xmr; -import bisq.core.trade.XmrAutoConfirmResult; +import bisq.core.trade.autoconf.xmr.XmrAutoConfirmResult; +import bisq.core.trade.autoconf.xmr.XmrProofInfo; import java.time.Instant; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index eb805a76a3b..81e017d666f 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -40,9 +40,9 @@ import bisq.core.payment.payload.SepaInstantAccountPayload; import bisq.core.payment.payload.USPostalMoneyOrderAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload; -import bisq.core.trade.AutoConfirmResult; import bisq.core.trade.Contract; import bisq.core.trade.Trade; +import bisq.core.trade.autoconf.AutoConfirmResult; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; From bfab6ffc2e3d9a4d38bd20997d5099fce0936b2b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 15:51:43 -0500 Subject: [PATCH 19/85] Replace success/failure with booleans --- .../autoconf/xmr/XmrAutoConfirmationManager.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index 1b0c405199f..a82e8961aed 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -208,23 +208,20 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra } private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { - boolean success = true; - boolean failure = false; - // here we count the Trade's API results from all // different serviceAddress and figure out when all have finished int resultsCountdown = txProofResultsPending.getOrDefault(trade.getId(), 0); if (resultsCountdown < 0) { // see failure scenario below log.info("Ignoring stale API result [{}], tradeId {} due to previous error", result.getState(), trade.getShortId()); - return failure; // terminate any pending responses + return false; // terminate any pending responses } if (trade.isPayoutPublished()) { log.warn("Trade payout already published, shutting down all open API requests for this trade {}", trade.getShortId()); txProofResultsPending.remove(trade.getId()); - return failure; + return false; } if (result.isPendingState()) { @@ -232,7 +229,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { result.getState(), trade.getShortId()); trade.setAutoConfirmResult(result); // this updates the GUI with the status.. // Repeating the requests is handled in XmrTransferProofRequester - return success; + return true; } if (result.isSuccessState()) { @@ -241,7 +238,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { result.getState(), resultsCountdown, trade.getShortId()); if (resultsCountdown > 0) { txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count - return success; // not all APIs have confirmed yet + return true; // not all APIs have confirmed yet } // we've received the final PROOF_OK, all good here. txProofResultsPending.remove(trade.getId()); @@ -257,7 +254,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { }, errorMessage -> { }); - return success; + return true; } // error case. any validation error from XmrProofRequester or XmrProofInfo.check @@ -269,7 +266,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { trade.setAutoConfirmResult(result); // this updates the GUI with the status.. resultsCountdown = -1; // signal all API requestors to cease txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count - return failure; + return false; } private boolean isAutoConfDisabledByFilter() { From 5143b1ed59c0b0f79d3a7901c2da7b1dcd1107e1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 15:56:44 -0500 Subject: [PATCH 20/85] Apply suggested changes from code analysis --- .../trade/autoconf/AutoConfirmResult.java | 3 ++- .../autoconf/xmr/XmrAutoConfirmResult.java | 4 ++-- .../xmr/XmrAutoConfirmationManager.java | 21 ++++++++++--------- .../core/trade/autoconf/xmr/XmrProofInfo.java | 10 ++++----- .../xmr/XmrTransferProofRequester.java | 8 +++---- .../autoconf/xmr/XmrTransferProofService.java | 8 +++---- .../autoconf/xmr/XmrTxProofHttpClient.java | 2 +- 7 files changed, 29 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java index 640fc245400..535577c237d 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java @@ -48,12 +48,13 @@ protected AutoConfirmResult(String stateName) { /////////////////////////////////////////////////////////////////////////////////////////// - // PROTOBUF + // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// // We use fromProto as kind of factory method to get the specific AutoConfirmResult @Nullable public static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { + //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { case "XMR": return XmrAutoConfirmResult.fromProto(proto); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index 0fe7e91dd1a..00c6c20ddb7 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -87,7 +87,7 @@ public XmrAutoConfirmResult(State state, @Nullable String errorMsg) { /////////////////////////////////////////////////////////////////////////////////////////// - // PROTOBUF + // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// @Override @@ -131,7 +131,7 @@ public boolean isSuccessState() { return (state == State.PROOF_OK); } - public boolean isErrorState() { + private boolean isErrorState() { return (!isPendingState() && !isSuccessState()); } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index a82e8961aed..98e7db2773d 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -61,21 +61,21 @@ public class XmrAutoConfirmationManager { private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; - private Map txProofResultsPending = new HashMap<>(); + private final Map txProofResultsPending = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @Inject - XmrAutoConfirmationManager(FilterManager filterManager, - Preferences preferences, - XmrTransferProofService xmrTransferProofService, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager, - P2PService p2PService, - WalletsSetup walletsSetup, - AccountAgeWitnessService accountAgeWitnessService + private XmrAutoConfirmationManager(FilterManager filterManager, + Preferences preferences, + XmrTransferProofService xmrTransferProofService, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, + P2PService p2PService, + WalletsSetup walletsSetup, + AccountAgeWitnessService accountAgeWitnessService ) { this.filterManager = filterManager; this.preferences = preferences; @@ -127,6 +127,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); if (offer.getCurrencyCode().equals("XMR")) { + //noinspection UnnecessaryLocalVariable String txKey = counterCurrencyExtraData; if (!txHash.matches("[a-fA-F0-9]{64}") || !txKey.matches("[a-fA-F0-9]{64}")) { @@ -264,7 +265,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", result.getState(), trade.getShortId()); trade.setAutoConfirmResult(result); // this updates the GUI with the status.. - resultsCountdown = -1; // signal all API requestors to cease + resultsCountdown = -1; // signal all API requesters to cease txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count return false; } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index 3a196d8fb66..4577456e422 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -98,7 +98,7 @@ public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { } } - // validate that the txhash matches + // validate that the txHash matches JsonElement jsonTxHash = jsonData.get("tx_hash"); if (jsonTxHash == null) { return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); @@ -109,7 +109,7 @@ public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { } } - // validate that the txkey matches + // validate that the txKey matches JsonElement jsonViewkey = jsonData.get("viewkey"); if (jsonViewkey == null) { return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing viewkey field"); @@ -138,11 +138,11 @@ public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { // calculate how many confirms are still needed int confirmations; - JsonElement jsonConfs = jsonData.get("tx_confirmations"); - if (jsonConfs == null) { + JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); + if (jsonConfirmations == null) { return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); } else { - confirmations = jsonConfs.getAsInt(); + confirmations = jsonConfirmations.getAsInt(); log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java index e6836ed60f5..d947f22a837 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java @@ -39,10 +39,10 @@ import javax.annotation.Nullable; @Slf4j -public class XmrTransferProofRequester { +class XmrTransferProofRequester { // these settings are not likely to change and therefore not put into Config - private static long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); - private static long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); + private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); + private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofRequester", 3, 5, 10 * 60); @@ -52,7 +52,7 @@ public class XmrTransferProofRequester { private final FaultHandler faultHandler; private boolean terminated; - private long firstRequest; + private final long firstRequest; /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 2afafb3423f..f6841d211ad 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -35,13 +35,13 @@ * Manages the XMR transfers proof requests for multiple trades. */ @Slf4j -public class XmrTransferProofService { - private Map map = new HashMap<>(); +class XmrTransferProofService { + private final Map map = new HashMap<>(); @Nullable - private Socks5ProxyProvider socks5ProxyProvider; + private final Socks5ProxyProvider socks5ProxyProvider; @Inject - public XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { + private XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { socks5ProxyProvider = provider; } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java index 700dfa7a794..0d74b86ce55 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java @@ -24,7 +24,7 @@ import javax.annotation.Nullable; -public class XmrTxProofHttpClient extends HttpClient { +class XmrTxProofHttpClient extends HttpClient { @Inject public XmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); From 77c203e87e02517f302155ba11561163c4a41378 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 16:08:07 -0500 Subject: [PATCH 21/85] No functional change, pure refactoring Move to convertToRawHex CryptoNoteUtils as well as the classes inside the validator. --- .../asset/CryptoNoteAddressValidator.java | 229 +--------------- .../main/java/bisq/asset/CryptoNoteUtils.java | 247 ++++++++++++++++++ .../trade/autoconf/AutoConfirmResult.java | 1 + .../core/trade/autoconf/xmr/XmrProofInfo.java | 4 +- 4 files changed, 251 insertions(+), 230 deletions(-) create mode 100644 assets/src/main/java/bisq/asset/CryptoNoteUtils.java diff --git a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java index 603461dbb0d..295c1e0af8a 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java @@ -17,16 +17,6 @@ package bisq.asset; -import org.bitcoinj.core.Utils; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -import java.math.BigInteger; - -import java.util.Arrays; -import java.util.Map; - /** * {@link AddressValidator} for Base58-encoded Cryptonote addresses. * @@ -49,7 +39,7 @@ public CryptoNoteAddressValidator(long... validPrefixes) { @Override public AddressValidationResult validate(String address) { try { - long prefix = MoneroBase58.decodeAddress(address, this.validateChecksum); + long prefix = CryptoNoteUtils.MoneroBase58.decodeAddress(address, this.validateChecksum); for (long validPrefix : this.validPrefixes) { if (prefix == validPrefix) { return AddressValidationResult.validAddress(); @@ -60,221 +50,4 @@ public AddressValidationResult validate(String address) { return AddressValidationResult.invalidStructure(); } } - - public static String convertToRawHex(String address) { - try { - byte[] decoded = MoneroBase58.decode(address); - // omit the type (1st byte) and checksum (last 4 byte) - byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); - return Utils.HEX.encode(slice); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} - -class Keccak { - - private static final int BLOCK_SIZE = 136; - private static final int LONGS_PER_BLOCK = BLOCK_SIZE / 8; - private static final int KECCAK_ROUNDS = 24; - private static final long[] KECCAKF_RNDC = { - 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808aL, - 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, - 0x8000000080008081L, 0x8000000000008009L, 0x000000000000008aL, - 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL, - 0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, - 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, - 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, - 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L - }; - private static final int[] KECCAKF_ROTC = { - 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, - 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 - }; - private static final int[] KECCAKF_PILN = { - 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, - 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 - }; - - private static long rotateLeft(long value, int shift) { - return (value << shift) | (value >>> (64 - shift)); - } - - private static void keccakf(long[] st, int rounds) { - long[] bc = new long[5]; - - for (int round = 0; round < rounds; ++round) { - for (int i = 0; i < 5; ++i) { - bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20]; - } - - for (int i = 0; i < 5; i++) { - long t = bc[(i + 4) % 5] ^ rotateLeft(bc[(i + 1) % 5], 1); - for (int j = 0; j < 25; j += 5) { - st[j + i] ^= t; - } - } - - long t = st[1]; - for (int i = 0; i < 24; ++i) { - int j = KECCAKF_PILN[i]; - bc[0] = st[j]; - st[j] = rotateLeft(t, KECCAKF_ROTC[i]); - t = bc[0]; - } - - for (int j = 0; j < 25; j += 5) { - for (int i = 0; i < 5; i++) { - bc[i] = st[j + i]; - } - for (int i = 0; i < 5; i++) { - st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; - } - } - - st[0] ^= KECCAKF_RNDC[round]; - } - } - - public static ByteBuffer keccak1600(ByteBuffer input) { - input.order(ByteOrder.LITTLE_ENDIAN); - - int fullBlocks = input.remaining() / BLOCK_SIZE; - long[] st = new long[25]; - for (int block = 0; block < fullBlocks; ++block) { - for (int index = 0; index < LONGS_PER_BLOCK; ++index) { - st[index] ^= input.getLong(); - } - keccakf(st, KECCAK_ROUNDS); - } - - ByteBuffer lastBlock = ByteBuffer.allocate(144).order(ByteOrder.LITTLE_ENDIAN); - lastBlock.put(input); - lastBlock.put((byte)1); - int paddingOffset = BLOCK_SIZE - 1; - lastBlock.put(paddingOffset, (byte)(lastBlock.get(paddingOffset) | 0x80)); - lastBlock.rewind(); - - for (int index = 0; index < LONGS_PER_BLOCK; ++index) { - st[index] ^= lastBlock.getLong(); - } - - keccakf(st, KECCAK_ROUNDS); - - ByteBuffer result = ByteBuffer.allocate(32); - result.slice().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer().put(st, 0, 4); - return result; - } -} - -class MoneroBase58 { - - private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; - private static final BigInteger ALPHABET_SIZE = BigInteger.valueOf(ALPHABET.length()); - private static final int FULL_DECODED_BLOCK_SIZE = 8; - private static final int FULL_ENCODED_BLOCK_SIZE = 11; - private static final BigInteger UINT64_MAX = new BigInteger("18446744073709551615"); - private static final Map DECODED_CHUNK_LENGTH = Map.of( 2, 1, - 3, 2, - 5, 3, - 6, 4, - 7, 5, - 9, 6, - 10, 7, - 11, 8); - - private static void decodeChunk(String input, - int inputOffset, - int inputLength, - byte[] decoded, - int decodedOffset, - int decodedLength) throws Exception { - - BigInteger result = BigInteger.ZERO; - - BigInteger order = BigInteger.ONE; - for (int index = inputOffset + inputLength; index != inputOffset; order = order.multiply(ALPHABET_SIZE)) { - char character = input.charAt(--index); - int digit = ALPHABET.indexOf(character); - if (digit == -1) { - throw new Exception("invalid character " + character); - } - result = result.add(order.multiply(BigInteger.valueOf(digit))); - if (result.compareTo(UINT64_MAX) > 0) { - throw new Exception("64-bit unsigned integer overflow " + result.toString()); - } - } - - BigInteger maxCapacity = BigInteger.ONE.shiftLeft(8 * decodedLength); - if (result.compareTo(maxCapacity) >= 0) { - throw new Exception("capacity overflow " + result.toString()); - } - - for (int index = decodedOffset + decodedLength; index != decodedOffset; result = result.shiftRight(8)) { - decoded[--index] = result.byteValue(); - } - } - - public static byte[] decode(String input) throws Exception { - if (input.length() == 0) { - return new byte[0]; - } - - int chunks = input.length() / FULL_ENCODED_BLOCK_SIZE; - int lastEncodedSize = input.length() % FULL_ENCODED_BLOCK_SIZE; - int lastChunkSize = lastEncodedSize > 0 ? DECODED_CHUNK_LENGTH.get(lastEncodedSize) : 0; - - byte[] result = new byte[chunks * FULL_DECODED_BLOCK_SIZE + lastChunkSize]; - int inputOffset = 0; - int resultOffset = 0; - for (int chunk = 0; chunk < chunks; ++chunk, - inputOffset += FULL_ENCODED_BLOCK_SIZE, - resultOffset += FULL_DECODED_BLOCK_SIZE) { - decodeChunk(input, inputOffset, FULL_ENCODED_BLOCK_SIZE, result, resultOffset, FULL_DECODED_BLOCK_SIZE); - } - if (lastChunkSize > 0) { - decodeChunk(input, inputOffset, lastEncodedSize, result, resultOffset, lastChunkSize); - } - - return result; - } - - private static long readVarInt(ByteBuffer buffer) { - long result = 0; - for (int shift = 0; ; shift += 7) { - byte current = buffer.get(); - result += (current & 0x7fL) << shift; - if ((current & 0x80L) == 0) { - break; - } - } - return result; - } - - public static long decodeAddress(String address, boolean validateChecksum) throws Exception { - byte[] decoded = decode(address); - - int checksumSize = 4; - if (decoded.length < checksumSize) { - throw new Exception("invalid length"); - } - - ByteBuffer decodedAddress = ByteBuffer.wrap(decoded, 0, decoded.length - checksumSize); - - long prefix = readVarInt(decodedAddress.slice()); - if (!validateChecksum) { - return prefix; - } - - ByteBuffer fastHash = Keccak.keccak1600(decodedAddress.slice()); - int checksum = fastHash.getInt(); - int expected = ByteBuffer.wrap(decoded, decoded.length - checksumSize, checksumSize).getInt(); - if (checksum != expected) { - throw new Exception(String.format("invalid checksum %08X, expected %08X", checksum, expected)); - } - - return prefix; - } } diff --git a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java new file mode 100644 index 00000000000..425fbe19b2e --- /dev/null +++ b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java @@ -0,0 +1,247 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.asset; + +import org.bitcoinj.core.Utils; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import java.math.BigInteger; + +import java.util.Arrays; +import java.util.Map; + +public class CryptoNoteUtils { + public static String convertToRawHex(String address) { + try { + byte[] decoded = MoneroBase58.decode(address); + // omit the type (1st byte) and checksum (last 4 byte) + byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); + return Utils.HEX.encode(slice); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + static class Keccak { + private static final int BLOCK_SIZE = 136; + private static final int LONGS_PER_BLOCK = BLOCK_SIZE / 8; + private static final int KECCAK_ROUNDS = 24; + private static final long[] KECCAKF_RNDC = { + 0x0000000000000001L, 0x0000000000008082L, 0x800000000000808aL, + 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, + 0x8000000080008081L, 0x8000000000008009L, 0x000000000000008aL, + 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL, + 0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, + 0x8000000000008003L, 0x8000000000008002L, 0x8000000000000080L, + 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, + 0x8000000000008080L, 0x0000000080000001L, 0x8000000080008008L + }; + private static final int[] KECCAKF_ROTC = { + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 + }; + private static final int[] KECCAKF_PILN = { + 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 + }; + + private static long rotateLeft(long value, int shift) { + return (value << shift) | (value >>> (64 - shift)); + } + + private static void keccakf(long[] st, int rounds) { + long[] bc = new long[5]; + + for (int round = 0; round < rounds; ++round) { + for (int i = 0; i < 5; ++i) { + bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20]; + } + + for (int i = 0; i < 5; i++) { + long t = bc[(i + 4) % 5] ^ rotateLeft(bc[(i + 1) % 5], 1); + for (int j = 0; j < 25; j += 5) { + st[j + i] ^= t; + } + } + + long t = st[1]; + for (int i = 0; i < 24; ++i) { + int j = KECCAKF_PILN[i]; + bc[0] = st[j]; + st[j] = rotateLeft(t, KECCAKF_ROTC[i]); + t = bc[0]; + } + + for (int j = 0; j < 25; j += 5) { + for (int i = 0; i < 5; i++) { + bc[i] = st[j + i]; + } + for (int i = 0; i < 5; i++) { + st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; + } + } + + st[0] ^= KECCAKF_RNDC[round]; + } + } + + static ByteBuffer keccak1600(ByteBuffer input) { + input.order(ByteOrder.LITTLE_ENDIAN); + + int fullBlocks = input.remaining() / BLOCK_SIZE; + long[] st = new long[25]; + for (int block = 0; block < fullBlocks; ++block) { + for (int index = 0; index < LONGS_PER_BLOCK; ++index) { + st[index] ^= input.getLong(); + } + keccakf(st, KECCAK_ROUNDS); + } + + ByteBuffer lastBlock = ByteBuffer.allocate(144).order(ByteOrder.LITTLE_ENDIAN); + lastBlock.put(input); + lastBlock.put((byte) 1); + int paddingOffset = BLOCK_SIZE - 1; + lastBlock.put(paddingOffset, (byte) (lastBlock.get(paddingOffset) | 0x80)); + lastBlock.rewind(); + + for (int index = 0; index < LONGS_PER_BLOCK; ++index) { + st[index] ^= lastBlock.getLong(); + } + + keccakf(st, KECCAK_ROUNDS); + + ByteBuffer result = ByteBuffer.allocate(32); + result.slice().order(ByteOrder.LITTLE_ENDIAN).asLongBuffer().put(st, 0, 4); + return result; + } + } + + static class MoneroBase58 { + + private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private static final BigInteger ALPHABET_SIZE = BigInteger.valueOf(ALPHABET.length()); + private static final int FULL_DECODED_BLOCK_SIZE = 8; + private static final int FULL_ENCODED_BLOCK_SIZE = 11; + private static final BigInteger UINT64_MAX = new BigInteger("18446744073709551615"); + private static final Map DECODED_CHUNK_LENGTH = Map.of(2, 1, + 3, 2, + 5, 3, + 6, 4, + 7, 5, + 9, 6, + 10, 7, + 11, 8); + + private static void decodeChunk(String input, + int inputOffset, + int inputLength, + byte[] decoded, + int decodedOffset, + int decodedLength) throws Exception { + + BigInteger result = BigInteger.ZERO; + + BigInteger order = BigInteger.ONE; + for (int index = inputOffset + inputLength; index != inputOffset; order = order.multiply(ALPHABET_SIZE)) { + char character = input.charAt(--index); + int digit = ALPHABET.indexOf(character); + if (digit == -1) { + throw new Exception("invalid character " + character); + } + result = result.add(order.multiply(BigInteger.valueOf(digit))); + if (result.compareTo(UINT64_MAX) > 0) { + throw new Exception("64-bit unsigned integer overflow " + result.toString()); + } + } + + BigInteger maxCapacity = BigInteger.ONE.shiftLeft(8 * decodedLength); + if (result.compareTo(maxCapacity) >= 0) { + throw new Exception("capacity overflow " + result.toString()); + } + + for (int index = decodedOffset + decodedLength; index != decodedOffset; result = result.shiftRight(8)) { + decoded[--index] = result.byteValue(); + } + } + + public static byte[] decode(String input) throws Exception { + if (input.length() == 0) { + return new byte[0]; + } + + int chunks = input.length() / FULL_ENCODED_BLOCK_SIZE; + int lastEncodedSize = input.length() % FULL_ENCODED_BLOCK_SIZE; + int lastChunkSize = lastEncodedSize > 0 ? DECODED_CHUNK_LENGTH.get(lastEncodedSize) : 0; + + byte[] result = new byte[chunks * FULL_DECODED_BLOCK_SIZE + lastChunkSize]; + int inputOffset = 0; + int resultOffset = 0; + for (int chunk = 0; chunk < chunks; ++chunk, + inputOffset += FULL_ENCODED_BLOCK_SIZE, + resultOffset += FULL_DECODED_BLOCK_SIZE) { + decodeChunk(input, inputOffset, FULL_ENCODED_BLOCK_SIZE, result, resultOffset, FULL_DECODED_BLOCK_SIZE); + } + if (lastChunkSize > 0) { + decodeChunk(input, inputOffset, lastEncodedSize, result, resultOffset, lastChunkSize); + } + + return result; + } + + private static long readVarInt(ByteBuffer buffer) { + long result = 0; + for (int shift = 0; ; shift += 7) { + byte current = buffer.get(); + result += (current & 0x7fL) << shift; + if ((current & 0x80L) == 0) { + break; + } + } + return result; + } + + static long decodeAddress(String address, boolean validateChecksum) throws Exception { + byte[] decoded = decode(address); + + int checksumSize = 4; + if (decoded.length < checksumSize) { + throw new Exception("invalid length"); + } + + ByteBuffer decodedAddress = ByteBuffer.wrap(decoded, 0, decoded.length - checksumSize); + + long prefix = readVarInt(decodedAddress.slice()); + if (!validateChecksum) { + return prefix; + } + + ByteBuffer fastHash = Keccak.keccak1600(decodedAddress.slice()); + int checksum = fastHash.getInt(); + int expected = ByteBuffer.wrap(decoded, decoded.length - checksumSize, checksumSize).getInt(); + if (checksum != expected) { + throw new Exception(String.format("invalid checksum %08X, expected %08X", checksum, expected)); + } + + return prefix; + } + } +} + diff --git a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java index 535577c237d..b0f236814bd 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java @@ -32,6 +32,7 @@ public abstract class AutoConfirmResult { public static AutoConfirmResult fromCurrencyCode(String currencyCode) { + //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { case "XMR": return new XmrAutoConfirmResult(); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index 4577456e422..a12b71a3ea0 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -17,7 +17,7 @@ package bisq.core.trade.autoconf.xmr; -import bisq.asset.CryptoNoteAddressValidator; +import bisq.asset.CryptoNoteUtils; import bisq.common.app.DevEnv; @@ -91,7 +91,7 @@ public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { if (jsonAddress == null) { return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing address field"); } else { - String expectedAddressHex = CryptoNoteAddressValidator.convertToRawHex(this.recipientAddress); + String expectedAddressHex = CryptoNoteUtils.convertToRawHex(this.recipientAddress); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.ADDRESS_INVALID, null); From 67723fa1a733d017f6761d9f0993635c4867df2c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 16:43:32 -0500 Subject: [PATCH 22/85] Do not pass over xmrAutoConfirmationManager to ProcessModel but use a getter from tradeManager. Avoids boilerplate code (I know there is more in the existing code to optimize here ;-)) --- core/src/main/java/bisq/core/trade/Trade.java | 52 +++++++++---------- .../java/bisq/core/trade/TradeManager.java | 2 +- .../core/trade/protocol/ProcessModel.java | 4 -- ...CounterCurrencyTransferStartedMessage.java | 2 +- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 0393a9d6f89..c510f8bf50f 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -39,7 +39,6 @@ import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; import bisq.core.trade.autoconf.AutoConfirmResult; -import bisq.core.trade.autoconf.xmr.XmrAutoConfirmationManager; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; @@ -431,24 +430,16 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt private long refreshInterval; private static final long MAX_REFRESH_INTERVAL = 4 * ChronoUnit.HOURS.getDuration().toMillis(); - // Added after v1.3.7 + // Added at v1.3.8 // We use that for the XMR txKey but want to keep it generic to be flexible for other payment methods or assets. @Getter @Setter private String counterCurrencyExtraData; - public AutoConfirmResult getAutoConfirmResult() { - return autoConfirmResult != null ? autoConfirmResult : AutoConfirmResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); - } - + // Added at v1.3.8 @Nullable private AutoConfirmResult autoConfirmResult; - public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { - this.autoConfirmResult = autoConfirmResult; - autoConfirmResultProperty.setValue(autoConfirmResult); - } - @Getter // This observable property can be used for UI to show a notification to user of the XMR proof status transient final private ObjectProperty autoConfirmResultProperty = new SimpleObjectProperty<>(); @@ -630,7 +621,6 @@ public void init(P2PService p2PService, User user, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, - XmrAutoConfirmationManager xmrAutoConfirmationManager, TradeStatisticsManager tradeStatisticsManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -650,7 +640,6 @@ public void init(P2PService p2PService, user, filterManager, accountAgeWitnessService, - xmrAutoConfirmationManager, tradeStatisticsManager, arbitratorManager, mediatorManager, @@ -764,6 +753,20 @@ public void appendErrorMessage(String msg) { errorMessage = errorMessage == null ? msg : errorMessage + "\n" + msg; } + public boolean allowedRefresh() { + var allowRefresh = new Date().getTime() > lastRefreshRequestDate + getRefreshInterval(); + if (!allowRefresh) { + log.info("Refresh not allowed, last refresh at {}", lastRefreshRequestDate); + } + return allowRefresh; + } + + public void logRefresh() { + var time = new Date().getTime(); + log.debug("Log refresh at {}", time); + lastRefreshRequestDate = time; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Model implementation @@ -872,6 +875,11 @@ public void setErrorMessage(String errorMessage) { errorMessageProperty.set(errorMessage); } + public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { + this.autoConfirmResult = autoConfirmResult; + autoConfirmResultProperty.setValue(autoConfirmResult); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1084,6 +1092,11 @@ public String getErrorMessage() { return errorMessageProperty.get(); } + public AutoConfirmResult getAutoConfirmResult() { + return autoConfirmResult != null ? autoConfirmResult : AutoConfirmResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); + } + + public byte[] getArbitratorBtcPubKey() { // In case we are already in a trade the arbitrator can have been revoked and we still can complete the trade // Only new trades cannot start without any arbitrator @@ -1097,19 +1110,6 @@ public byte[] getArbitratorBtcPubKey() { return arbitratorBtcPubKey; } - public boolean allowedRefresh() { - var allowRefresh = new Date().getTime() > lastRefreshRequestDate + getRefreshInterval(); - if (!allowRefresh) { - log.info("Refresh not allowed, last refresh at {}", lastRefreshRequestDate); - } - return allowRefresh; - } - - public void logRefresh() { - var time = new Date().getTime(); - log.debug("Log refresh at {}", time); - lastRefreshRequestDate = time; - } /////////////////////////////////////////////////////////////////////////////////////////// // Private diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index cf81ecd3dd8..93a5f5ee615 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -127,6 +127,7 @@ public class TradeManager implements PersistedDataHost { private final TradeStatisticsManager tradeStatisticsManager; private final ReferralIdService referralIdService; private final AccountAgeWitnessService accountAgeWitnessService; + @Getter private final XmrAutoConfirmationManager xmrAutoConfirmationManager; private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; @@ -437,7 +438,6 @@ private void initTrade(Trade trade, boolean useSavingsWallet, Coin fundsNeededFo user, filterManager, accountAgeWitnessService, - xmrAutoConfirmationManager, tradeStatisticsManager, arbitratorManager, mediatorManager, diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index f3148f26bc7..27317d7569c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -36,7 +36,6 @@ import bisq.core.trade.MakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.autoconf.xmr.XmrAutoConfirmationManager; import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; @@ -90,7 +89,6 @@ public class ProcessModel implements Model, PersistablePayload { transient private User user; transient private FilterManager filterManager; transient private AccountAgeWitnessService accountAgeWitnessService; - transient private XmrAutoConfirmationManager xmrAutoConfirmationManager; transient private TradeStatisticsManager tradeStatisticsManager; transient private ArbitratorManager arbitratorManager; transient private MediatorManager mediatorManager; @@ -247,7 +245,6 @@ public void onAllServicesInitialized(Offer offer, User user, FilterManager filterManager, AccountAgeWitnessService accountAgeWitnessService, - XmrAutoConfirmationManager xmrAutoConfirmationManager, TradeStatisticsManager tradeStatisticsManager, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, @@ -266,7 +263,6 @@ public void onAllServicesInitialized(Offer offer, this.user = user; this.filterManager = filterManager; this.accountAgeWitnessService = accountAgeWitnessService; - this.xmrAutoConfirmationManager = xmrAutoConfirmationManager; this.tradeStatisticsManager = tradeStatisticsManager; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 2faa9fc3353..b10f1fd993e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,7 +58,7 @@ protected void run() { String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getXmrAutoConfirmationManager().processCounterCurrencyExtraData( + processModel.getTradeManager().getXmrAutoConfirmationManager().processCounterCurrencyExtraData( trade, processModel.getTradeManager().getTradableList().stream()); } processModel.removeMailboxMessageAfterProcessing(trade); From 595c968f2d41e965db4989735a28499a481956be Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 16:44:45 -0500 Subject: [PATCH 23/85] Various small cleanups... --- .../autoconf/xmr/XmrTransferProofService.java | 2 +- .../bisq/core/user/AutoConfirmSettings.java | 5 ++++ .../main/java/bisq/core/user/Preferences.java | 6 ++--- .../bisq/core/user/PreferencesPayload.java | 2 +- .../resources/i18n/displayStrings.properties | 13 +++++----- .../overlays/windows/TradeDetailsWindow.java | 6 ++--- .../steps/seller/SellerStep3View.java | 2 +- proto/src/main/proto/pb.proto | 24 +++++++++---------- 8 files changed, 33 insertions(+), 27 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index f6841d211ad..e713a116317 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -32,7 +32,7 @@ import javax.annotation.Nullable; /** - * Manages the XMR transfers proof requests for multiple trades. + * Manages the XMR transfers proof requests for multiple trades and multiple services. */ @Slf4j class XmrTransferProofService { diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java index b2420c47714..e83acb7c6d3 100644 --- a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -43,6 +43,11 @@ public AutoConfirmSettings(boolean enabled, this.currencyCode = currencyCode; } + + /////////////////////////////////////////////////////////////////////////////////////////// + // PROTO BUFFER + /////////////////////////////////////////////////////////////////////////////////////////// + @Override public Message toProtoMessage() { return protobuf.AutoConfirmSettings.newBuilder() diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 8cab825896c..584e051e7d0 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -128,9 +128,9 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid // list of XMR proof providers : this list will be used if no preference has been set public static final List getDefaultXmrProofProviders() { if (DevEnv.isDevMode()) { - return new ArrayList<>(Arrays.asList( - "78.47.61.90:8081")); + return new ArrayList<>(Arrays.asList("78.47.61.90:8081")); } else { + // TODO we need at least 2 for relase return new ArrayList<>(Arrays.asList( "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); } @@ -410,7 +410,7 @@ public void setTacAcceptedV120(boolean tacAccepted) { persist(); } - // AutoConfirmSettings is currently only used for one coin: XMR. Although it could + // AutoConfirmSettings is currently only used for XMR. Although it could // potentially in the future be used for others too. In the interest of flexibility // we store it as a list in the proto definition, but in practical terms the // application is not coded to handle more than one entry. For now this API diff --git a/core/src/main/java/bisq/core/user/PreferencesPayload.java b/core/src/main/java/bisq/core/user/PreferencesPayload.java index 9894612f7bc..fbf97ea575f 100644 --- a/core/src/main/java/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/bisq/core/user/PreferencesPayload.java @@ -127,7 +127,7 @@ public final class PreferencesPayload implements UserThreadMappedPersistableEnve private int blockNotifyPort; private boolean tacAcceptedV120; - // Added after 1.3.7 + // Added at 1.3.8 private List autoConfirmSettingsList = new ArrayList<>(); diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 3678fcd0d67..c20bd807835 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -572,7 +572,7 @@ portfolio.pending.step3_seller.autoConfirmStatus=Auto-confirm status portfolio.pending.autoConfirmTxNotFound=Transaction not found portfolio.pending.autoConfirmPending=Pending portfolio.pending.autoConfirmDisabled=Disabled -portfolio.pending.autoConfirmSuccess=Auto-Confirmed +portfolio.pending.autoConfirmSuccess=Auto-confirmed portfolio.pending.step5.completed=Completed portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for at least one blockchain confirmation before starting the payment. @@ -643,8 +643,9 @@ portfolio.pending.step2_buyer.confirmStart.msg=Did you initiate the {0} payment portfolio.pending.step2_buyer.confirmStart.yes=Yes, I have started the payment portfolio.pending.step2_buyer.confirmStart.warningTitle=You have not provided proof of payment portfolio.pending.step2_buyer.confirmStart.warning=You have not entered the transaction ID and the transaction key.\n\n\ - By not providing this data the peer cannot use the auto confirm feature to release the BTC as soon the XMR has been received.\n\ - Beside that, Bisq requires that the sender of the XMR transaction is able to provide this information to the mediator or arbitrator in case of a dispute. + By not providing this data the peer cannot use the auto-confirm feature to release the BTC as soon the XMR has been received.\n\ + Beside that, Bisq requires that the sender of the XMR transaction is able to provide this information to the mediator or arbitrator in case of a dispute.\n\ + See more details on the Bisq wiki: https://bisq.wiki/Trading_Monero#Auto-confirming_trades portfolio.pending.step2_buyer.confirmStart.warningButton=Ignore and continue anyway portfolio.pending.step2_seller.waitPayment.headline=Wait for payment portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information @@ -1069,7 +1070,7 @@ setting.preferences.avoidStandbyMode=Avoid standby mode setting.preferences.autoConfirmXMR=XMR auto-confirm setting.preferences.autoConfirmEnabled=Enabled setting.preferences.autoConfirmRequiredConfirmations=Required confirmations -setting.preferences.autoConfirmMaxTradeSize=Max trade size (BTC) +setting.preferences.autoConfirmMaxTradeSize=Max. trade amount (BTC) setting.preferences.autoConfirmServiceAddresses=Service addresses setting.preferences.deviationToLarge=Values higher than {0}% are not allowed. setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte) @@ -1349,7 +1350,7 @@ https://bisq.wiki/Trading_Monero#Proving_payments\n\n\ Failure to provide the required transaction data will result in losing disputes.\n\n\ Also note that Bisq now offers automatic confirming for XMR transactions to make trades quicker, \ but you need to enable it in Settings.\n\n\ -See the wiki for more about the auto-confirm feature:\n\ +See the wiki for more information about the auto-confirm feature:\n\ https://bisq.wiki/Trading_Monero#Auto-confirming_trades # suppress inspection "TrailingSpacesInProperty" account.altcoin.popup.msr.msg=Trading MSR on Bisq requires that you understand and fulfill \ @@ -2450,7 +2451,7 @@ filterWindow.priceRelayNode=Filtered price relay nodes (comma sep. onion address filterWindow.btcNode=Filtered Bitcoin nodes (comma sep. addresses + port) filterWindow.preventPublicBtcNetwork=Prevent usage of public Bitcoin network filterWindow.disableDao=Disable DAO -filterWindow.disableAutoConf=Disable auto-confirmation (altcoins) +filterWindow.disableAutoConf=Disable auto-confirm filterWindow.disableDaoBelowVersion=Min. version required for DAO filterWindow.disableTradeBelowVersion=Min. version required for trading filterWindow.add=Add filter diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 9e7d6a2cea3..ecf34d3109b 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -158,11 +158,11 @@ private void addContent() { DisplayUtils.formatVolumeWithCode(trade.getTradeVolume())); addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), FormattingUtils.formatPrice(trade.getTradePrice())); - String methodText = Res.get(offer.getPaymentMethod().getId()); + String paymentMethodText = Res.get(offer.getPaymentMethod().getId()); if (trade.getAutoConfirmResult().isSuccessState()) { - methodText += " (" + trade.getAutoConfirmResult().getTextStatus() + ")"; + paymentMethodText += " (" + trade.getAutoConfirmResult().getTextStatus() + ")"; } - addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), methodText); + addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), paymentMethodText); // second group rows = 6; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 81e017d666f..777d7f0f82f 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -225,7 +225,7 @@ protected void addContent() { GridPane.setRowSpan(titledGroupBg, 4); } - if (isBlockChain && trade.getOffer().getCurrencyCode().equalsIgnoreCase("XMR")) { + if (isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR")) { autoConfirmStatusField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, Res.get("portfolio.pending.step3_seller.autoConfirmStatus"), "", Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE).second; diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 10a27fd3757..7a3e231bee4 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1396,10 +1396,6 @@ message Trade { AutoConfirmResult auto_confirm_result = 38; } -message AutoConfirmResult { - string stateName = 1; -} - message BuyerAsMakerTrade { Trade trade = 1; } @@ -1561,6 +1557,14 @@ message PreferencesPayload { repeated AutoConfirmSettings auto_confirm_settings = 56; } +message AutoConfirmSettings { + bool enabled = 1; + int32 required_confirmations = 2; + int64 trade_limit = 3; + repeated string service_addresses = 4; + string currency_code = 5; +} + /////////////////////////////////////////////////////////////////////////////////////////// // UserPayload /////////////////////////////////////////////////////////////////////////////////////////// @@ -1583,6 +1587,10 @@ message UserPayload { RefundAgent registered_refund_agent = 15; } +message AutoConfirmResult { + string stateName = 1; +} + /////////////////////////////////////////////////////////////////////////////////////////// // DAO /////////////////////////////////////////////////////////////////////////////////////////// @@ -2042,14 +2050,6 @@ message TradeCurrency { } } -message AutoConfirmSettings { - bool enabled = 1; - int32 required_confirmations = 2; - int64 trade_limit = 3; - repeated string service_addresses = 4; - string currency_code = 5; -} - message CryptoCurrency { bool is_asset = 1; } From 2fb625642a232463fa5799e6cdf1f82d4f6e19fa Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 16:45:46 -0500 Subject: [PATCH 24/85] Refactoring: Rename getTextStatus to getStatusAsDisplayString --- .../main/java/bisq/core/trade/autoconf/AutoConfirmResult.java | 2 +- .../bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java | 2 +- .../desktop/main/overlays/windows/TradeDetailsWindow.java | 2 +- .../portfolio/pendingtrades/steps/seller/SellerStep3View.java | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java index b0f236814bd..40b48c7dafa 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java @@ -73,5 +73,5 @@ public static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto, Stri abstract public boolean isSuccessState(); - abstract public String getTextStatus(); + abstract public String getStatusAsDisplayString(); } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index 00c6c20ddb7..b08acb86acc 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -105,7 +105,7 @@ public static XmrAutoConfirmResult fromProto(protobuf.AutoConfirmResult proto) { // API /////////////////////////////////////////////////////////////////////////////////////////// - public String getTextStatus() { + public String getStatusAsDisplayString() { switch (state) { case TX_NOT_CONFIRMED: return Res.get("portfolio.pending.autoConfirmPending") diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index ecf34d3109b..05525a32afd 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -160,7 +160,7 @@ private void addContent() { FormattingUtils.formatPrice(trade.getTradePrice())); String paymentMethodText = Res.get(offer.getPaymentMethod().getId()); if (trade.getAutoConfirmResult().isSuccessState()) { - paymentMethodText += " (" + trade.getAutoConfirmResult().getTextStatus() + ")"; + paymentMethodText += " (" + trade.getAutoConfirmResult().getStatusAsDisplayString() + ")"; } addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), paymentMethodText); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 777d7f0f82f..fd6e0b50401 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -88,7 +88,7 @@ public SellerStep3View(PendingTradesViewModel model) { // we listen for updates on the trade autoConfirmResult field autoConfirmResultListener = (observable, oldValue, newValue) -> { - autoConfirmStatusField.setText(newValue.getTextStatus()); + autoConfirmStatusField.setText(newValue.getStatusAsDisplayString()); }; } @@ -154,7 +154,7 @@ public void activate() { if (autoConfirmStatusField != null) { trade.getAutoConfirmResultProperty().addListener(autoConfirmResultListener); // display the initial value, or FEATURE_DISABLED if there is none - autoConfirmStatusField.setText(trade.getAutoConfirmResult().getTextStatus()); + autoConfirmStatusField.setText(trade.getAutoConfirmResult().getStatusAsDisplayString()); } } From 94c84b6f5045417a0bcc661dd12f90c34af7e61d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 16:47:39 -0500 Subject: [PATCH 25/85] Add @Override, cleanup --- .../autoconf/xmr/XmrAutoConfirmResult.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index b08acb86acc..d31467d7556 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -57,7 +57,7 @@ public enum State { /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor + // Constructors /////////////////////////////////////////////////////////////////////////////////////////// public XmrAutoConfirmResult() { @@ -105,6 +105,7 @@ public static XmrAutoConfirmResult fromProto(protobuf.AutoConfirmResult proto) { // API /////////////////////////////////////////////////////////////////////////////////////////// + @Override public String getStatusAsDisplayString() { switch (state) { case TX_NOT_CONFIRMED: @@ -123,14 +124,20 @@ public String getStatusAsDisplayString() { } } - public boolean isPendingState() { - return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); - } - + @Override public boolean isSuccessState() { return (state == State.PROOF_OK); } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + + boolean isPendingState() { + return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); + } + private boolean isErrorState() { return (!isPendingState() && !isSuccessState()); } From 2a887e1f011243408b214ac223c8d73f5e81264d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 17:01:21 -0500 Subject: [PATCH 26/85] Improve SetXmrTxKeyWindow --- .../resources/i18n/displayStrings.properties | 5 +-- .../overlays/windows/SetXmrTxKeyWindow.java | 8 +++-- .../steps/buyer/BuyerStep2View.java | 31 +++++++++++++------ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index c20bd807835..21732654414 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -641,11 +641,12 @@ portfolio.pending.step2_buyer.fasterPaymentsHolderNameInfo=Some banks might veri portfolio.pending.step2_buyer.confirmStart.headline=Confirm that you have started the payment portfolio.pending.step2_buyer.confirmStart.msg=Did you initiate the {0} payment to your trading partner? portfolio.pending.step2_buyer.confirmStart.yes=Yes, I have started the payment -portfolio.pending.step2_buyer.confirmStart.warningTitle=You have not provided proof of payment -portfolio.pending.step2_buyer.confirmStart.warning=You have not entered the transaction ID and the transaction key.\n\n\ +portfolio.pending.step2_buyer.confirmStart.proof.warningTitle=You have not provided proof of payment +portfolio.pending.step2_buyer.confirmStart.proof.noneProvided=You have not entered the transaction ID and the transaction key.\n\n\ By not providing this data the peer cannot use the auto-confirm feature to release the BTC as soon the XMR has been received.\n\ Beside that, Bisq requires that the sender of the XMR transaction is able to provide this information to the mediator or arbitrator in case of a dispute.\n\ See more details on the Bisq wiki: https://bisq.wiki/Trading_Monero#Auto-confirming_trades +portfolio.pending.step2_buyer.confirmStart.proof.invalidInput=Input is not a 32 byte hexadecimal value portfolio.pending.step2_buyer.confirmStart.warningButton=Ignore and continue anyway portfolio.pending.step2_seller.waitPayment.headline=Wait for payment portfolio.pending.step2_seller.f2fInfo.headline=Buyer's contact information diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index f9668638e97..42fcbd4969e 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -30,6 +30,8 @@ import javafx.geometry.HPos; import javafx.geometry.Insets; +import lombok.Getter; + import javax.annotation.Nullable; import static bisq.common.app.DevEnv.isDevMode; @@ -38,6 +40,8 @@ public class SetXmrTxKeyWindow extends Overlay { private InputTextField txHashInputTextField, txKeyInputTextField; + @Getter + private RegexValidator regexValidator; public SetXmrTxKeyWindow() { type = Type.Attention; @@ -53,9 +57,9 @@ public void show() { addContent(); addButtons(); - RegexValidator regexValidator = new RegexValidator(); + regexValidator = new RegexValidator(); regexValidator.setPattern("[a-fA-F0-9]{64}"); - regexValidator.setErrorMessage("Input must be a 32 byte hexadeximal number"); + regexValidator.setErrorMessage(Res.get("portfolio.pending.step2_buyer.confirmStart.proof.invalidInput")); txHashInputTextField.setValidator(regexValidator); txKeyInputTextField.setValidator(regexValidator); if (isDevMode()) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 69a5b17160f..baa265798f4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -74,6 +74,7 @@ import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; import bisq.core.user.DontShowAgainLookup; +import bisq.core.util.validation.InputValidator; import bisq.common.Timer; import bisq.common.UserThread; @@ -465,14 +466,26 @@ private void onPaymentStarted() { .onAction(() -> { String txKey = setXmrTxKeyWindow.getTxKey(); String txHash = setXmrTxKeyWindow.getTxHash(); - if (txKey.length() == 64 && txHash.length() == 64) { - trade.setCounterCurrencyExtraData(txKey); - trade.setCounterCurrencyTxId(txHash); - showConfirmPaymentStartedPopup(); - } else { - UserThread.runAfter(() -> - showProofWarningPopup(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + if (txKey == null || txHash == null || txKey.isEmpty() || txHash.isEmpty()) { + UserThread.runAfter(this::showProofWarningPopup, Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + return; } + + InputValidator.ValidationResult validateTxKey = setXmrTxKeyWindow.getRegexValidator().validate(txKey); + if (!validateTxKey.isValid) { + UserThread.runAfter(() -> new Popup().warning(validateTxKey.errorMessage).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + return; + } + + InputValidator.ValidationResult validateTxHash = setXmrTxKeyWindow.getRegexValidator().validate(txHash); + if (!validateTxHash.isValid) { + UserThread.runAfter(() -> new Popup().warning(validateTxHash.errorMessage).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + return; + } + + trade.setCounterCurrencyExtraData(txKey); + trade.setCounterCurrencyTxId(txHash); + showConfirmPaymentStartedPopup(); }) .closeButtonText(Res.get("shared.cancel")) .onClose(setXmrTxKeyWindow::hide) @@ -485,8 +498,8 @@ private void onPaymentStarted() { private void showProofWarningPopup() { Popup popup = new Popup(); - popup.headLine(Res.get("portfolio.pending.step2_buyer.confirmStart.warningTitle")) - .confirmation(Res.get("portfolio.pending.step2_buyer.confirmStart.warning")) + popup.headLine(Res.get("portfolio.pending.step2_buyer.confirmStart.proof.warningTitle")) + .confirmation(Res.get("portfolio.pending.step2_buyer.confirmStart.proof.noneProvided")) .width(700) .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.warningButton")) .onAction(this::showConfirmPaymentStartedPopup) From 38ac145213c463ebb8149486bfb1c816aa4c0467 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 17:56:44 -0500 Subject: [PATCH 27/85] Add devMode check for NO_MATCH case. Inline method --- .../bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java | 6 +----- .../java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index d31467d7556..b937fbb3371 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -80,7 +80,7 @@ public XmrAutoConfirmResult(State state, int confirmCount, int confirmsRequired) public XmrAutoConfirmResult(State state, @Nullable String errorMsg) { this(state, 0, 0); - if (isErrorState()) { + if (!isPendingState() && !isSuccessState() && state != State.FEATURE_DISABLED && state != State.UNDEFINED) { log.error(errorMsg != null ? errorMsg : state.toString()); } } @@ -137,8 +137,4 @@ public boolean isSuccessState() { boolean isPendingState() { return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); } - - private boolean isErrorState() { - return (!isPendingState() && !isSuccessState()); - } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index a12b71a3ea0..ce4ae4c4da7 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -167,7 +167,7 @@ public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { } // None of the outputs had a match entry - if (!anyMatchFound) { + if (!anyMatchFound && !DevEnv.isDevMode()) { return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.NO_MATCH_FOUND, null); } From c6c8a3e5cb74ded235cfdb6e13dfb05bf593eb0f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:03:37 -0500 Subject: [PATCH 28/85] Refactoring: - Move xmrProofInfo.checkApiResponse to XmrProofParser.parse --- .../core/trade/autoconf/xmr/XmrProofInfo.java | 124 --------------- .../trade/autoconf/xmr/XmrProofParser.java | 150 ++++++++++++++++++ .../xmr/XmrTransferProofRequester.java | 2 +- ...fInfoTest.java => XmrProofParserTest.java} | 43 ++--- 4 files changed, 173 insertions(+), 146 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java rename core/src/test/java/bisq/core/trade/asset/xmr/{XmrProofInfoTest.java => XmrProofParserTest.java} (81%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index ce4ae4c4da7..84de8630eb6 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -17,18 +17,7 @@ package bisq.core.trade.autoconf.xmr; -import bisq.asset.CryptoNoteUtils; - -import bisq.common.app.DevEnv; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; - import java.util.Date; -import java.util.concurrent.TimeUnit; import lombok.Value; import lombok.extern.slf4j.Slf4j; @@ -65,117 +54,4 @@ public XmrProofInfo( public String getKey() { return txHash + "|" + serviceAddress; } - - public XmrAutoConfirmResult checkApiResponse(String jsonTxt) { - try { - JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); - if (json == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Empty json"); - } - // there should always be "data" and "status" at the top level - if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing data / status fields"); - } - JsonObject jsonData = json.get("data").getAsJsonObject(); - String jsonStatus = json.get("status").getAsString(); - if (jsonStatus.matches("fail")) { - // the API returns "fail" until the transaction has successfully reached the mempool. - // we return TX_NOT_FOUND which will cause a retry later - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND, null); - } else if (!jsonStatus.matches("success")) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_FAILURE, "Unhandled status value"); - } - - // validate that the address matches - JsonElement jsonAddress = jsonData.get("address"); - if (jsonAddress == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing address field"); - } else { - String expectedAddressHex = CryptoNoteUtils.convertToRawHex(this.recipientAddress); - if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { - log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.ADDRESS_INVALID, null); - } - } - - // validate that the txHash matches - JsonElement jsonTxHash = jsonData.get("tx_hash"); - if (jsonTxHash == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); - } else { - if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { - log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_HASH_INVALID, null); - } - } - - // validate that the txKey matches - JsonElement jsonViewkey = jsonData.get("viewkey"); - if (jsonViewkey == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing viewkey field"); - } else { - if (!jsonViewkey.getAsString().equalsIgnoreCase(this.txKey)) { - log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), txKey); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_INVALID, null); - } - } - - // validate that the txDate matches within tolerance - // (except that in dev mode we let this check pass anyway) - JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); - if (jsonTimestamp == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_timestamp field"); - } else { - long tradeDateSeconds = tradeDate.getTime() / 1000; - long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); - // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync - if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { - log.warn("tx_timestamp {}, tradeDate: {}, difference {}", - jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); - } - } - - // calculate how many confirms are still needed - int confirmations; - JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); - if (jsonConfirmations == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); - } else { - confirmations = jsonConfirmations.getAsInt(); - log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); - } - - // iterate through the list of outputs, one of them has to match the amount we are trying to verify. - // check that the "match" field is true as well as validating the amount value - // (except that in dev mode we allow any amount as valid) - JsonArray jsonOutputs = jsonData.get("outputs").getAsJsonArray(); - boolean anyMatchFound = false; - for (int i = 0; i < jsonOutputs.size(); i++) { - JsonObject out = jsonOutputs.get(i).getAsJsonObject(); - if (out.get("match").getAsBoolean()) { - anyMatchFound = true; - long jsonAmount = out.get("amount").getAsLong(); - if (jsonAmount == amount || DevEnv.isDevMode()) { // any amount ok in dev mode - if (confirmations < confirmsRequired) - // we return TX_NOT_CONFIRMED which will cause a retry later - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); - else - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.PROOF_OK, confirmations, confirmsRequired); - } - } - } - - // None of the outputs had a match entry - if (!anyMatchFound && !DevEnv.isDevMode()) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.NO_MATCH_FOUND, null); - } - - // reaching this point means there was no matching amount - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); - - } catch (JsonParseException | NullPointerException e) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, e.toString()); - } - } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java new file mode 100644 index 00000000000..3e2515975da --- /dev/null +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java @@ -0,0 +1,150 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.autoconf.xmr; + +import bisq.asset.CryptoNoteUtils; + +import bisq.common.app.DevEnv; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import java.util.concurrent.TimeUnit; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class XmrProofParser { + static public XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { + String txHash = xmrProofInfo.getTxHash(); + try { + JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); + if (json == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Empty json"); + } + // there should always be "data" and "status" at the top level + if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing data / status fields"); + } + JsonObject jsonData = json.get("data").getAsJsonObject(); + String jsonStatus = json.get("status").getAsString(); + if (jsonStatus.matches("fail")) { + // the API returns "fail" until the transaction has successfully reached the mempool. + // we return TX_NOT_FOUND which will cause a retry later + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND, null); + } else if (!jsonStatus.matches("success")) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_FAILURE, "Unhandled status value"); + } + + // validate that the address matches + JsonElement jsonAddress = jsonData.get("address"); + if (jsonAddress == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing address field"); + } else { + String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrProofInfo.getRecipientAddress()); + if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { + log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.ADDRESS_INVALID, null); + } + } + + // validate that the txHash matches + JsonElement jsonTxHash = jsonData.get("tx_hash"); + if (jsonTxHash == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); + } else { + if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { + log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_HASH_INVALID, null); + } + } + + // validate that the txKey matches + JsonElement jsonViewkey = jsonData.get("viewkey"); + if (jsonViewkey == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing viewkey field"); + } else { + if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrProofInfo.getTxKey())) { + log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrProofInfo.getTxKey()); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_INVALID, null); + } + } + + // validate that the txDate matches within tolerance + // (except that in dev mode we let this check pass anyway) + JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); + if (jsonTimestamp == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_timestamp field"); + } else { + long tradeDateSeconds = xmrProofInfo.getTradeDate().getTime() / 1000; + long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); + // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync + if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { + log.warn("tx_timestamp {}, tradeDate: {}, difference {}", + jsonTimestamp.getAsLong(), tradeDateSeconds, difference); + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); + } + } + + // calculate how many confirms are still needed + int confirmations; + JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); + if (jsonConfirmations == null) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); + } else { + confirmations = jsonConfirmations.getAsInt(); + log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); + } + + // iterate through the list of outputs, one of them has to match the amount we are trying to verify. + // check that the "match" field is true as well as validating the amount value + // (except that in dev mode we allow any amount as valid) + JsonArray jsonOutputs = jsonData.get("outputs").getAsJsonArray(); + boolean anyMatchFound = false; + for (int i = 0; i < jsonOutputs.size(); i++) { + JsonObject out = jsonOutputs.get(i).getAsJsonObject(); + if (out.get("match").getAsBoolean()) { + anyMatchFound = true; + long jsonAmount = out.get("amount").getAsLong(); + if (jsonAmount == xmrProofInfo.getAmount() || DevEnv.isDevMode()) { // any amount ok in dev mode + int confirmsRequired = xmrProofInfo.getConfirmsRequired(); + if (confirmations < confirmsRequired) + // we return TX_NOT_CONFIRMED which will cause a retry later + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); + else + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.PROOF_OK, confirmations, confirmsRequired); + } + } + } + + // None of the outputs had a match entry + if (!anyMatchFound && !DevEnv.isDevMode()) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.NO_MATCH_FOUND, null); + } + + // reaching this point means there was no matching amount + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); + + } catch (JsonParseException | NullPointerException e) { + return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, e.toString()); + } + } +} diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java index d947f22a837..700a16cba5f 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java @@ -100,7 +100,7 @@ public void request() { "&txprove=1"; log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - XmrAutoConfirmResult autoConfirmResult = xmrProofInfo.checkApiResponse(json); + XmrAutoConfirmResult autoConfirmResult = XmrProofParser.parse(xmrProofInfo, json); log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); return autoConfirmResult; }); diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java similarity index 81% rename from core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java rename to core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java index 6da3b92cf60..d3c0bd09e99 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofInfoTest.java +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java @@ -2,6 +2,7 @@ import bisq.core.trade.autoconf.xmr.XmrAutoConfirmResult; import bisq.core.trade.autoconf.xmr.XmrProofInfo; +import bisq.core.trade.autoconf.xmr.XmrProofParser; import java.time.Instant; @@ -13,7 +14,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class XmrProofInfoTest { +public class XmrProofParserTest { private XmrProofInfo xmrProofInfo; private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae"; private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; @@ -48,36 +49,36 @@ public void testKey() { @Test public void testJsonRoot() { // checking what happens when bad input is provided - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "invalid json data").getState() == XmrAutoConfirmResult.State.API_INVALID); - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "").getState() == XmrAutoConfirmResult.State.API_INVALID); - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "[]").getState() == XmrAutoConfirmResult.State.API_INVALID); - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "{}").getState() == XmrAutoConfirmResult.State.API_INVALID); } @Test public void testJsonTopLevel() { // testing the top level fields: data and status - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'title':''},'status':'fail'}" ) .getState() == XmrAutoConfirmResult.State.TX_NOT_FOUND); - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'title':''},'missingstatus':'success'}" ) .getState() == XmrAutoConfirmResult.State.API_INVALID); - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "{'missingdata':{'title':''},'status':'success'}" ) .getState() == XmrAutoConfirmResult.State.API_INVALID); } @Test public void testJsonAddress() { - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) .getState() == XmrAutoConfirmResult.State.API_INVALID); - assertTrue(xmrProofInfo.checkApiResponse( + assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'address':'e957dac7'},'status':'success'}" ) .getState() == XmrAutoConfirmResult.State.ADDRESS_INVALID); } @@ -85,11 +86,11 @@ public void testJsonAddress() { @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertTrue(xmrProofInfo.checkApiResponse(missing_tx_hash).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_hash).getState() == XmrAutoConfirmResult.State.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_hash).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_hash).getState() == XmrAutoConfirmResult.State.TX_HASH_INVALID); } @@ -97,13 +98,13 @@ public void testJsonTxHash() { public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertTrue(xmrProofInfo.checkApiResponse(missing_tx_key).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_key).getState() == XmrAutoConfirmResult.State.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; - assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_key).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_key).getState() == XmrAutoConfirmResult.State.TX_KEY_INVALID); } @@ -112,14 +113,14 @@ public void testJsonTxTimestamp() { String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertTrue(xmrProofInfo.checkApiResponse(missing_tx_timestamp).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_timestamp).getState() == XmrAutoConfirmResult.State.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; - assertTrue(xmrProofInfo.checkApiResponse(invalid_tx_timestamp).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_timestamp).getState() == XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING); } @@ -137,26 +138,26 @@ public void testJsonTxConfirmation() { "'viewkey':'" + txKey + "', " + "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; - assertTrue(xmrProofInfo.checkApiResponse(json).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() == XmrAutoConfirmResult.State.PROOF_OK); json = json.replaceFirst("777", "0"); - assertTrue(xmrProofInfo.checkApiResponse(json).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() == XmrAutoConfirmResult.State.TX_NOT_CONFIRMED); json = json.replaceFirst("100000000000", "100000000001"); - assertTrue(xmrProofInfo.checkApiResponse(json).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() == XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); - assertTrue(xmrProofInfo.checkApiResponse(json).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() == XmrAutoConfirmResult.State.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertTrue(xmrProofInfo.checkApiResponse(failedJson).getState() + assertTrue(XmrProofParser.parse(xmrProofInfo, failedJson).getState() == XmrAutoConfirmResult.State.API_INVALID); } } From 213dffbef920aea48ede391411e93cd84347638e Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:05:18 -0500 Subject: [PATCH 29/85] Refactoring: - Rename getKey to getUID - Rename key to uid --- .../core/trade/autoconf/xmr/XmrProofInfo.java | 3 +-- .../xmr/XmrTransferProofRequester.java | 2 +- .../autoconf/xmr/XmrTransferProofService.java | 22 +++++++++---------- .../trade/asset/xmr/XmrProofParserTest.java | 6 ++--- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index 84de8630eb6..378120a113a 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -50,8 +50,7 @@ public XmrProofInfo( this.serviceAddress = serviceAddress; } - // something to uniquely identify this object by - public String getKey() { + public String getUID() { return txHash + "|" + serviceAddress; } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java index 700a16cba5f..142e085ccc0 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java @@ -93,7 +93,7 @@ public void request() { return; } ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getKey()); + Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getUID()); String param = "/api/outputs?txhash=" + xmrProofInfo.getTxHash() + "&address=" + xmrProofInfo.getRecipientAddress() + "&viewkey=" + xmrProofInfo.getTxKey() + diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index e713a116317..5d9f0e3f76b 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -48,37 +48,37 @@ private XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { public void requestProof(XmrProofInfo xmrProofInfo, Consumer resultHandler, FaultHandler faultHandler) { - String key = xmrProofInfo.getKey(); - if (map.containsKey(key)) { - log.warn("We started a proof request for key {} already", key); + String uid = xmrProofInfo.getUID(); + if (map.containsKey(uid)) { + log.warn("We started a proof request for uid {} already", uid); return; } - log.info("requesting tx proof with key {}", key); + log.info("requesting tx proof with uid {}", uid); XmrTransferProofRequester requester = new XmrTransferProofRequester( socks5ProxyProvider, xmrProofInfo, result -> { if (result.isSuccessState()) { - cleanup(key); + cleanup(uid); } resultHandler.accept(result); }, (errorMsg, throwable) -> { - cleanup(key); + cleanup(uid); faultHandler.handleFault(errorMsg, throwable); }); - map.put(key, requester); + map.put(uid, requester); requester.request(); } public void terminateRequest(XmrProofInfo xmrProofInfo) { - String key = xmrProofInfo.getKey(); - XmrTransferProofRequester requester = map.getOrDefault(key, null); + String uid = xmrProofInfo.getUID(); + XmrTransferProofRequester requester = map.getOrDefault(uid, null); if (requester != null) { - log.info("Terminating API request for request with key {}", key); + log.info("Terminating API request for request with uid {}", uid); requester.stop(); - cleanup(key); + cleanup(uid); } } diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java index d3c0bd09e99..cda9e155658 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java @@ -41,9 +41,9 @@ public void prepareMocksAndObjects() { @Test public void testKey() { - assertTrue(xmrProofInfo.getKey().contains(xmrProofInfo.getTxHash())); - assertTrue(xmrProofInfo.getKey().contains(xmrProofInfo.getServiceAddress())); - assertFalse(xmrProofInfo.getKey().contains(xmrProofInfo.getRecipientAddress())); + assertTrue(xmrProofInfo.getUID().contains(xmrProofInfo.getTxHash())); + assertTrue(xmrProofInfo.getUID().contains(xmrProofInfo.getServiceAddress())); + assertFalse(xmrProofInfo.getUID().contains(xmrProofInfo.getRecipientAddress())); } @Test From b76357e620ba60eb507ff541d1d261a9e89e2679 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:07:04 -0500 Subject: [PATCH 30/85] Refactoring: Change test package from bisq.core.trade.asset.xmr to bisq.core.trade.autoconf.xmr --- .../trade/{asset => autoconf}/xmr/XmrProofParserTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename core/src/test/java/bisq/core/trade/{asset => autoconf}/xmr/XmrProofParserTest.java (97%) diff --git a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java similarity index 97% rename from core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java rename to core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java index cda9e155658..9f235c76566 100644 --- a/core/src/test/java/bisq/core/trade/asset/xmr/XmrProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java @@ -1,8 +1,4 @@ -package bisq.core.trade.asset.xmr; - -import bisq.core.trade.autoconf.xmr.XmrAutoConfirmResult; -import bisq.core.trade.autoconf.xmr.XmrProofInfo; -import bisq.core.trade.autoconf.xmr.XmrProofParser; +package bisq.core.trade.autoconf.xmr; import java.time.Instant; From ef9ac125192b4a342f55142069e3f55bd8964bec Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:09:04 -0500 Subject: [PATCH 31/85] Refactoring: -Reduce visibility --- .../core/trade/autoconf/xmr/XmrAutoConfirmResult.java | 6 +++--- .../java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java | 6 +++--- .../java/bisq/core/trade/autoconf/xmr/XmrProofParser.java | 4 ++-- .../trade/autoconf/xmr/XmrTransferProofRequester.java | 4 ++-- .../core/trade/autoconf/xmr/XmrTransferProofService.java | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index b937fbb3371..51c93335ec9 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -64,12 +64,12 @@ public XmrAutoConfirmResult() { this(State.UNDEFINED, 0, 0); } - public XmrAutoConfirmResult(State state) { + XmrAutoConfirmResult(State state) { this(state, 0, 0); } // alternate constructor for showing confirmation progress information - public XmrAutoConfirmResult(State state, int confirmCount, int confirmsRequired) { + XmrAutoConfirmResult(State state, int confirmCount, int confirmsRequired) { super(state.name()); this.state = state; this.confirmCount = confirmCount; @@ -77,7 +77,7 @@ public XmrAutoConfirmResult(State state, int confirmCount, int confirmsRequired) } // alternate constructor for error scenarios - public XmrAutoConfirmResult(State state, @Nullable String errorMsg) { + XmrAutoConfirmResult(State state, @Nullable String errorMsg) { this(state, 0, 0); if (!isPendingState() && !isSuccessState() && state != State.FEATURE_DISABLED && state != State.UNDEFINED) { diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java index 378120a113a..df573cbe003 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java @@ -24,7 +24,7 @@ @Slf4j @Value -public class XmrProofInfo { +class XmrProofInfo { private final String txHash; private final String txKey; private final String recipientAddress; @@ -33,7 +33,7 @@ public class XmrProofInfo { private final int confirmsRequired; private final String serviceAddress; - public XmrProofInfo( + XmrProofInfo( String txHash, String txKey, String recipientAddress, @@ -50,7 +50,7 @@ public XmrProofInfo( this.serviceAddress = serviceAddress; } - public String getUID() { + String getUID() { return txHash + "|" + serviceAddress; } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java index 3e2515975da..33ba174d190 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java @@ -32,8 +32,8 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -public class XmrProofParser { - static public XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { +class XmrProofParser { + static XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { String txHash = xmrProofInfo.getTxHash(); try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java index 142e085ccc0..ed34bd0541c 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java @@ -81,11 +81,11 @@ class XmrTransferProofRequester { /////////////////////////////////////////////////////////////////////////////////////////// // used by the service to abort further automatic retries - public void stop() { + void stop() { terminated = true; } - public void request() { + void request() { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls // this scenario may happen if a re-request is scheduled from the callback below diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 5d9f0e3f76b..b5d02b2f7b7 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -45,9 +45,9 @@ private XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { socks5ProxyProvider = provider; } - public void requestProof(XmrProofInfo xmrProofInfo, - Consumer resultHandler, - FaultHandler faultHandler) { + void requestProof(XmrProofInfo xmrProofInfo, + Consumer resultHandler, + FaultHandler faultHandler) { String uid = xmrProofInfo.getUID(); if (map.containsKey(uid)) { log.warn("We started a proof request for uid {} already", uid); @@ -72,7 +72,7 @@ public void requestProof(XmrProofInfo xmrProofInfo, requester.request(); } - public void terminateRequest(XmrProofInfo xmrProofInfo) { + void terminateRequest(XmrProofInfo xmrProofInfo) { String uid = xmrProofInfo.getUID(); XmrTransferProofRequester requester = map.getOrDefault(uid, null); if (requester != null) { From 0740f8d12b95c62059f6ef6b0146d6febd85e404 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:15:32 -0500 Subject: [PATCH 32/85] Remove @nullable from socks5ProxyProvider param Remove @Inject from XmrTxProofHttpClient as we pass the Socks5ProxyProvider anyway --- .../core/trade/autoconf/xmr/XmrTransferProofRequester.java | 6 ++---- .../core/trade/autoconf/xmr/XmrTransferProofService.java | 5 +---- .../bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java | 7 +------ 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java index ed34bd0541c..a53b177b385 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java @@ -36,8 +36,6 @@ import org.jetbrains.annotations.NotNull; -import javax.annotation.Nullable; - @Slf4j class XmrTransferProofRequester { // these settings are not likely to change and therefore not put into Config @@ -59,7 +57,7 @@ class XmrTransferProofRequester { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTransferProofRequester(@Nullable Socks5ProxyProvider socks5ProxyProvider, + XmrTransferProofRequester(Socks5ProxyProvider socks5ProxyProvider, XmrProofInfo xmrProofInfo, Consumer resultHandler, FaultHandler faultHandler) { @@ -85,7 +83,7 @@ void stop() { terminated = true; } - void request() { + public void request() { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls // this scenario may happen if a re-request is scheduled from the callback below diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index b5d02b2f7b7..24e7931b261 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -29,19 +29,16 @@ import lombok.extern.slf4j.Slf4j; -import javax.annotation.Nullable; - /** * Manages the XMR transfers proof requests for multiple trades and multiple services. */ @Slf4j class XmrTransferProofService { private final Map map = new HashMap<>(); - @Nullable private final Socks5ProxyProvider socks5ProxyProvider; @Inject - private XmrTransferProofService(@Nullable Socks5ProxyProvider provider) { + private XmrTransferProofService(Socks5ProxyProvider provider) { socks5ProxyProvider = provider; } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java index 0d74b86ce55..b9e2f9ebe34 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java @@ -20,13 +20,8 @@ import bisq.network.Socks5ProxyProvider; import bisq.network.http.HttpClient; -import javax.inject.Inject; - -import javax.annotation.Nullable; - class XmrTxProofHttpClient extends HttpClient { - @Inject - public XmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { + XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); } } From 6f56f90a66a3fdd56f4561afa5abc547277a7d75 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:23:41 -0500 Subject: [PATCH 33/85] Refactor: - Rename processCounterCurrencyExtraData to startRequestTxProofProcess - Change activeTrades from Stream to List --- .../core/trade/autoconf/xmr/XmrAutoConfirmationManager.java | 6 +++--- .../SellerProcessCounterCurrencyTransferStartedMessage.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index 98e7db2773d..3ecfd6719f6 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -91,7 +91,7 @@ private XmrAutoConfirmationManager(FilterManager filterManager, // API /////////////////////////////////////////////////////////////////////////////////////////// - public void processCounterCurrencyExtraData(Trade trade, Stream activeTrades) { + public void startRequestTxProofProcess(Trade trade, List activeTrades) { String counterCurrencyExtraData = trade.getCounterCurrencyExtraData(); if (counterCurrencyExtraData == null || counterCurrencyExtraData.isEmpty()) { return; @@ -138,7 +138,7 @@ public void processCounterCurrencyExtraData(Trade trade, Stream activeTra // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with // the same user (same address) and same amount. We check only for the txKey as a same txHash but different // txKey is not possible to get a valid result at proof. - Stream failedAndOpenTrades = Stream.concat(activeTrades, failedTradesManager.getFailedTrades().stream()); + Stream failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getFailedTrades().stream()); Stream closedTrades = closedTradableManager.getClosedTradables().stream() .filter(tradable -> tradable instanceof Trade) .map(tradable -> (Trade) tradable); @@ -235,7 +235,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { if (result.isSuccessState()) { resultsCountdown -= 1; - log.info("Received a {} message, remaining proofs needed: {}, tradeId {}", + log.info("Received a {} result, remaining proofs needed: {}, tradeId {}", result.getState(), resultsCountdown, trade.getShortId()); if (resultsCountdown > 0) { txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index b10f1fd993e..30ea7c09be4 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,8 +58,8 @@ protected void run() { String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getTradeManager().getXmrAutoConfirmationManager().processCounterCurrencyExtraData( - trade, processModel.getTradeManager().getTradableList().stream()); + processModel.getTradeManager().getXmrAutoConfirmationManager().startRequestTxProofProcess( + trade, processModel.getTradeManager().getTradableList()); } processModel.removeMailboxMessageAfterProcessing(trade); From 40a7320f32939d7fafe8a2be3c2d956b275d1ccf Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:25:02 -0500 Subject: [PATCH 34/85] Refactor: - Rename XmrTransferProofRequester to XmrTransferProofRequest --- ...roofRequester.java => XmrTransferProofRequest.java} | 10 +++++----- .../trade/autoconf/xmr/XmrTransferProofService.java | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrTransferProofRequester.java => XmrTransferProofRequest.java} (94%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java similarity index 94% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java index a53b177b385..efac14042ca 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequester.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java @@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull; @Slf4j -class XmrTransferProofRequester { +class XmrTransferProofRequest { // these settings are not likely to change and therefore not put into Config private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); @@ -57,10 +57,10 @@ class XmrTransferProofRequester { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTransferProofRequester(Socks5ProxyProvider socks5ProxyProvider, - XmrProofInfo xmrProofInfo, - Consumer resultHandler, - FaultHandler faultHandler) { + XmrTransferProofRequest(Socks5ProxyProvider socks5ProxyProvider, + XmrProofInfo xmrProofInfo, + Consumer resultHandler, + FaultHandler faultHandler) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrProofInfo.getServiceAddress()); if (xmrProofInfo.getServiceAddress().matches("^192.*|^localhost.*")) { diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 24e7931b261..22363f1459d 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -34,7 +34,7 @@ */ @Slf4j class XmrTransferProofService { - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); private final Socks5ProxyProvider socks5ProxyProvider; @Inject @@ -52,7 +52,7 @@ void requestProof(XmrProofInfo xmrProofInfo, } log.info("requesting tx proof with uid {}", uid); - XmrTransferProofRequester requester = new XmrTransferProofRequester( + XmrTransferProofRequest requester = new XmrTransferProofRequest( socks5ProxyProvider, xmrProofInfo, result -> { @@ -71,7 +71,7 @@ void requestProof(XmrProofInfo xmrProofInfo, void terminateRequest(XmrProofInfo xmrProofInfo) { String uid = xmrProofInfo.getUID(); - XmrTransferProofRequester requester = map.getOrDefault(uid, null); + XmrTransferProofRequest requester = map.getOrDefault(uid, null); if (requester != null) { log.info("Terminating API request for request with uid {}", uid); requester.stop(); From f8b5c30fb43d469408d6db2428e928dc810bc135 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:35:26 -0500 Subject: [PATCH 35/85] Refactor: - Rename AutoConfirmResult to AssetTxProofResult - Update protobuf entry --- core/src/main/java/bisq/core/trade/Trade.java | 22 +++++++++---------- ...irmResult.java => AssetTxProofResult.java} | 8 +++---- .../autoconf/xmr/XmrAutoConfirmResult.java | 4 ++-- .../xmr/XmrAutoConfirmationManager.java | 14 ++++++------ .../overlays/windows/TradeDetailsWindow.java | 4 ++-- .../steps/buyer/BuyerStep4View.java | 2 +- .../steps/seller/SellerStep3View.java | 10 ++++----- proto/src/main/proto/pb.proto | 4 ++-- 8 files changed, 34 insertions(+), 34 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/{AutoConfirmResult.java => AssetTxProofResult.java} (88%) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index c510f8bf50f..2748acd35f2 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -38,7 +38,7 @@ import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.autoconf.AutoConfirmResult; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; @@ -438,11 +438,11 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt // Added at v1.3.8 @Nullable - private AutoConfirmResult autoConfirmResult; + private AssetTxProofResult assetTxProofResult; @Getter // This observable property can be used for UI to show a notification to user of the XMR proof status - transient final private ObjectProperty autoConfirmResultProperty = new SimpleObjectProperty<>(); + transient final private ObjectProperty assetTxProofResultProperty = new SimpleObjectProperty<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -556,7 +556,7 @@ public Message toProtoMessage() { Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState))); Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes))); Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); - Optional.ofNullable(autoConfirmResult).ifPresent(e -> builder.setAutoConfirmResult(autoConfirmResult.toProtoMessage())); + Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.toProtoMessage())); return builder.build(); } @@ -591,7 +591,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); - trade.setAutoConfirmResult(AutoConfirmResult.fromProto(proto.getAutoConfirmResult(), checkNotNull(trade.getOffer()).getCurrencyCode())); + trade.setAssetTxProofResult(AssetTxProofResult.fromProto(proto.getAssetTxProofResult(), checkNotNull(trade.getOffer()).getCurrencyCode())); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) @@ -875,9 +875,9 @@ public void setErrorMessage(String errorMessage) { errorMessageProperty.set(errorMessage); } - public void setAutoConfirmResult(AutoConfirmResult autoConfirmResult) { - this.autoConfirmResult = autoConfirmResult; - autoConfirmResultProperty.setValue(autoConfirmResult); + public void setAssetTxProofResult(AssetTxProofResult assetTxProofResult) { + this.assetTxProofResult = assetTxProofResult; + assetTxProofResultProperty.setValue(assetTxProofResult); } @@ -1092,8 +1092,8 @@ public String getErrorMessage() { return errorMessageProperty.get(); } - public AutoConfirmResult getAutoConfirmResult() { - return autoConfirmResult != null ? autoConfirmResult : AutoConfirmResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); + public AssetTxProofResult getAssetTxProofResult() { + return assetTxProofResult != null ? assetTxProofResult : AssetTxProofResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); } @@ -1194,7 +1194,7 @@ public String toString() { ",\n errorMessage='" + errorMessage + '\'' + ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' + ",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' + - ",\n autoConfirmResult='" + autoConfirmResult + '\'' + + ",\n assetTxProofResult='" + assetTxProofResult + '\'' + ",\n chatMessages=" + chatMessages + ",\n txFee=" + txFee + ",\n takerFee=" + takerFee + diff --git a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java similarity index 88% rename from core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java rename to core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java index 40b48c7dafa..0c8a2d50af5 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java @@ -29,9 +29,9 @@ */ @EqualsAndHashCode @Getter -public abstract class AutoConfirmResult { +public abstract class AssetTxProofResult { - public static AutoConfirmResult fromCurrencyCode(String currencyCode) { + public static AssetTxProofResult fromCurrencyCode(String currencyCode) { //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { case "XMR": @@ -43,7 +43,7 @@ public static AutoConfirmResult fromCurrencyCode(String currencyCode) { private final String stateName; - protected AutoConfirmResult(String stateName) { + protected AssetTxProofResult(String stateName) { this.stateName = stateName; } @@ -54,7 +54,7 @@ protected AutoConfirmResult(String stateName) { // We use fromProto as kind of factory method to get the specific AutoConfirmResult @Nullable - public static AutoConfirmResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { + public static AssetTxProofResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { case "XMR": diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java index 51c93335ec9..f49f45878eb 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java @@ -18,7 +18,7 @@ package bisq.core.trade.autoconf.xmr; import bisq.core.locale.Res; -import bisq.core.trade.autoconf.AutoConfirmResult; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.common.proto.ProtoUtil; @@ -31,7 +31,7 @@ @Slf4j @Getter @EqualsAndHashCode(callSuper = true) -public class XmrAutoConfirmResult extends AutoConfirmResult { +public class XmrAutoConfirmResult extends AssetTxProofResult { public enum State { UNDEFINED, FEATURE_DISABLED, diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index 3ecfd6719f6..ddf00ba6758 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -156,7 +156,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { if (alreadyUsed) { String message = "Peer used the XMR tx key already at another trade with trade ID " + t.getId() + ". This might be a scam attempt."; - trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_REUSED, message)); + trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_REUSED, message)); } return alreadyUsed; }); @@ -166,7 +166,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { } if (!preferences.getAutoConfirmSettings().enabled || this.isAutoConfDisabledByFilter()) { - trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.FEATURE_DISABLED, null)); + trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.FEATURE_DISABLED, null)); return; } Coin tradeAmount = trade.getTradeAmount(); @@ -174,7 +174,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_LIMIT_EXCEEDED, null)); + trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_LIMIT_EXCEEDED, null)); return; } @@ -183,7 +183,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { Volume volume = offer.getVolumeByAmount(tradeAmount); long amountXmr = volume != null ? volume.getValue() * 10000L : 0L; int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; - trade.setAutoConfirmResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND)); + trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND)); List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address for (String serviceAddress : serviceAddresses) { @@ -228,7 +228,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { if (result.isPendingState()) { log.info("Auto confirm received a {} message for tradeId {}, retry will happen automatically", result.getState(), trade.getShortId()); - trade.setAutoConfirmResult(result); // this updates the GUI with the status.. + trade.setAssetTxProofResult(result); // this updates the GUI with the status.. // Repeating the requests is handled in XmrTransferProofRequester return true; } @@ -243,7 +243,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { } // we've received the final PROOF_OK, all good here. txProofResultsPending.remove(trade.getId()); - trade.setAutoConfirmResult(result); // this updates the GUI with the status.. + trade.setAssetTxProofResult(result); // this updates the GUI with the status.. log.info("Auto confirm was successful, transitioning trade {} to next step...", trade.getShortId()); if (!trade.isPayoutPublished()) { // note that this state can also be triggered by auto confirmation feature @@ -264,7 +264,7 @@ private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { // TX_KEY_INVALID, ADDRESS_INVALID, NO_MATCH_FOUND, AMOUNT_NOT_MATCHING, TRADE_DATE_NOT_MATCHING log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", result.getState(), trade.getShortId()); - trade.setAutoConfirmResult(result); // this updates the GUI with the status.. + trade.setAssetTxProofResult(result); // this updates the GUI with the status.. resultsCountdown = -1; // signal all API requesters to cease txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count return false; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 05525a32afd..3a62347f8dc 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -159,8 +159,8 @@ private void addContent() { addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), FormattingUtils.formatPrice(trade.getTradePrice())); String paymentMethodText = Res.get(offer.getPaymentMethod().getId()); - if (trade.getAutoConfirmResult().isSuccessState()) { - paymentMethodText += " (" + trade.getAutoConfirmResult().getStatusAsDisplayString() + ")"; + if (trade.getAssetTxProofResult().isSuccessState()) { + paymentMethodText += " (" + trade.getAssetTxProofResult().getStatusAsDisplayString() + ")"; } addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), paymentMethodText); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 83a5decfdd7..091b2ed7cf8 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -117,7 +117,7 @@ protected void addContent() { GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); gridPane.getChildren().add(hBox2); GridPane.setRowSpan(hBox2, 5); - if (!trade.getAutoConfirmResult().isSuccessState()) { + if (!trade.getAssetTxProofResult().isSuccessState()) { autoConfBadge.setVisible(false); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index fd6e0b50401..9ea9212eba4 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -42,7 +42,7 @@ import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.autoconf.AutoConfirmResult; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; @@ -77,7 +77,7 @@ public class SellerStep3View extends TradeStepView { private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; private TextFieldWithCopyIcon autoConfirmStatusField; - private final ChangeListener autoConfirmResultListener; + private final ChangeListener autoConfirmResultListener; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -152,9 +152,9 @@ public void activate() { // we listen for updates on the trade autoConfirmResult field if (autoConfirmStatusField != null) { - trade.getAutoConfirmResultProperty().addListener(autoConfirmResultListener); + trade.getAssetTxProofResultProperty().addListener(autoConfirmResultListener); // display the initial value, or FEATURE_DISABLED if there is none - autoConfirmStatusField.setText(trade.getAutoConfirmResult().getStatusAsDisplayString()); + autoConfirmStatusField.setText(trade.getAssetTxProofResult().getStatusAsDisplayString()); } } @@ -172,7 +172,7 @@ public void deactivate() { if (timeoutTimer != null) timeoutTimer.stop(); - trade.getAutoConfirmResultProperty().removeListener(autoConfirmResultListener); + trade.getAssetTxProofResultProperty().removeListener(autoConfirmResultListener); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 7a3e231bee4..9774e90b10e 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1393,7 +1393,7 @@ message Trade { RefundResultState refund_result_state = 35; int64 last_refresh_request_date = 36; string counter_currency_extra_data = 37; - AutoConfirmResult auto_confirm_result = 38; + AutoConfirmResult asset_tx_proof_result = 38; } message BuyerAsMakerTrade { @@ -1588,7 +1588,7 @@ message UserPayload { } message AutoConfirmResult { - string stateName = 1; + string stateName = 1; // name of state enum } /////////////////////////////////////////////////////////////////////////////////////////// From 44782223ec97ee93e70ef8e31450bb5043ac5fa3 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:35:56 -0500 Subject: [PATCH 36/85] Refactor: - Rename XmrAutoConfirmResult to XmrTxProofResult --- .../trade/autoconf/AssetTxProofResult.java | 6 +-- .../xmr/XmrAutoConfirmationManager.java | 10 ++--- .../trade/autoconf/xmr/XmrProofParser.java | 38 +++++++++--------- .../autoconf/xmr/XmrTransferProofRequest.java | 12 +++--- .../autoconf/xmr/XmrTransferProofService.java | 2 +- ...nfirmResult.java => XmrTxProofResult.java} | 16 ++++---- .../autoconf/xmr/XmrProofParserTest.java | 40 +++++++++---------- 7 files changed, 62 insertions(+), 62 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrAutoConfirmResult.java => XmrTxProofResult.java} (87%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java index 0c8a2d50af5..0ddcbef7559 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java @@ -17,7 +17,7 @@ package bisq.core.trade.autoconf; -import bisq.core.trade.autoconf.xmr.XmrAutoConfirmResult; +import bisq.core.trade.autoconf.xmr.XmrTxProofResult; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -35,7 +35,7 @@ public static AssetTxProofResult fromCurrencyCode(String currencyCode) { //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { case "XMR": - return new XmrAutoConfirmResult(); + return new XmrTxProofResult(); default: return null; } @@ -58,7 +58,7 @@ public static AssetTxProofResult fromProto(protobuf.AutoConfirmResult proto, Str //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { case "XMR": - return XmrAutoConfirmResult.fromProto(proto); + return XmrTxProofResult.fromProto(proto); default: return null; } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index ddf00ba6758..ff45c88dd45 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -156,7 +156,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { if (alreadyUsed) { String message = "Peer used the XMR tx key already at another trade with trade ID " + t.getId() + ". This might be a scam attempt."; - trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_REUSED, message)); + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_REUSED, message)); } return alreadyUsed; }); @@ -166,7 +166,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { } if (!preferences.getAutoConfirmSettings().enabled || this.isAutoConfDisabledByFilter()) { - trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.FEATURE_DISABLED, null)); + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.FEATURE_DISABLED, null)); return; } Coin tradeAmount = trade.getTradeAmount(); @@ -174,7 +174,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_LIMIT_EXCEEDED, null)); + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TRADE_LIMIT_EXCEEDED, null)); return; } @@ -183,7 +183,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { Volume volume = offer.getVolumeByAmount(tradeAmount); long amountXmr = volume != null ? volume.getValue() * 10000L : 0L; int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; - trade.setAssetTxProofResult(new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND)); + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND)); List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address for (String serviceAddress : serviceAddresses) { @@ -208,7 +208,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { } } - private boolean handleProofResult(XmrAutoConfirmResult result, Trade trade) { + private boolean handleProofResult(XmrTxProofResult result, Trade trade) { // here we count the Trade's API results from all // different serviceAddress and figure out when all have finished int resultsCountdown = txProofResultsPending.getOrDefault(trade.getId(), 0); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java index 33ba174d190..f4075a1a174 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java @@ -33,58 +33,58 @@ @Slf4j class XmrProofParser { - static XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { + static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { String txHash = xmrProofInfo.getTxHash(); try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Empty json"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Empty json"); } // there should always be "data" and "status" at the top level if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing data / status fields"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing data / status fields"); } JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); if (jsonStatus.matches("fail")) { // the API returns "fail" until the transaction has successfully reached the mempool. // we return TX_NOT_FOUND which will cause a retry later - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_FOUND, null); + return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND, null); } else if (!jsonStatus.matches("success")) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_FAILURE, "Unhandled status value"); + return new XmrTxProofResult(XmrTxProofResult.State.API_FAILURE, "Unhandled status value"); } // validate that the address matches JsonElement jsonAddress = jsonData.get("address"); if (jsonAddress == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing address field"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing address field"); } else { String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrProofInfo.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.ADDRESS_INVALID, null); + return new XmrTxProofResult(XmrTxProofResult.State.ADDRESS_INVALID, null); } } // validate that the txHash matches JsonElement jsonTxHash = jsonData.get("tx_hash"); if (jsonTxHash == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_hash field"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_hash field"); } else { if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_HASH_INVALID, null); + return new XmrTxProofResult(XmrTxProofResult.State.TX_HASH_INVALID, null); } } // validate that the txKey matches JsonElement jsonViewkey = jsonData.get("viewkey"); if (jsonViewkey == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing viewkey field"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing viewkey field"); } else { if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrProofInfo.getTxKey())) { log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrProofInfo.getTxKey()); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_KEY_INVALID, null); + return new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_INVALID, null); } } @@ -92,7 +92,7 @@ static XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { // (except that in dev mode we let this check pass anyway) JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); if (jsonTimestamp == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_timestamp field"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_timestamp field"); } else { long tradeDateSeconds = xmrProofInfo.getTradeDate().getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); @@ -100,7 +100,7 @@ static XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING, null); + return new XmrTxProofResult(XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING, null); } } @@ -108,7 +108,7 @@ static XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { int confirmations; JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); if (jsonConfirmations == null) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, "Missing tx_confirmations field"); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_confirmations field"); } else { confirmations = jsonConfirmations.getAsInt(); log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); @@ -128,23 +128,23 @@ static XmrAutoConfirmResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { int confirmsRequired = xmrProofInfo.getConfirmsRequired(); if (confirmations < confirmsRequired) // we return TX_NOT_CONFIRMED which will cause a retry later - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); + return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); else - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.PROOF_OK, confirmations, confirmsRequired); + return new XmrTxProofResult(XmrTxProofResult.State.PROOF_OK, confirmations, confirmsRequired); } } } // None of the outputs had a match entry if (!anyMatchFound && !DevEnv.isDevMode()) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.NO_MATCH_FOUND, null); + return new XmrTxProofResult(XmrTxProofResult.State.NO_MATCH_FOUND, null); } // reaching this point means there was no matching amount - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING, null); + return new XmrTxProofResult(XmrTxProofResult.State.AMOUNT_NOT_MATCHING, null); } catch (JsonParseException | NullPointerException e) { - return new XmrAutoConfirmResult(XmrAutoConfirmResult.State.API_INVALID, e.toString()); + return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, e.toString()); } } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java index efac14042ca..f7961c3e789 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java @@ -46,7 +46,7 @@ class XmrTransferProofRequest { "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; private final XmrProofInfo xmrProofInfo; - private final Consumer resultHandler; + private final Consumer resultHandler; private final FaultHandler faultHandler; private boolean terminated; @@ -59,7 +59,7 @@ class XmrTransferProofRequest { XmrTransferProofRequest(Socks5ProxyProvider socks5ProxyProvider, XmrProofInfo xmrProofInfo, - Consumer resultHandler, + Consumer resultHandler, FaultHandler faultHandler) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrProofInfo.getServiceAddress()); @@ -90,7 +90,7 @@ public void request() { log.info("Request() aborted, this object has been terminated. Service: {}", httpClient.getBaseUrl()); return; } - ListenableFuture future = executorService.submit(() -> { + ListenableFuture future = executorService.submit(() -> { Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getUID()); String param = "/api/outputs?txhash=" + xmrProofInfo.getTxHash() + "&address=" + xmrProofInfo.getRecipientAddress() + @@ -98,13 +98,13 @@ public void request() { "&txprove=1"; log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - XmrAutoConfirmResult autoConfirmResult = XmrProofParser.parse(xmrProofInfo, json); + XmrTxProofResult autoConfirmResult = XmrProofParser.parse(xmrProofInfo, json); log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); return autoConfirmResult; }); Futures.addCallback(future, new FutureCallback<>() { - public void onSuccess(XmrAutoConfirmResult result) { + public void onSuccess(XmrTxProofResult result) { if (terminated) { log.info("API terminated from higher level: {}", httpClient.getBaseUrl()); return; @@ -123,7 +123,7 @@ public void onFailure(@NotNull Throwable throwable) { String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; faultHandler.handleFault(errorMessage, throwable); UserThread.execute(() -> resultHandler.accept( - new XmrAutoConfirmResult(XmrAutoConfirmResult.State.CONNECTION_FAIL, errorMessage))); + new XmrTxProofResult(XmrTxProofResult.State.CONNECTION_FAIL, errorMessage))); } }); } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 22363f1459d..602ce352335 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -43,7 +43,7 @@ private XmrTransferProofService(Socks5ProxyProvider provider) { } void requestProof(XmrProofInfo xmrProofInfo, - Consumer resultHandler, + Consumer resultHandler, FaultHandler faultHandler) { String uid = xmrProofInfo.getUID(); if (map.containsKey(uid)) { diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java similarity index 87% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java index f49f45878eb..c40779695c8 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java @@ -31,7 +31,7 @@ @Slf4j @Getter @EqualsAndHashCode(callSuper = true) -public class XmrAutoConfirmResult extends AssetTxProofResult { +public class XmrTxProofResult extends AssetTxProofResult { public enum State { UNDEFINED, FEATURE_DISABLED, @@ -60,16 +60,16 @@ public enum State { // Constructors /////////////////////////////////////////////////////////////////////////////////////////// - public XmrAutoConfirmResult() { + public XmrTxProofResult() { this(State.UNDEFINED, 0, 0); } - XmrAutoConfirmResult(State state) { + XmrTxProofResult(State state) { this(state, 0, 0); } // alternate constructor for showing confirmation progress information - XmrAutoConfirmResult(State state, int confirmCount, int confirmsRequired) { + XmrTxProofResult(State state, int confirmCount, int confirmsRequired) { super(state.name()); this.state = state; this.confirmCount = confirmCount; @@ -77,7 +77,7 @@ public XmrAutoConfirmResult() { } // alternate constructor for error scenarios - XmrAutoConfirmResult(State state, @Nullable String errorMsg) { + XmrTxProofResult(State state, @Nullable String errorMsg) { this(state, 0, 0); if (!isPendingState() && !isSuccessState() && state != State.FEATURE_DISABLED && state != State.UNDEFINED) { @@ -95,9 +95,9 @@ public protobuf.AutoConfirmResult toProtoMessage() { return protobuf.AutoConfirmResult.newBuilder().setStateName(state.name()).build(); } - public static XmrAutoConfirmResult fromProto(protobuf.AutoConfirmResult proto) { - XmrAutoConfirmResult.State state = ProtoUtil.enumFromProto(XmrAutoConfirmResult.State.class, proto.getStateName()); - return state != null ? new XmrAutoConfirmResult(state) : new XmrAutoConfirmResult(State.UNDEFINED); + public static XmrTxProofResult fromProto(protobuf.AutoConfirmResult proto) { + XmrTxProofResult.State state = ProtoUtil.enumFromProto(XmrTxProofResult.State.class, proto.getStateName()); + return state != null ? new XmrTxProofResult(state) : new XmrTxProofResult(State.UNDEFINED); } diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java index 9f235c76566..43554b7e54c 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java @@ -46,13 +46,13 @@ public void testKey() { public void testJsonRoot() { // checking what happens when bad input is provided assertTrue(XmrProofParser.parse(xmrProofInfo, - "invalid json data").getState() == XmrAutoConfirmResult.State.API_INVALID); + "invalid json data").getState() == XmrTxProofResult.State.API_INVALID); assertTrue(XmrProofParser.parse(xmrProofInfo, - "").getState() == XmrAutoConfirmResult.State.API_INVALID); + "").getState() == XmrTxProofResult.State.API_INVALID); assertTrue(XmrProofParser.parse(xmrProofInfo, - "[]").getState() == XmrAutoConfirmResult.State.API_INVALID); + "[]").getState() == XmrTxProofResult.State.API_INVALID); assertTrue(XmrProofParser.parse(xmrProofInfo, - "{}").getState() == XmrAutoConfirmResult.State.API_INVALID); + "{}").getState() == XmrTxProofResult.State.API_INVALID); } @Test @@ -60,34 +60,34 @@ public void testJsonTopLevel() { // testing the top level fields: data and status assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'title':''},'status':'fail'}" ) - .getState() == XmrAutoConfirmResult.State.TX_NOT_FOUND); + .getState() == XmrTxProofResult.State.TX_NOT_FOUND); assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'title':''},'missingstatus':'success'}" ) - .getState() == XmrAutoConfirmResult.State.API_INVALID); + .getState() == XmrTxProofResult.State.API_INVALID); assertTrue(XmrProofParser.parse(xmrProofInfo, "{'missingdata':{'title':''},'status':'success'}" ) - .getState() == XmrAutoConfirmResult.State.API_INVALID); + .getState() == XmrTxProofResult.State.API_INVALID); } @Test public void testJsonAddress() { assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) - .getState() == XmrAutoConfirmResult.State.API_INVALID); + .getState() == XmrTxProofResult.State.API_INVALID); assertTrue(XmrProofParser.parse(xmrProofInfo, "{'data':{'address':'e957dac7'},'status':'success'}" ) - .getState() == XmrAutoConfirmResult.State.ADDRESS_INVALID); + .getState() == XmrTxProofResult.State.ADDRESS_INVALID); } @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_hash).getState() - == XmrAutoConfirmResult.State.API_INVALID); + == XmrTxProofResult.State.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_hash).getState() - == XmrAutoConfirmResult.State.TX_HASH_INVALID); + == XmrTxProofResult.State.TX_HASH_INVALID); } @Test @@ -95,13 +95,13 @@ public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_key).getState() - == XmrAutoConfirmResult.State.API_INVALID); + == XmrTxProofResult.State.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_key).getState() - == XmrAutoConfirmResult.State.TX_KEY_INVALID); + == XmrTxProofResult.State.TX_KEY_INVALID); } @Test @@ -110,14 +110,14 @@ public void testJsonTxTimestamp() { "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_timestamp).getState() - == XmrAutoConfirmResult.State.API_INVALID); + == XmrTxProofResult.State.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_timestamp).getState() - == XmrAutoConfirmResult.State.TRADE_DATE_NOT_MATCHING); + == XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING); } @Test @@ -135,25 +135,25 @@ public void testJsonTxConfirmation() { "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() - == XmrAutoConfirmResult.State.PROOF_OK); + == XmrTxProofResult.State.PROOF_OK); json = json.replaceFirst("777", "0"); assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() - == XmrAutoConfirmResult.State.TX_NOT_CONFIRMED); + == XmrTxProofResult.State.TX_NOT_CONFIRMED); json = json.replaceFirst("100000000000", "100000000001"); assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() - == XmrAutoConfirmResult.State.AMOUNT_NOT_MATCHING); + == XmrTxProofResult.State.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() - == XmrAutoConfirmResult.State.NO_MATCH_FOUND); + == XmrTxProofResult.State.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; assertTrue(XmrProofParser.parse(xmrProofInfo, failedJson).getState() - == XmrAutoConfirmResult.State.API_INVALID); + == XmrTxProofResult.State.API_INVALID); } } From 87070531ddaee753f2dc06e6b3179f9eb36de6e6 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:36:54 -0500 Subject: [PATCH 37/85] Refactor: - Rename XmrProofParser to XmrTxProofParser (and rename test) --- .../autoconf/xmr/XmrTransferProofRequest.java | 2 +- ...ProofParser.java => XmrTxProofParser.java} | 2 +- ...serTest.java => XmrTxProofParserTest.java} | 42 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrProofParser.java => XmrTxProofParser.java} (99%) rename core/src/test/java/bisq/core/trade/autoconf/xmr/{XmrProofParserTest.java => XmrTxProofParserTest.java} (80%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java index f7961c3e789..a1f064b8d7a 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java @@ -98,7 +98,7 @@ public void request() { "&txprove=1"; log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - XmrTxProofResult autoConfirmResult = XmrProofParser.parse(xmrProofInfo, json); + XmrTxProofResult autoConfirmResult = XmrTxProofParser.parse(xmrProofInfo, json); log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); return autoConfirmResult; }); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java similarity index 99% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java index f4075a1a174..9572f590b4a 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java @@ -32,7 +32,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -class XmrProofParser { +class XmrTxProofParser { static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { String txHash = xmrProofInfo.getTxHash(); try { diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java similarity index 80% rename from core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java rename to core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java index 43554b7e54c..65fe52e64a3 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java @@ -10,7 +10,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -public class XmrProofParserTest { +public class XmrTxProofParserTest { private XmrProofInfo xmrProofInfo; private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae"; private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; @@ -45,36 +45,36 @@ public void testKey() { @Test public void testJsonRoot() { // checking what happens when bad input is provided - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "invalid json data").getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "").getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "[]").getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "{}").getState() == XmrTxProofResult.State.API_INVALID); } @Test public void testJsonTopLevel() { // testing the top level fields: data and status - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "{'data':{'title':''},'status':'fail'}" ) .getState() == XmrTxProofResult.State.TX_NOT_FOUND); - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "{'data':{'title':''},'missingstatus':'success'}" ) .getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "{'missingdata':{'title':''},'status':'success'}" ) .getState() == XmrTxProofResult.State.API_INVALID); } @Test public void testJsonAddress() { - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) .getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrProofInfo, "{'data':{'address':'e957dac7'},'status':'success'}" ) .getState() == XmrTxProofResult.State.ADDRESS_INVALID); } @@ -82,11 +82,11 @@ public void testJsonAddress() { @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_hash).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, missing_tx_hash).getState() == XmrTxProofResult.State.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_hash).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, invalid_tx_hash).getState() == XmrTxProofResult.State.TX_HASH_INVALID); } @@ -94,13 +94,13 @@ public void testJsonTxHash() { public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_key).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, missing_tx_key).getState() == XmrTxProofResult.State.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_key).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, invalid_tx_key).getState() == XmrTxProofResult.State.TX_KEY_INVALID); } @@ -109,14 +109,14 @@ public void testJsonTxTimestamp() { String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, missing_tx_timestamp).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, missing_tx_timestamp).getState() == XmrTxProofResult.State.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, invalid_tx_timestamp).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, invalid_tx_timestamp).getState() == XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING); } @@ -134,26 +134,26 @@ public void testJsonTxConfirmation() { "'viewkey':'" + txKey + "', " + "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() == XmrTxProofResult.State.PROOF_OK); json = json.replaceFirst("777", "0"); - assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() == XmrTxProofResult.State.TX_NOT_CONFIRMED); json = json.replaceFirst("100000000000", "100000000001"); - assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() == XmrTxProofResult.State.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); - assertTrue(XmrProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() == XmrTxProofResult.State.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertTrue(XmrProofParser.parse(xmrProofInfo, failedJson).getState() + assertTrue(XmrTxProofParser.parse(xmrProofInfo, failedJson).getState() == XmrTxProofResult.State.API_INVALID); } } From a758880211acd6d4086517abbac3dd885ff695cc Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:37:22 -0500 Subject: [PATCH 38/85] Refactor: - Rename XmrProofInfo to XmrTxProofModel --- .../xmr/XmrAutoConfirmationManager.java | 6 +-- .../autoconf/xmr/XmrTransferProofRequest.java | 22 ++++---- .../autoconf/xmr/XmrTransferProofService.java | 10 ++-- ...XmrProofInfo.java => XmrTxProofModel.java} | 4 +- .../trade/autoconf/xmr/XmrTxProofParser.java | 16 +++--- .../autoconf/xmr/XmrTxProofParserTest.java | 50 +++++++++---------- 6 files changed, 54 insertions(+), 54 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrProofInfo.java => XmrTxProofModel.java} (97%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index ff45c88dd45..09e5b90f46f 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -187,7 +187,7 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address for (String serviceAddress : serviceAddresses) { - XmrProofInfo xmrProofInfo = new XmrProofInfo( + XmrTxProofModel xmrTxProofModel = new XmrTxProofModel( txHash, txKey, address, @@ -195,10 +195,10 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { trade.getDate(), confirmsRequired, serviceAddress); - xmrTransferProofService.requestProof(xmrProofInfo, + xmrTransferProofService.requestProof(xmrTxProofModel, result -> { if (!handleProofResult(result, trade)) - xmrTransferProofService.terminateRequest(xmrProofInfo); + xmrTransferProofService.terminateRequest(xmrTxProofModel); }, (errorMsg, throwable) -> { log.warn(errorMsg); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java index a1f064b8d7a..3865a306bb7 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java @@ -45,7 +45,7 @@ class XmrTransferProofRequest { private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; - private final XmrProofInfo xmrProofInfo; + private final XmrTxProofModel xmrTxProofModel; private final Consumer resultHandler; private final FaultHandler faultHandler; @@ -58,16 +58,16 @@ class XmrTransferProofRequest { /////////////////////////////////////////////////////////////////////////////////////////// XmrTransferProofRequest(Socks5ProxyProvider socks5ProxyProvider, - XmrProofInfo xmrProofInfo, + XmrTxProofModel xmrTxProofModel, Consumer resultHandler, FaultHandler faultHandler) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); - this.httpClient.setBaseUrl("http://" + xmrProofInfo.getServiceAddress()); - if (xmrProofInfo.getServiceAddress().matches("^192.*|^localhost.*")) { - log.info("Ignoring Socks5 proxy for local net address: {}", xmrProofInfo.getServiceAddress()); + this.httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); + if (xmrTxProofModel.getServiceAddress().matches("^192.*|^localhost.*")) { + log.info("Ignoring Socks5 proxy for local net address: {}", xmrTxProofModel.getServiceAddress()); this.httpClient.setIgnoreSocks5Proxy(true); } - this.xmrProofInfo = xmrProofInfo; + this.xmrTxProofModel = xmrTxProofModel; this.resultHandler = resultHandler; this.faultHandler = faultHandler; this.terminated = false; @@ -91,14 +91,14 @@ public void request() { return; } ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("XmrTransferProofRequest-" + xmrProofInfo.getUID()); - String param = "/api/outputs?txhash=" + xmrProofInfo.getTxHash() + - "&address=" + xmrProofInfo.getRecipientAddress() + - "&viewkey=" + xmrProofInfo.getTxKey() + + Thread.currentThread().setName("XmrTransferProofRequest-" + xmrTxProofModel.getUID()); + String param = "/api/outputs?txhash=" + xmrTxProofModel.getTxHash() + + "&address=" + xmrTxProofModel.getRecipientAddress() + + "&viewkey=" + xmrTxProofModel.getTxKey() + "&txprove=1"; log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - XmrTxProofResult autoConfirmResult = XmrTxProofParser.parse(xmrProofInfo, json); + XmrTxProofResult autoConfirmResult = XmrTxProofParser.parse(xmrTxProofModel, json); log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); return autoConfirmResult; }); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 602ce352335..31bd5e52247 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -42,10 +42,10 @@ private XmrTransferProofService(Socks5ProxyProvider provider) { socks5ProxyProvider = provider; } - void requestProof(XmrProofInfo xmrProofInfo, + void requestProof(XmrTxProofModel xmrTxProofModel, Consumer resultHandler, FaultHandler faultHandler) { - String uid = xmrProofInfo.getUID(); + String uid = xmrTxProofModel.getUID(); if (map.containsKey(uid)) { log.warn("We started a proof request for uid {} already", uid); return; @@ -54,7 +54,7 @@ void requestProof(XmrProofInfo xmrProofInfo, XmrTransferProofRequest requester = new XmrTransferProofRequest( socks5ProxyProvider, - xmrProofInfo, + xmrTxProofModel, result -> { if (result.isSuccessState()) { cleanup(uid); @@ -69,8 +69,8 @@ void requestProof(XmrProofInfo xmrProofInfo, requester.request(); } - void terminateRequest(XmrProofInfo xmrProofInfo) { - String uid = xmrProofInfo.getUID(); + void terminateRequest(XmrTxProofModel xmrTxProofModel) { + String uid = xmrTxProofModel.getUID(); XmrTransferProofRequest requester = map.getOrDefault(uid, null); if (requester != null) { log.info("Terminating API request for request with uid {}", uid); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java similarity index 97% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java index df573cbe003..0fbfeea906c 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrProofInfo.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java @@ -24,7 +24,7 @@ @Slf4j @Value -class XmrProofInfo { +class XmrTxProofModel { private final String txHash; private final String txKey; private final String recipientAddress; @@ -33,7 +33,7 @@ class XmrProofInfo { private final int confirmsRequired; private final String serviceAddress; - XmrProofInfo( + XmrTxProofModel( String txHash, String txKey, String recipientAddress, diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java index 9572f590b4a..d3dc82b6d4c 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java @@ -33,8 +33,8 @@ @Slf4j class XmrTxProofParser { - static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { - String txHash = xmrProofInfo.getTxHash(); + static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { + String txHash = xmrTxProofModel.getTxHash(); try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) { @@ -59,7 +59,7 @@ static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { if (jsonAddress == null) { return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing address field"); } else { - String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrProofInfo.getRecipientAddress()); + String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrTxProofModel.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); return new XmrTxProofResult(XmrTxProofResult.State.ADDRESS_INVALID, null); @@ -82,8 +82,8 @@ static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { if (jsonViewkey == null) { return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing viewkey field"); } else { - if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrProofInfo.getTxKey())) { - log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrProofInfo.getTxKey()); + if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrTxProofModel.getTxKey())) { + log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrTxProofModel.getTxKey()); return new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_INVALID, null); } } @@ -94,7 +94,7 @@ static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { if (jsonTimestamp == null) { return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_timestamp field"); } else { - long tradeDateSeconds = xmrProofInfo.getTradeDate().getTime() / 1000; + long tradeDateSeconds = xmrTxProofModel.getTradeDate().getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { @@ -124,8 +124,8 @@ static XmrTxProofResult parse(XmrProofInfo xmrProofInfo, String jsonTxt) { if (out.get("match").getAsBoolean()) { anyMatchFound = true; long jsonAmount = out.get("amount").getAsLong(); - if (jsonAmount == xmrProofInfo.getAmount() || DevEnv.isDevMode()) { // any amount ok in dev mode - int confirmsRequired = xmrProofInfo.getConfirmsRequired(); + if (jsonAmount == xmrTxProofModel.getAmount() || DevEnv.isDevMode()) { // any amount ok in dev mode + int confirmsRequired = xmrTxProofModel.getConfirmsRequired(); if (confirmations < confirmsRequired) // we return TX_NOT_CONFIRMED which will cause a retry later return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java index 65fe52e64a3..96f5faad690 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java @@ -11,7 +11,7 @@ import static org.junit.Assert.assertTrue; public class XmrTxProofParserTest { - private XmrProofInfo xmrProofInfo; + private XmrTxProofModel xmrTxProofModel; private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae"; private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f"; @@ -25,7 +25,7 @@ public void prepareMocksAndObjects() { int confirmsRequired = 10; String serviceAddress = "127.0.0.1:8081"; - xmrProofInfo = new XmrProofInfo( + xmrTxProofModel = new XmrTxProofModel( txHash, txKey, recipientAddress, @@ -37,44 +37,44 @@ public void prepareMocksAndObjects() { @Test public void testKey() { - assertTrue(xmrProofInfo.getUID().contains(xmrProofInfo.getTxHash())); - assertTrue(xmrProofInfo.getUID().contains(xmrProofInfo.getServiceAddress())); - assertFalse(xmrProofInfo.getUID().contains(xmrProofInfo.getRecipientAddress())); + assertTrue(xmrTxProofModel.getUID().contains(xmrTxProofModel.getTxHash())); + assertTrue(xmrTxProofModel.getUID().contains(xmrTxProofModel.getServiceAddress())); + assertFalse(xmrTxProofModel.getUID().contains(xmrTxProofModel.getRecipientAddress())); } @Test public void testJsonRoot() { // checking what happens when bad input is provided - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "invalid json data").getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "").getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "[]").getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{}").getState() == XmrTxProofResult.State.API_INVALID); } @Test public void testJsonTopLevel() { // testing the top level fields: data and status - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'title':''},'status':'fail'}" ) .getState() == XmrTxProofResult.State.TX_NOT_FOUND); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'title':''},'missingstatus':'success'}" ) .getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'missingdata':{'title':''},'status':'success'}" ) .getState() == XmrTxProofResult.State.API_INVALID); } @Test public void testJsonAddress() { - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) .getState() == XmrTxProofResult.State.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'address':'e957dac7'},'status':'success'}" ) .getState() == XmrTxProofResult.State.ADDRESS_INVALID); } @@ -82,11 +82,11 @@ public void testJsonAddress() { @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, missing_tx_hash).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_hash).getState() == XmrTxProofResult.State.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, invalid_tx_hash).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_hash).getState() == XmrTxProofResult.State.TX_HASH_INVALID); } @@ -94,13 +94,13 @@ public void testJsonTxHash() { public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, missing_tx_key).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_key).getState() == XmrTxProofResult.State.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, invalid_tx_key).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_key).getState() == XmrTxProofResult.State.TX_KEY_INVALID); } @@ -109,14 +109,14 @@ public void testJsonTxTimestamp() { String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, missing_tx_timestamp).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_timestamp).getState() == XmrTxProofResult.State.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, invalid_tx_timestamp).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_timestamp).getState() == XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING); } @@ -134,26 +134,26 @@ public void testJsonTxConfirmation() { "'viewkey':'" + txKey + "', " + "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() == XmrTxProofResult.State.PROOF_OK); json = json.replaceFirst("777", "0"); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() == XmrTxProofResult.State.TX_NOT_CONFIRMED); json = json.replaceFirst("100000000000", "100000000001"); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() == XmrTxProofResult.State.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); - assertTrue(XmrTxProofParser.parse(xmrProofInfo, json).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() == XmrTxProofResult.State.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertTrue(XmrTxProofParser.parse(xmrProofInfo, failedJson).getState() + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, failedJson).getState() == XmrTxProofResult.State.API_INVALID); } } From 0e2268d6fe530a72f826d5eb83abd93f95856493 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:37:43 -0500 Subject: [PATCH 39/85] Refactor: - Rename XmrTransferProofRequest to XmrTxProofRequest --- .../trade/autoconf/xmr/XmrTransferProofService.java | 6 +++--- ...ransferProofRequest.java => XmrTxProofRequest.java} | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrTransferProofRequest.java => XmrTxProofRequest.java} (95%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java index 31bd5e52247..90069faac09 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java @@ -34,7 +34,7 @@ */ @Slf4j class XmrTransferProofService { - private final Map map = new HashMap<>(); + private final Map map = new HashMap<>(); private final Socks5ProxyProvider socks5ProxyProvider; @Inject @@ -52,7 +52,7 @@ void requestProof(XmrTxProofModel xmrTxProofModel, } log.info("requesting tx proof with uid {}", uid); - XmrTransferProofRequest requester = new XmrTransferProofRequest( + XmrTxProofRequest requester = new XmrTxProofRequest( socks5ProxyProvider, xmrTxProofModel, result -> { @@ -71,7 +71,7 @@ void requestProof(XmrTxProofModel xmrTxProofModel, void terminateRequest(XmrTxProofModel xmrTxProofModel) { String uid = xmrTxProofModel.getUID(); - XmrTransferProofRequest requester = map.getOrDefault(uid, null); + XmrTxProofRequest requester = map.getOrDefault(uid, null); if (requester != null) { log.info("Terminating API request for request with uid {}", uid); requester.stop(); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java similarity index 95% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java index 3865a306bb7..c656ea521e8 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java @@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull; @Slf4j -class XmrTransferProofRequest { +class XmrTxProofRequest { // these settings are not likely to change and therefore not put into Config private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); @@ -57,10 +57,10 @@ class XmrTransferProofRequest { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTransferProofRequest(Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofModel xmrTxProofModel, - Consumer resultHandler, - FaultHandler faultHandler) { + XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, + XmrTxProofModel xmrTxProofModel, + Consumer resultHandler, + FaultHandler faultHandler) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); if (xmrTxProofModel.getServiceAddress().matches("^192.*|^localhost.*")) { From f3ad669fabfa4f131662283b4a6be11220dd3011 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:38:05 -0500 Subject: [PATCH 40/85] Refactor: - Rename XmrTransferProofService to XmrTxProofRequestService --- .../trade/autoconf/xmr/XmrAutoConfirmationManager.java | 10 +++++----- ...ProofService.java => XmrTxProofRequestService.java} | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrTransferProofService.java => XmrTxProofRequestService.java} (96%) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java index 09e5b90f46f..d7f4d196532 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java @@ -55,7 +55,7 @@ public class XmrAutoConfirmationManager { private final FilterManager filterManager; private final Preferences preferences; - private final XmrTransferProofService xmrTransferProofService; + private final XmrTxProofRequestService xmrTxProofRequestService; private final AccountAgeWitnessService accountAgeWitnessService; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; @@ -70,7 +70,7 @@ public class XmrAutoConfirmationManager { @Inject private XmrAutoConfirmationManager(FilterManager filterManager, Preferences preferences, - XmrTransferProofService xmrTransferProofService, + XmrTxProofRequestService xmrTxProofRequestService, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, P2PService p2PService, @@ -79,7 +79,7 @@ private XmrAutoConfirmationManager(FilterManager filterManager, ) { this.filterManager = filterManager; this.preferences = preferences; - this.xmrTransferProofService = xmrTransferProofService; + this.xmrTxProofRequestService = xmrTxProofRequestService; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; this.p2PService = p2PService; @@ -195,10 +195,10 @@ public void startRequestTxProofProcess(Trade trade, List activeTrades) { trade.getDate(), confirmsRequired, serviceAddress); - xmrTransferProofService.requestProof(xmrTxProofModel, + xmrTxProofRequestService.requestProof(xmrTxProofModel, result -> { if (!handleProofResult(result, trade)) - xmrTransferProofService.terminateRequest(xmrTxProofModel); + xmrTxProofRequestService.terminateRequest(xmrTxProofModel); }, (errorMsg, throwable) -> { log.warn(errorMsg); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java similarity index 96% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java index 90069faac09..c206543dcec 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTransferProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java @@ -33,12 +33,12 @@ * Manages the XMR transfers proof requests for multiple trades and multiple services. */ @Slf4j -class XmrTransferProofService { +class XmrTxProofRequestService { private final Map map = new HashMap<>(); private final Socks5ProxyProvider socks5ProxyProvider; @Inject - private XmrTransferProofService(Socks5ProxyProvider provider) { + private XmrTxProofRequestService(Socks5ProxyProvider provider) { socks5ProxyProvider = provider; } From aab478a63cccf6fd318f10cc89b77dd00e06b44c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 19:38:29 -0500 Subject: [PATCH 41/85] Refactor: - Rename XmrAutoConfirmationManager to XmrTxProofService --- .../java/bisq/core/trade/TradeManager.java | 8 ++++---- ...tionManager.java => XmrTxProofService.java} | 18 +++++++++--------- ...sCounterCurrencyTransferStartedMessage.java | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) rename core/src/main/java/bisq/core/trade/autoconf/xmr/{XmrAutoConfirmationManager.java => XmrTxProofService.java} (95%) diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 93a5f5ee615..cc1fd98ec49 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -38,7 +38,7 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.autoconf.xmr.XmrAutoConfirmationManager; +import bisq.core.trade.autoconf.xmr.XmrTxProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; @@ -128,7 +128,7 @@ public class TradeManager implements PersistedDataHost { private final ReferralIdService referralIdService; private final AccountAgeWitnessService accountAgeWitnessService; @Getter - private final XmrAutoConfirmationManager xmrAutoConfirmationManager; + private final XmrTxProofService xmrTxProofService; private final ArbitratorManager arbitratorManager; private final MediatorManager mediatorManager; private final RefundAgentManager refundAgentManager; @@ -170,7 +170,7 @@ public TradeManager(User user, TradeStatisticsManager tradeStatisticsManager, ReferralIdService referralIdService, AccountAgeWitnessService accountAgeWitnessService, - XmrAutoConfirmationManager xmrAutoConfirmationManager, + XmrTxProofService xmrTxProofService, ArbitratorManager arbitratorManager, MediatorManager mediatorManager, RefundAgentManager refundAgentManager, @@ -193,7 +193,7 @@ public TradeManager(User user, this.tradeStatisticsManager = tradeStatisticsManager; this.referralIdService = referralIdService; this.accountAgeWitnessService = accountAgeWitnessService; - this.xmrAutoConfirmationManager = xmrAutoConfirmationManager; + this.xmrTxProofService = xmrTxProofService; this.arbitratorManager = arbitratorManager; this.mediatorManager = mediatorManager; this.refundAgentManager = refundAgentManager; diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java similarity index 95% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java rename to core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java index d7f4d196532..8b1b28dcaef 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrAutoConfirmationManager.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java @@ -51,7 +51,7 @@ @Slf4j @Singleton -public class XmrAutoConfirmationManager { +public class XmrTxProofService { private final FilterManager filterManager; private final Preferences preferences; @@ -68,14 +68,14 @@ public class XmrAutoConfirmationManager { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private XmrAutoConfirmationManager(FilterManager filterManager, - Preferences preferences, - XmrTxProofRequestService xmrTxProofRequestService, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager, - P2PService p2PService, - WalletsSetup walletsSetup, - AccountAgeWitnessService accountAgeWitnessService + private XmrTxProofService(FilterManager filterManager, + Preferences preferences, + XmrTxProofRequestService xmrTxProofRequestService, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, + P2PService p2PService, + WalletsSetup walletsSetup, + AccountAgeWitnessService accountAgeWitnessService ) { this.filterManager = filterManager; this.preferences = preferences; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 30ea7c09be4..c97a9eb0ecf 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,7 +58,7 @@ protected void run() { String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getTradeManager().getXmrAutoConfirmationManager().startRequestTxProofProcess( + processModel.getTradeManager().getXmrTxProofService().startRequestTxProofProcess( trade, processModel.getTradeManager().getTradableList()); } processModel.removeMailboxMessageAfterProcessing(trade); From 29675308787f72be890e25420c428dbdd07d6fd3 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 21:37:17 -0500 Subject: [PATCH 42/85] Max amount was set to 0.1 BTC, should be 1 BTC --- core/src/main/java/bisq/core/user/Preferences.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 584e051e7d0..1493e779c6c 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -130,7 +130,7 @@ public static final List getDefaultXmrProofProviders() { if (DevEnv.isDevMode()) { return new ArrayList<>(Arrays.asList("78.47.61.90:8081")); } else { - // TODO we need at least 2 for relase + // TODO we need at least 2 for release return new ArrayList<>(Arrays.asList( "monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion")); } @@ -421,7 +421,7 @@ public AutoConfirmSettings getAutoConfirmSettings() { if (prefPayload.getAutoConfirmSettingsList().size() == 0) { // default values for AutoConfirmSettings when persisted payload is empty: prefPayload.getAutoConfirmSettingsList().add(new AutoConfirmSettings( - false, 5, Coin.valueOf(10000000).value, getDefaultXmrProofProviders(), "XMR")); + false, 5, Coin.COIN.value, getDefaultXmrProofProviders(), "XMR")); } return prefPayload.getAutoConfirmSettingsList().get(0); } @@ -781,7 +781,9 @@ public ArrayList getBlockChainExplorers() { } } - public ArrayList getBsqBlockChainExplorers() { return BSQ_MAIN_NET_EXPLORERS; } + public ArrayList getBsqBlockChainExplorers() { + return BSQ_MAIN_NET_EXPLORERS; + } public boolean showAgain(String key) { return !prefPayload.getDontShowAgainMap().containsKey(key) || !prefPayload.getDontShowAgainMap().get(key); @@ -879,8 +881,7 @@ else if (change.wasRemoved() && change.getRemovedSize() == 1 && initialReadDone) } private boolean blockExplorerExists(ArrayList explorers, - BlockChainExplorer explorer) - { + BlockChainExplorer explorer) { if (explorer != null && explorers != null && explorers.size() > 0) for (int i = 0; i < explorers.size(); i++) if (explorers.get(i).name.equals(explorer.name)) From 4f0e574bf9333db54721c913fb1f6ed0d0cf1bd3 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 30 Aug 2020 21:38:32 -0500 Subject: [PATCH 43/85] Use static field for dev test values --- .../bisq/core/trade/autoconf/xmr/XmrTxProofModel.java | 8 +++++++- .../desktop/main/overlays/windows/SetXmrTxKeyWindow.java | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java index 0fbfeea906c..97162b26714 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java @@ -24,7 +24,13 @@ @Slf4j @Value -class XmrTxProofModel { +public class XmrTxProofModel { + // Those are values from a valid tx which are set automatically if DevEnv.isDevMode is enabled + public static final String DEV_ADDRESS = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub"; + public static final String DEV_TX_KEY = "f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906"; + public static final String DEV_TX_HASH = "5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802"; + public static final long DEV_AMOUNT = 8902597360000L; + private final String txHash; private final String txKey; private final String recipientAddress; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index 42fcbd4969e..518b5da8d59 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -22,6 +22,7 @@ import bisq.desktop.util.validation.RegexValidator; import bisq.core.locale.Res; +import bisq.core.trade.autoconf.xmr.XmrTxProofModel; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; @@ -64,8 +65,8 @@ public void show() { txKeyInputTextField.setValidator(regexValidator); if (isDevMode()) { // pre-populate the fields with test data when in dev mode - txHashInputTextField.setText("e8dcd8160aee016d8a0d9c480355d65773dc577313a0af8237c35f9d997b01c0"); - txKeyInputTextField.setText("300fa18ff99b32ff097d75c64d62732bdb486af8c225f558ee48c5f777f9b509"); + txHashInputTextField.setText(XmrTxProofModel.DEV_TX_HASH); + txKeyInputTextField.setText(XmrTxProofModel.DEV_TX_KEY); } applyStyles(); From e9e7b489befa243fb8961a2d4d8f16a0c3034a62 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 31 Aug 2020 00:10:41 -0500 Subject: [PATCH 44/85] Various refactorings, bug fixes and improvements. Sorry for the messy commit... its late ;-) --- core/src/main/java/bisq/core/trade/Trade.java | 1 + .../java/bisq/core/trade/TradeManager.java | 5 + .../trade/autoconf/AssetTxProofResult.java | 1 + .../trade/autoconf/xmr/XmrTxProofParser.java | 41 +- .../trade/autoconf/xmr/XmrTxProofRequest.java | 15 +- .../xmr/XmrTxProofRequestService.java | 5 +- .../trade/autoconf/xmr/XmrTxProofResult.java | 141 +++++-- .../trade/autoconf/xmr/XmrTxProofService.java | 355 +++++++++++------- ...CounterCurrencyTransferStartedMessage.java | 2 +- .../resources/i18n/displayStrings.properties | 17 +- .../autoconf/xmr/XmrTxProofParserTest.java | 7 +- .../overlays/windows/TradeDetailsWindow.java | 25 +- .../steps/buyer/BuyerStep2View.java | 68 ++-- .../steps/buyer/BuyerStep4View.java | 4 +- .../steps/seller/SellerStep3View.java | 5 +- 15 files changed, 451 insertions(+), 241 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 2748acd35f2..bbb092662a0 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -1092,6 +1092,7 @@ public String getErrorMessage() { return errorMessageProperty.get(); } + @Nullable public AssetTxProofResult getAssetTxProofResult() { return assetTxProofResult != null ? assetTxProofResult : AssetTxProofResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); } diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index cc1fd98ec49..29d608c4413 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -324,6 +324,11 @@ private void initPendingTrades() { addTradeToFailedTradesList.add(trade); } } + + if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG && + trade.getCounterCurrencyExtraData() != null) { + xmrTxProofService.maybeStartRequestTxProofProcess(trade, tradableList.getList()); + } } ); diff --git a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java index 0ddcbef7559..d8ea275e044 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java @@ -31,6 +31,7 @@ @Getter public abstract class AssetTxProofResult { + @Nullable public static AssetTxProofResult fromCurrencyCode(String currencyCode) { //noinspection SwitchStatementWithTooFewBranches switch (currencyCode) { diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java index d3dc82b6d4c..a89a049ce4b 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java @@ -49,7 +49,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { if (jsonStatus.matches("fail")) { // the API returns "fail" until the transaction has successfully reached the mempool. // we return TX_NOT_FOUND which will cause a retry later - return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND, null); + return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND); } else if (!jsonStatus.matches("success")) { return new XmrTxProofResult(XmrTxProofResult.State.API_FAILURE, "Unhandled status value"); } @@ -62,7 +62,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrTxProofModel.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return new XmrTxProofResult(XmrTxProofResult.State.ADDRESS_INVALID, null); + return new XmrTxProofResult(XmrTxProofResult.State.ADDRESS_INVALID); } } @@ -73,7 +73,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { } else { if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return new XmrTxProofResult(XmrTxProofResult.State.TX_HASH_INVALID, null); + return new XmrTxProofResult(XmrTxProofResult.State.TX_HASH_INVALID); } } @@ -84,7 +84,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { } else { if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrTxProofModel.getTxKey())) { log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrTxProofModel.getTxKey()); - return new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_INVALID, null); + return new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_INVALID); } } @@ -100,7 +100,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return new XmrTxProofResult(XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING, null); + return new XmrTxProofResult(XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING); } } @@ -119,29 +119,38 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // (except that in dev mode we allow any amount as valid) JsonArray jsonOutputs = jsonData.get("outputs").getAsJsonArray(); boolean anyMatchFound = false; + boolean amountMatches = false; for (int i = 0; i < jsonOutputs.size(); i++) { JsonObject out = jsonOutputs.get(i).getAsJsonObject(); if (out.get("match").getAsBoolean()) { anyMatchFound = true; long jsonAmount = out.get("amount").getAsLong(); - if (jsonAmount == xmrTxProofModel.getAmount() || DevEnv.isDevMode()) { // any amount ok in dev mode - int confirmsRequired = xmrTxProofModel.getConfirmsRequired(); - if (confirmations < confirmsRequired) - // we return TX_NOT_CONFIRMED which will cause a retry later - return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_CONFIRMED, confirmations, confirmsRequired); - else - return new XmrTxProofResult(XmrTxProofResult.State.PROOF_OK, confirmations, confirmsRequired); + amountMatches = jsonAmount == xmrTxProofModel.getAmount(); + if (amountMatches) { + break; } } } // None of the outputs had a match entry - if (!anyMatchFound && !DevEnv.isDevMode()) { - return new XmrTxProofResult(XmrTxProofResult.State.NO_MATCH_FOUND, null); + if (!anyMatchFound) { + return new XmrTxProofResult(XmrTxProofResult.State.NO_MATCH_FOUND); } - // reaching this point means there was no matching amount - return new XmrTxProofResult(XmrTxProofResult.State.AMOUNT_NOT_MATCHING, null); + // None of the outputs had a match entry + if (!amountMatches) { + return new XmrTxProofResult(XmrTxProofResult.State.AMOUNT_NOT_MATCHING); + } + + int confirmsRequired = xmrTxProofModel.getConfirmsRequired(); + if (confirmations < confirmsRequired) { + XmrTxProofResult xmrTxProofResult = new XmrTxProofResult(XmrTxProofResult.State.PENDING_CONFIRMATIONS); + xmrTxProofResult.setNumConfirmations(confirmations); + xmrTxProofResult.setRequiredConfirmations(confirmsRequired); + return xmrTxProofResult; + } else { + return new XmrTxProofResult(XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED); + } } catch (JsonParseException | NullPointerException e) { return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, e.toString()); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java index c656ea521e8..0cf8d809b2c 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java @@ -24,6 +24,9 @@ import bisq.common.handlers.FaultHandler; import bisq.common.util.Utilities; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; + import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -36,6 +39,10 @@ import org.jetbrains.annotations.NotNull; +/** + * Requests for the XMR tx proof for a particular trade and one service. + * Repeats requests if tx is not confirmed yet. + */ @Slf4j class XmrTxProofRequest { // these settings are not likely to change and therefore not put into Config @@ -98,9 +105,11 @@ public void request() { "&txprove=1"; log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - XmrTxProofResult autoConfirmResult = XmrTxProofParser.parse(xmrTxProofModel, json); - log.info("Response json {} resulted in autoConfirmResult {}", json, autoConfirmResult); - return autoConfirmResult; + String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); + log.info("Response json\n{}", prettyJson); + XmrTxProofResult xmrTxProofResult = XmrTxProofParser.parse(xmrTxProofModel, json); + log.info("xmrTxProofResult {}", xmrTxProofResult); + return xmrTxProofResult; }); Futures.addCallback(future, new FutureCallback<>() { diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java index c206543dcec..91c562370c2 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java @@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j; /** - * Manages the XMR transfers proof requests for multiple trades and multiple services. + * Handles the XMR tx proof requests for multiple trades and multiple services. */ @Slf4j class XmrTxProofRequestService { @@ -56,7 +56,7 @@ void requestProof(XmrTxProofModel xmrTxProofModel, socks5ProxyProvider, xmrTxProofModel, result -> { - if (result.isSuccessState()) { + if (result.getState() == XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED) { cleanup(uid); } resultHandler.accept(result); @@ -66,6 +66,7 @@ void requestProof(XmrTxProofModel xmrTxProofModel, faultHandler.handleFault(errorMsg, throwable); }); map.put(uid, requester); + requester.request(); } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java index c40779695c8..f0776a2a1ab 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java @@ -24,20 +24,35 @@ import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; @Slf4j -@Getter @EqualsAndHashCode(callSuper = true) +@ToString public class XmrTxProofResult extends AssetTxProofResult { public enum State { UNDEFINED, + + // Feature disable cases FEATURE_DISABLED, + TRADE_LIMIT_EXCEEDED, + + // Pending state + REQUEST_STARTED, TX_NOT_FOUND, - TX_NOT_CONFIRMED, - PROOF_OK, + PENDING_SERVICE_RESULTS, + PENDING_CONFIRMATIONS, + + SINGLE_SERVICE_SUCCEEDED, // Individual service has delivered proof ok + + // Success state + ALL_SERVICES_SUCCEEDED, // All services delivered PROOF_OK + + // Error state CONNECTION_FAIL, API_FAILURE, API_INVALID, @@ -47,13 +62,21 @@ public enum State { ADDRESS_INVALID, NO_MATCH_FOUND, AMOUNT_NOT_MATCHING, - TRADE_LIMIT_EXCEEDED, TRADE_DATE_NOT_MATCHING } - private final State state; - private final transient int confirmCount; - private final transient int confirmsRequired; + @Getter + private transient final State state; + @Setter + private transient int numConfirmations; + @Setter + private transient int requiredConfirmations; + @Nullable + private transient String errorMsg; + @Setter + private transient int pendingServiceResults; + @Setter + private transient int requiredServiceResults; /////////////////////////////////////////////////////////////////////////////////////////// @@ -61,28 +84,20 @@ public enum State { /////////////////////////////////////////////////////////////////////////////////////////// public XmrTxProofResult() { - this(State.UNDEFINED, 0, 0); + this(State.UNDEFINED); } XmrTxProofResult(State state) { - this(state, 0, 0); - } - - // alternate constructor for showing confirmation progress information - XmrTxProofResult(State state, int confirmCount, int confirmsRequired) { super(state.name()); + this.state = state; - this.confirmCount = confirmCount; - this.confirmsRequired = confirmsRequired; } - // alternate constructor for error scenarios - XmrTxProofResult(State state, @Nullable String errorMsg) { - this(state, 0, 0); + XmrTxProofResult(State state, String errorMsg) { + this(state); - if (!isPendingState() && !isSuccessState() && state != State.FEATURE_DISABLED && state != State.UNDEFINED) { - log.error(errorMsg != null ? errorMsg : state.toString()); - } + this.errorMsg = errorMsg; + log.error(errorMsg); } @@ -107,34 +122,86 @@ public static XmrTxProofResult fromProto(protobuf.AutoConfirmResult proto) { @Override public String getStatusAsDisplayString() { + String key = "portfolio.pending.autoConf.state." + state; switch (state) { - case TX_NOT_CONFIRMED: - return Res.get("portfolio.pending.autoConfirmPending") - + " " + confirmCount - + "/" + confirmsRequired; - case TX_NOT_FOUND: - return Res.get("portfolio.pending.autoConfirmTxNotFound"); + // Invalid protobuf data + case UNDEFINED: + return state.toString(); + + // Feature disable cases case FEATURE_DISABLED: - return Res.get("portfolio.pending.autoConfirmDisabled"); - case PROOF_OK: - return Res.get("portfolio.pending.autoConfirmSuccess"); + case TRADE_LIMIT_EXCEEDED: + + // Pending state + case REQUEST_STARTED: + case TX_NOT_FOUND: // Tx still not confirmed and not in mempool + return Res.get(key); + case PENDING_SERVICE_RESULTS: + return Res.get(key, pendingServiceResults, requiredServiceResults); + case PENDING_CONFIRMATIONS: + return Res.get(key, numConfirmations, requiredConfirmations); + case SINGLE_SERVICE_SUCCEEDED: + + // Success state + case ALL_SERVICES_SUCCEEDED: + return Res.get(key); + + // Error state + case CONNECTION_FAIL: + case API_FAILURE: + case API_INVALID: + case TX_KEY_REUSED: + case TX_HASH_INVALID: + case TX_KEY_INVALID: + case ADDRESS_INVALID: + case NO_MATCH_FOUND: + case AMOUNT_NOT_MATCHING: + case TRADE_DATE_NOT_MATCHING: + return getErrorMsg(); + default: - // any other statuses we display the enum name - return this.state.toString(); + return state.toString(); } } @Override public boolean isSuccessState() { - return (state == State.PROOF_OK); + return (state == State.ALL_SERVICES_SUCCEEDED); } + boolean isErrorState() { + switch (state) { + case CONNECTION_FAIL: + case API_FAILURE: + case API_INVALID: + case TX_KEY_REUSED: + case TX_HASH_INVALID: + case TX_KEY_INVALID: + case ADDRESS_INVALID: + case NO_MATCH_FOUND: + case AMOUNT_NOT_MATCHING: + case TRADE_DATE_NOT_MATCHING: + return true; - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// + default: + return false; + } + } boolean isPendingState() { - return (state == State.TX_NOT_FOUND || state == State.TX_NOT_CONFIRMED); + switch (state) { + case REQUEST_STARTED: + case TX_NOT_FOUND: + case PENDING_SERVICE_RESULTS: + case PENDING_CONFIRMATIONS: + return true; + + default: + return false; + } + } + + private String getErrorMsg() { + return errorMsg != null ? errorMsg : state.name(); } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java index 8b1b28dcaef..bbd451ef9b7 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java @@ -21,10 +21,8 @@ import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; import bisq.core.monetary.Volume; -import bisq.core.offer.Offer; import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Contract; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.closed.ClosedTradableManager; @@ -40,19 +38,25 @@ import javax.inject.Inject; import javax.inject.Singleton; +import javafx.beans.value.ChangeListener; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import static com.google.common.base.Preconditions.checkNotNull; +/** + * Entry point for clients to request tx proof and trigger auto-confirm if all conditions + * are met. + */ @Slf4j @Singleton public class XmrTxProofService { - private final FilterManager filterManager; private final Preferences preferences; private final XmrTxProofRequestService xmrTxProofRequestService; @@ -61,7 +65,8 @@ public class XmrTxProofService { private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; - private final Map txProofResultsPending = new HashMap<>(); + private final Map requestInfoByTxIdMap = new HashMap<>(); + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor @@ -75,8 +80,7 @@ private XmrTxProofService(FilterManager filterManager, FailedTradesManager failedTradesManager, P2PService p2PService, WalletsSetup walletsSetup, - AccountAgeWitnessService accountAgeWitnessService - ) { + AccountAgeWitnessService accountAgeWitnessService) { this.filterManager = filterManager; this.preferences = preferences; this.xmrTxProofRequestService = xmrTxProofRequestService; @@ -87,141 +91,114 @@ private XmrTxProofService(FilterManager filterManager, this.accountAgeWitnessService = accountAgeWitnessService; } + /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// - public void startRequestTxProofProcess(Trade trade, List activeTrades) { - String counterCurrencyExtraData = trade.getCounterCurrencyExtraData(); - if (counterCurrencyExtraData == null || counterCurrencyExtraData.isEmpty()) { + public void maybeStartRequestTxProofProcess(Trade trade, List activeTrades) { + if (!dataValid(trade)) { return; } - String txHash = trade.getCounterCurrencyTxId(); - if (txHash == null || txHash.isEmpty()) { + if (!isXmrBuyer(trade)) { return; } - Contract contract = checkNotNull(trade.getContract(), "Contract must not be null"); - PaymentAccountPayload sellersPaymentAccountPayload = contract.getSellerPaymentAccountPayload(); - if (!(sellersPaymentAccountPayload instanceof AssetsAccountPayload)) { + if (!featureEnabled(trade)) { return; } - AssetsAccountPayload sellersAssetsAccountPayload = (AssetsAccountPayload) sellersPaymentAccountPayload; - if (!(trade instanceof SellerTrade)) { + if (!networkAndWalletReady()) { return; } - // Take the safe option and don't begin auto confirmation if the app has not reached a high enough level - // of operation. In that case it will be left for the user to confirm the trade manually which is fine. - if (!p2PService.isBootstrapped()) { - return; - } - if (!walletsSetup.hasSufficientPeersForBroadcast()) { + if (isTradeAmountAboveLimit(trade)) { return; } - if (!walletsSetup.isDownloadComplete()) { + + if (wasTxKeyReUsed(trade, activeTrades)) { return; } - Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); - if (offer.getCurrencyCode().equals("XMR")) { - //noinspection UnnecessaryLocalVariable - String txKey = counterCurrencyExtraData; - - if (!txHash.matches("[a-fA-F0-9]{64}") || !txKey.matches("[a-fA-F0-9]{64}")) { - log.error("Validation failed: txHash {} txKey {}", txHash, txKey); - return; - } - - // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with - // the same user (same address) and same amount. We check only for the txKey as a same txHash but different - // txKey is not possible to get a valid result at proof. - Stream failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getFailedTrades().stream()); - Stream closedTrades = closedTradableManager.getClosedTradables().stream() - .filter(tradable -> tradable instanceof Trade) - .map(tradable -> (Trade) tradable); - Stream allTrades = Stream.concat(failedAndOpenTrades, closedTrades); - - boolean txKeyUsedAtAnyOpenTrade = allTrades - .filter(t -> !t.getId().equals(trade.getId())) // ignore same trade - .anyMatch(t -> { - String extra = t.getCounterCurrencyExtraData(); - if (extra == null) { - return false; - } - - boolean alreadyUsed = extra.equals(txKey); - if (alreadyUsed) { - String message = "Peer used the XMR tx key already at another trade with trade ID " + - t.getId() + ". This might be a scam attempt."; - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_REUSED, message)); - } - return alreadyUsed; - }); + Coin tradeAmount = trade.getTradeAmount(); + Volume volume = checkNotNull(trade.getOffer()).getVolumeByAmount(tradeAmount); + // XMR satoshis have 12 decimal places vs. bitcoin's 8 + long amountXmr = volume != null ? volume.getValue() * 10000L : 0L; + + PaymentAccountPayload sellersPaymentAccountPayload = checkNotNull(trade.getContract()).getSellerPaymentAccountPayload(); + String recipientAddress = ((AssetsAccountPayload) sellersPaymentAccountPayload).getAddress(); + if (DevEnv.isDevMode()) { + // For dev testing we need to add the matching address to the dev tx key and dev view key + recipientAddress = XmrTxProofModel.DEV_ADDRESS; + amountXmr = XmrTxProofModel.DEV_AMOUNT; + } + int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; + String txHash = trade.getCounterCurrencyTxId(); + String txKey = trade.getCounterCurrencyExtraData(); + List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; - if (txKeyUsedAtAnyOpenTrade && !DevEnv.isDevMode()) { - return; - } - if (!preferences.getAutoConfirmSettings().enabled || this.isAutoConfDisabledByFilter()) { - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.FEATURE_DISABLED, null)); - return; - } - Coin tradeAmount = trade.getTradeAmount(); - Coin tradeLimit = Coin.valueOf(preferences.getAutoConfirmSettings().tradeLimit); - if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { - log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", - tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TRADE_LIMIT_EXCEEDED, null)); - return; + ChangeListener listener = (observable, oldValue, newValue) -> { + if (trade.isPayoutPublished()) { + log.warn("Trade payout already published, shutting down all open API requests for trade {}", + trade.getShortId()); + cleanup(trade); } - - String address = sellersAssetsAccountPayload.getAddress(); - // XMR satoshis have 12 decimal places vs. bitcoin's 8 - Volume volume = offer.getVolumeByAmount(tradeAmount); - long amountXmr = volume != null ? volume.getValue() * 10000L : 0L; - int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND)); - List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; - txProofResultsPending.put(trade.getId(), serviceAddresses.size()); // need result from each service address - for (String serviceAddress : serviceAddresses) { - XmrTxProofModel xmrTxProofModel = new XmrTxProofModel( - txHash, - txKey, - address, - amountXmr, - trade.getDate(), - confirmsRequired, - serviceAddress); - xmrTxProofRequestService.requestProof(xmrTxProofModel, - result -> { - if (!handleProofResult(result, trade)) - xmrTxProofRequestService.terminateRequest(xmrTxProofModel); - }, - (errorMsg, throwable) -> { - log.warn(errorMsg); + }; + trade.stateProperty().addListener(listener); + requestInfoByTxIdMap.put(trade.getId(), new RequestInfo(serviceAddresses.size(), listener)); // need result from each service address + + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.REQUEST_STARTED)); + for (String serviceAddress : serviceAddresses) { + XmrTxProofModel xmrTxProofModel = new XmrTxProofModel( + txHash, + txKey, + recipientAddress, + amountXmr, + trade.getDate(), + confirmsRequired, + serviceAddress); + xmrTxProofRequestService.requestProof(xmrTxProofModel, + result -> { + if (!handleProofResult(result, trade)) { + xmrTxProofRequestService.terminateRequest(xmrTxProofModel); } - ); - } + }, + (errorMsg, throwable) -> { + log.warn(errorMsg); + } + ); } } private boolean handleProofResult(XmrTxProofResult result, Trade trade) { // here we count the Trade's API results from all // different serviceAddress and figure out when all have finished - int resultsCountdown = txProofResultsPending.getOrDefault(trade.getId(), 0); - if (resultsCountdown < 0) { // see failure scenario below + if (!requestInfoByTxIdMap.containsKey(trade.getId())) { + // We have cleaned up our map in the meantime + return false; + } + + RequestInfo requestInfo = requestInfoByTxIdMap.get(trade.getId()); + + if (requestInfo.isInvalid()) { log.info("Ignoring stale API result [{}], tradeId {} due to previous error", result.getState(), trade.getShortId()); return false; // terminate any pending responses } if (trade.isPayoutPublished()) { - log.warn("Trade payout already published, shutting down all open API requests for this trade {}", + log.warn("Trade payout already published, shutting down all open API requests for trade {}", trade.getShortId()); - txProofResultsPending.remove(trade.getId()); + cleanup(trade); + } + + if (result.isErrorState()) { + log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", + result.getState(), trade.getShortId()); + trade.setAssetTxProofResult(result); // this updates the GUI with the status.. + requestInfo.invalidate(); return false; } @@ -233,45 +210,169 @@ private boolean handleProofResult(XmrTxProofResult result, Trade trade) { return true; } - if (result.isSuccessState()) { - resultsCountdown -= 1; + + if (result.getState() == XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED) { + int resultsCountdown = requestInfo.decrementAndGet(); log.info("Received a {} result, remaining proofs needed: {}, tradeId {}", result.getState(), resultsCountdown, trade.getShortId()); - if (resultsCountdown > 0) { - txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count + if (requestInfo.hasPendingResults()) { + XmrTxProofResult assetTxProofResult = new XmrTxProofResult(XmrTxProofResult.State.PENDING_SERVICE_RESULTS); + assetTxProofResult.setPendingServiceResults(requestInfo.getPendingResults()); + assetTxProofResult.setRequiredServiceResults(requestInfo.getNumServices()); + trade.setAssetTxProofResult(assetTxProofResult); return true; // not all APIs have confirmed yet } - // we've received the final PROOF_OK, all good here. - txProofResultsPending.remove(trade.getId()); - trade.setAssetTxProofResult(result); // this updates the GUI with the status.. + + // All our services have returned a PROOF_OK result so we have succeeded. + cleanup(trade); + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.ALL_SERVICES_SUCCEEDED)); log.info("Auto confirm was successful, transitioning trade {} to next step...", trade.getShortId()); if (!trade.isPayoutPublished()) { - // note that this state can also be triggered by auto confirmation feature - trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); + // Trade state update is handled in the trade protocol method triggered by the onFiatPaymentReceived call + // This triggers the completion of the trade with signing and publishing the payout tx + ((SellerTrade) trade).onFiatPaymentReceived(() -> { + }, + errorMessage -> { + }); } accountAgeWitnessService.maybeSignWitness(trade); - // transition the trade to step 4: - ((SellerTrade) trade).onFiatPaymentReceived(() -> { - }, - errorMessage -> { - }); + return true; + } else { + //TODO check if that can happen + log.error("Unexpected state {}", result.getState()); + return false; } + } - // error case. any validation error from XmrProofRequester or XmrProofInfo.check - // the following error codes will end up here: - // CONNECTION_FAIL, API_FAILURE, API_INVALID, TX_KEY_REUSED, TX_HASH_INVALID, - // TX_KEY_INVALID, ADDRESS_INVALID, NO_MATCH_FOUND, AMOUNT_NOT_MATCHING, TRADE_DATE_NOT_MATCHING - log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", - result.getState(), trade.getShortId()); - trade.setAssetTxProofResult(result); // this updates the GUI with the status.. - resultsCountdown = -1; // signal all API requesters to cease - txProofResultsPending.put(trade.getId(), resultsCountdown); // track proof result count - return false; + private boolean dataValid(Trade trade) { + String txKey = trade.getCounterCurrencyExtraData(); + String txHash = trade.getCounterCurrencyTxId(); + + if (txKey == null || txKey.isEmpty()) { + return false; + } + + if (txHash == null || txHash.isEmpty()) { + return false; + } + + if (!txHash.matches("[a-fA-F0-9]{64}") || !txKey.matches("[a-fA-F0-9]{64}")) { + log.error("Validation failed: txHash {} txKey {}", txHash, txKey); + return false; + } + + return true; + } + + private boolean isXmrBuyer(Trade trade) { + if (!checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) { + return false; + } + + if (!(trade instanceof SellerTrade)) { + return false; + } + + return checkNotNull(trade.getContract()).getSellerPaymentAccountPayload() instanceof AssetsAccountPayload; + } + + private boolean networkAndWalletReady() { + return p2PService.isBootstrapped() && + walletsSetup.isDownloadComplete() && + walletsSetup.hasSufficientPeersForBroadcast(); + } + + private boolean featureEnabled(Trade trade) { + boolean isEnabled = preferences.getAutoConfirmSettings().enabled && !isAutoConfDisabledByFilter(); + if (!isEnabled) { + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.FEATURE_DISABLED)); + } + return isEnabled; } private boolean isAutoConfDisabledByFilter() { return filterManager.getFilter() != null && filterManager.getFilter().isDisableAutoConf(); } + + private boolean isTradeAmountAboveLimit(Trade trade) { + Coin tradeAmount = trade.getTradeAmount(); + Coin tradeLimit = Coin.valueOf(preferences.getAutoConfirmSettings().tradeLimit); + if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { + log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", + tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); + + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TRADE_LIMIT_EXCEEDED)); + return true; + } + return false; + } + + private void cleanup(Trade trade) { + trade.stateProperty().removeListener(requestInfoByTxIdMap.get(trade.getId()).getListener()); + requestInfoByTxIdMap.remove(trade.getId()); + } + + private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { + if (DevEnv.isDevMode()) { + return false; + } + + // We need to prevent that a user tries to scam by reusing a txKey and txHash of a previous XMR trade with + // the same user (same address) and same amount. We check only for the txKey as a same txHash but different + // txKey is not possible to get a valid result at proof. + Stream failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getFailedTrades().stream()); + Stream closedTrades = closedTradableManager.getClosedTradables().stream() + .filter(tradable -> tradable instanceof Trade) + .map(tradable -> (Trade) tradable); + Stream allTrades = Stream.concat(failedAndOpenTrades, closedTrades); + String txKey = trade.getCounterCurrencyExtraData(); + return allTrades + .filter(t -> !t.getId().equals(trade.getId())) // ignore same trade + .anyMatch(t -> { + String extra = t.getCounterCurrencyExtraData(); + if (extra == null) { + return false; + } + + boolean alreadyUsed = extra.equals(txKey); + if (alreadyUsed) { + String message = "Peer used the XMR tx key already at another trade with trade ID " + + t.getId() + ". This might be a scam attempt."; + trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_REUSED, message)); + } + return alreadyUsed; + }); + } + + @Getter + private static class RequestInfo { + private final int numServices; + private int pendingResults; + private ChangeListener listener; + + RequestInfo(int numServices, ChangeListener listener) { + this.numServices = numServices; + this.pendingResults = numServices; + this.listener = listener; + } + + int decrementAndGet() { + pendingResults--; + return pendingResults; + } + + void invalidate() { + pendingResults = -1; + } + + boolean isInvalid() { + return pendingResults < 0; + } + + boolean hasPendingResults() { + return pendingResults > 0; + } + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index c97a9eb0ecf..db3a27ea3ca 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,7 +58,7 @@ protected void run() { String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getTradeManager().getXmrTxProofService().startRequestTxProofProcess( + processModel.getTradeManager().getXmrTxProofService().maybeStartRequestTxProofProcess( trade, processModel.getTradeManager().getTradableList()); } processModel.removeMailboxMessageAfterProcessing(trade); diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 21732654414..8a2af031599 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -568,13 +568,20 @@ portfolio.pending.step2_buyer.startPayment=Start payment portfolio.pending.step2_seller.waitPaymentStarted=Wait until payment has started portfolio.pending.step3_buyer.waitPaymentArrived=Wait until payment arrived portfolio.pending.step3_seller.confirmPaymentReceived=Confirm payment received -portfolio.pending.step3_seller.autoConfirmStatus=Auto-confirm status -portfolio.pending.autoConfirmTxNotFound=Transaction not found -portfolio.pending.autoConfirmPending=Pending -portfolio.pending.autoConfirmDisabled=Disabled -portfolio.pending.autoConfirmSuccess=Auto-confirmed portfolio.pending.step5.completed=Completed +portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status +portfolio.pending.autoConf=Auto-confirmed + +portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabled +portfolio.pending.autoConf.state.TRADE_LIMIT_EXCEEDED=Trade amount exceeds auto-confirm amount limit +portfolio.pending.autoConf.state.REQUEST_STARTED=Proof request started +portfolio.pending.autoConf.state.TX_NOT_FOUND=Transaction not confirmed yet +portfolio.pending.autoConf.state.PENDING_SERVICE_RESULTS=Pending service results: {0} of {1} +portfolio.pending.autoConf.state.PENDING_CONFIRMATIONS=Confirmations: {0} of {1} required +portfolio.pending.autoConf.state.SINGLE_SERVICE_SUCCEEDED=A service succeeded. Pending other services. +portfolio.pending.autoConf.state.ALL_SERVICES_SUCCEEDED=All proof services succeeded + portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for at least one blockchain confirmation before starting the payment. portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low. portfolio.pending.step1.openForDispute=The deposit transaction is still not confirmed. \ diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java index 96f5faad690..10c3c84ea2f 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java @@ -19,7 +19,6 @@ public class XmrTxProofParserTest { @Before public void prepareMocksAndObjects() { - long amount = 100000000000L; Date tradeDate = Date.from(Instant.now()); int confirmsRequired = 10; @@ -135,10 +134,12 @@ public void testJsonTxConfirmation() { "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() - == XmrTxProofResult.State.PROOF_OK); + == XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED); json = json.replaceFirst("777", "0"); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() - == XmrTxProofResult.State.TX_NOT_CONFIRMED); + == XmrTxProofResult.State.PENDING_CONFIRMATIONS); + json = json.replaceFirst("100000000000", "100000000001"); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() == XmrTxProofResult.State.AMOUNT_NOT_MATCHING); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 3a62347f8dc..7a13fcca02b 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -33,6 +33,7 @@ import bisq.core.trade.Contract; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; +import bisq.core.trade.autoconf.xmr.XmrTxProofResult; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -67,6 +68,7 @@ import org.slf4j.LoggerFactory; import static bisq.desktop.util.FormBuilder.*; +import static com.google.common.base.Preconditions.checkNotNull; public class TradeDetailsWindow extends Overlay { protected static final Logger log = LoggerFactory.getLogger(TradeDetailsWindow.class); @@ -159,9 +161,6 @@ private void addContent() { addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradePrice"), FormattingUtils.formatPrice(trade.getTradePrice())); String paymentMethodText = Res.get(offer.getPaymentMethod().getId()); - if (trade.getAssetTxProofResult().isSuccessState()) { - paymentMethodText += " (" + trade.getAssetTxProofResult().getStatusAsDisplayString() + ")"; - } addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.paymentMethod"), paymentMethodText); // second group @@ -235,11 +234,21 @@ private void addContent() { addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradingPeersOnion"), trade.getTradingPeerNodeAddress().getFullAddress()); - addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, - Res.get("tradeDetailsWindow.tradingPeersPubKeyHash"), - trade.getContract() != null ? Utils.HEX.encode(trade.getContract().getPeersPubKeyRing( - tradeManager.getKeyRing().getPubKeyRing()).getSignaturePubKeyBytes()) : - Res.get("shared.na")); + if (checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR") && + trade.getAssetTxProofResult() != null && + ((XmrTxProofResult) trade.getAssetTxProofResult()).getState() != XmrTxProofResult.State.UNDEFINED) { + // As the window is already overloaded we replace the tradingPeersPubKeyHash field with the auto-conf state + // if XMR is the currency + addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, + Res.get("portfolio.pending.step3_seller.autoConf.status.label"), + trade.getAssetTxProofResult().getStatusAsDisplayString()); + } else { + addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, + Res.get("tradeDetailsWindow.tradingPeersPubKeyHash"), + trade.getContract() != null ? Utils.HEX.encode(trade.getContract().getPeersPubKeyRing( + tradeManager.getKeyRing().getPubKeyRing()).getSignaturePubKeyBytes()) : + Res.get("shared.na")); + } if (contract != null) { if (buyerPaymentAccountPayload != null) { diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index baa265798f4..2f09cf258a9 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -457,40 +457,40 @@ private void onPaymentStarted() { } else { showConfirmPaymentStartedPopup(); } - } else if (sellersPaymentAccountPayload instanceof AssetsAccountPayload) { - Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null"); - if (offer.getCurrencyCode().equals("XMR")) { - SetXmrTxKeyWindow setXmrTxKeyWindow = new SetXmrTxKeyWindow(); - setXmrTxKeyWindow - .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) - .onAction(() -> { - String txKey = setXmrTxKeyWindow.getTxKey(); - String txHash = setXmrTxKeyWindow.getTxHash(); - if (txKey == null || txHash == null || txKey.isEmpty() || txHash.isEmpty()) { - UserThread.runAfter(this::showProofWarningPopup, Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); - return; - } - - InputValidator.ValidationResult validateTxKey = setXmrTxKeyWindow.getRegexValidator().validate(txKey); - if (!validateTxKey.isValid) { - UserThread.runAfter(() -> new Popup().warning(validateTxKey.errorMessage).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); - return; - } - - InputValidator.ValidationResult validateTxHash = setXmrTxKeyWindow.getRegexValidator().validate(txHash); - if (!validateTxHash.isValid) { - UserThread.runAfter(() -> new Popup().warning(validateTxHash.errorMessage).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); - return; - } - - trade.setCounterCurrencyExtraData(txKey); - trade.setCounterCurrencyTxId(txHash); - showConfirmPaymentStartedPopup(); - }) - .closeButtonText(Res.get("shared.cancel")) - .onClose(setXmrTxKeyWindow::hide) - .show(); - } + } else if (sellersPaymentAccountPayload instanceof AssetsAccountPayload && + checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) { + SetXmrTxKeyWindow setXmrTxKeyWindow = new SetXmrTxKeyWindow(); + setXmrTxKeyWindow + .actionButtonText(Res.get("portfolio.pending.step2_buyer.confirmStart.headline")) + .onAction(() -> { + String txKey = setXmrTxKeyWindow.getTxKey(); + String txHash = setXmrTxKeyWindow.getTxHash(); + if (txKey == null || txHash == null || txKey.isEmpty() || txHash.isEmpty()) { + UserThread.runAfter(this::showProofWarningPopup, Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + return; + } + + InputValidator.ValidationResult validateTxKey = setXmrTxKeyWindow.getRegexValidator().validate(txKey); + if (!validateTxKey.isValid) { + UserThread.runAfter(() -> new Popup().warning(validateTxKey.errorMessage).show(), + Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + return; + } + + InputValidator.ValidationResult validateTxHash = setXmrTxKeyWindow.getRegexValidator().validate(txHash); + if (!validateTxHash.isValid) { + UserThread.runAfter(() -> new Popup().warning(validateTxHash.errorMessage).show(), + Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); + return; + } + + trade.setCounterCurrencyExtraData(txKey); + trade.setCounterCurrencyTxId(txHash); + showConfirmPaymentStartedPopup(); + }) + .closeButtonText(Res.get("shared.cancel")) + .onClose(setXmrTxKeyWindow::hide) + .show(); } else { showConfirmPaymentStartedPopup(); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 091b2ed7cf8..e6137a1efa3 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -110,14 +110,14 @@ protected void addContent() { completedTradeLabel.setText(Res.get("portfolio.pending.step5_buyer.groupTitle")); JFXBadge autoConfBadge = new JFXBadge(new Label(""), Pos.BASELINE_RIGHT); - autoConfBadge.setText(Res.get("portfolio.pending.autoConfirmSuccess")); + autoConfBadge.setText(Res.get("portfolio.pending.autoConf")); autoConfBadge.getStyleClass().add("autoconf"); HBox hBox2 = new HBox(1, completedTradeLabel, autoConfBadge); GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); gridPane.getChildren().add(hBox2); GridPane.setRowSpan(hBox2, 5); - if (!trade.getAssetTxProofResult().isSuccessState()) { + if (trade.getAssetTxProofResult() != null && !trade.getAssetTxProofResult().isSuccessState()) { autoConfBadge.setVisible(false); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 9ea9212eba4..d6c7d944d1d 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -151,9 +151,8 @@ public void activate() { }); // we listen for updates on the trade autoConfirmResult field - if (autoConfirmStatusField != null) { + if (trade.getAssetTxProofResult() != null && autoConfirmStatusField != null) { trade.getAssetTxProofResultProperty().addListener(autoConfirmResultListener); - // display the initial value, or FEATURE_DISABLED if there is none autoConfirmStatusField.setText(trade.getAssetTxProofResult().getStatusAsDisplayString()); } } @@ -227,7 +226,7 @@ protected void addContent() { if (isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR")) { autoConfirmStatusField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, - Res.get("portfolio.pending.step3_seller.autoConfirmStatus"), + Res.get("portfolio.pending.step3_seller.autoConf.status.label"), "", Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE).second; } From 12ef5aafe09220a3f683a368a94e6af9b85dc426 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 31 Aug 2020 16:25:07 -0500 Subject: [PATCH 45/85] Update comment --- .../java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java index a89a049ce4b..1221f602d7e 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java @@ -47,8 +47,9 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); if (jsonStatus.matches("fail")) { - // the API returns "fail" until the transaction has successfully reached the mempool. - // we return TX_NOT_FOUND which will cause a retry later + // The API returns "fail" until the transaction has successfully reached the mempool or if request + // contained invalid data. + // We return TX_NOT_FOUND which will cause a retry later return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND); } else if (!jsonStatus.matches("success")) { return new XmrTxProofResult(XmrTxProofResult.State.API_FAILURE, "Unhandled status value"); From 7b3dc54815f4810a9ed1b48a2a831c7eb6768057 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 31 Aug 2020 19:29:56 -0500 Subject: [PATCH 46/85] dont use restart routing for seed nodes if in devmode --- .../java/bisq/core/app/misc/ExecutableForAppWithP2p.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index d4c14206708..592f887dbc9 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -29,6 +29,7 @@ import bisq.network.p2p.seed.SeedNodeRepository; import bisq.common.UserThread; +import bisq.common.app.DevEnv; import bisq.common.config.Config; import bisq.common.handlers.ResultHandler; import bisq.common.setup.GracefulShutDownHandler; @@ -116,6 +117,10 @@ public void gracefulShutDown(ResultHandler resultHandler) { } public void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) { + if (DevEnv.isDevMode()) { + return; + } + List seedNodeAddresses = new ArrayList<>(injector.getInstance(SeedNodeRepository.class).getSeedNodeAddresses()); seedNodeAddresses.sort(Comparator.comparing(NodeAddress::getFullAddress)); From 4323b417b147a2c33130d842a506d40748d38770 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Mon, 31 Aug 2020 19:36:19 -0500 Subject: [PATCH 47/85] Adjust to codacy complaints codacy does not like raw Exceptions ;-( - Remove unused method --- .../asset/CryptoNoteAddressValidator.java | 2 +- .../main/java/bisq/asset/CryptoNoteUtils.java | 42 +++++++++++++++---- .../main/java/bisq/desktop/main/MainView.java | 10 +---- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java index 295c1e0af8a..cc88ed72218 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java @@ -46,7 +46,7 @@ public AddressValidationResult validate(String address) { } } return AddressValidationResult.invalidAddress(String.format("invalid address prefix %x", prefix)); - } catch (Exception e) { + } catch (CryptoNoteUtils.CryptoNoteException e) { return AddressValidationResult.invalidStructure(); } } diff --git a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java index 425fbe19b2e..c89f73aaad7 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java @@ -34,7 +34,7 @@ public static String convertToRawHex(String address) { // omit the type (1st byte) and checksum (last 4 byte) byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); return Utils.HEX.encode(slice); - } catch (Exception e) { + } catch (CryptoNoteException e) { e.printStackTrace(); } return null; @@ -155,7 +155,7 @@ private static void decodeChunk(String input, int inputLength, byte[] decoded, int decodedOffset, - int decodedLength) throws Exception { + int decodedLength) throws CryptoNoteException { BigInteger result = BigInteger.ZERO; @@ -164,17 +164,17 @@ private static void decodeChunk(String input, char character = input.charAt(--index); int digit = ALPHABET.indexOf(character); if (digit == -1) { - throw new Exception("invalid character " + character); + throw new CryptoNoteException("invalid character " + character); } result = result.add(order.multiply(BigInteger.valueOf(digit))); if (result.compareTo(UINT64_MAX) > 0) { - throw new Exception("64-bit unsigned integer overflow " + result.toString()); + throw new CryptoNoteException("64-bit unsigned integer overflow " + result.toString()); } } BigInteger maxCapacity = BigInteger.ONE.shiftLeft(8 * decodedLength); if (result.compareTo(maxCapacity) >= 0) { - throw new Exception("capacity overflow " + result.toString()); + throw new CryptoNoteException("capacity overflow " + result.toString()); } for (int index = decodedOffset + decodedLength; index != decodedOffset; result = result.shiftRight(8)) { @@ -182,7 +182,7 @@ private static void decodeChunk(String input, } } - public static byte[] decode(String input) throws Exception { + public static byte[] decode(String input) throws CryptoNoteException { if (input.length() == 0) { return new byte[0]; } @@ -218,12 +218,12 @@ private static long readVarInt(ByteBuffer buffer) { return result; } - static long decodeAddress(String address, boolean validateChecksum) throws Exception { + static long decodeAddress(String address, boolean validateChecksum) throws CryptoNoteException { byte[] decoded = decode(address); int checksumSize = 4; if (decoded.length < checksumSize) { - throw new Exception("invalid length"); + throw new CryptoNoteException("invalid length"); } ByteBuffer decodedAddress = ByteBuffer.wrap(decoded, 0, decoded.length - checksumSize); @@ -237,11 +237,35 @@ static long decodeAddress(String address, boolean validateChecksum) throws Excep int checksum = fastHash.getInt(); int expected = ByteBuffer.wrap(decoded, decoded.length - checksumSize, checksumSize).getInt(); if (checksum != expected) { - throw new Exception(String.format("invalid checksum %08X, expected %08X", checksum, expected)); + throw new CryptoNoteException(String.format("invalid checksum %08X, expected %08X", checksum, expected)); } return prefix; } } + + static class CryptoNoteException extends Exception { + public CryptoNoteException() { + } + + public CryptoNoteException(String message) { + super(message); + } + + public CryptoNoteException(String message, Throwable cause) { + super(message, cause); + } + + public CryptoNoteException(Throwable cause) { + super(cause); + } + + public CryptoNoteException(String message, + Throwable cause, + boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + } } diff --git a/desktop/src/main/java/bisq/desktop/main/MainView.java b/desktop/src/main/java/bisq/desktop/main/MainView.java index f977c95d0bc..03a58699084 100644 --- a/desktop/src/main/java/bisq/desktop/main/MainView.java +++ b/desktop/src/main/java/bisq/desktop/main/MainView.java @@ -43,12 +43,12 @@ import bisq.desktop.util.Transitions; import bisq.core.dao.monitoring.DaoStateMonitoringService; -import bisq.common.BisqException; import bisq.core.locale.GlobalSettings; import bisq.core.locale.LanguageUtil; import bisq.core.locale.Res; import bisq.core.provider.price.MarketPrice; +import bisq.common.BisqException; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.app.Version; @@ -77,7 +77,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.text.TextAlignment; @@ -444,13 +443,6 @@ private Separator getNavigationSeparator() { return separator; } - @NotNull - private Region getNavigationSpacer() { - final Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - return spacer; - } - private Tuple2 getBalanceBox(String text) { Label balanceDisplay = new Label(); balanceDisplay.getStyleClass().add("nav-balance-display"); From d36306a73ca88ee17306f0740191f104df5148a0 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 00:56:54 -0500 Subject: [PATCH 48/85] More refactoring.... --- core/src/main/java/bisq/core/trade/Trade.java | 17 +- .../trade/autoconf/AssetTxProofResult.java | 85 +++--- .../trade/autoconf/xmr/XmrTxProofParser.java | 45 +-- .../trade/autoconf/xmr/XmrTxProofRequest.java | 163 +++++++++-- .../xmr/XmrTxProofRequestService.java | 54 ++-- .../trade/autoconf/xmr/XmrTxProofResult.java | 207 ------------- .../trade/autoconf/xmr/XmrTxProofService.java | 276 ++++++++++-------- .../resources/i18n/displayStrings.properties | 16 +- .../autoconf/xmr/XmrTxProofParserTest.java | 62 ++-- .../overlays/windows/TradeDetailsWindow.java | 7 +- .../steps/buyer/BuyerStep4View.java | 5 +- .../steps/seller/SellerStep3View.java | 26 +- .../main/java/bisq/desktop/util/GUIUtil.java | 30 ++ proto/src/main/proto/pb.proto | 6 +- 14 files changed, 466 insertions(+), 533 deletions(-) delete mode 100644 core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index bbb092662a0..deba9fcf15e 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -437,11 +437,12 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt private String counterCurrencyExtraData; // Added at v1.3.8 + // Generic tx proof result. We persist name if AssetTxProofResult enum. Other fields in the enum are not persisted + // as they are not very relevant as historical data (e.g. number of confirmations) @Nullable + @Getter private AssetTxProofResult assetTxProofResult; - @Getter - // This observable property can be used for UI to show a notification to user of the XMR proof status transient final private ObjectProperty assetTxProofResultProperty = new SimpleObjectProperty<>(); @@ -556,7 +557,7 @@ public Message toProtoMessage() { Optional.ofNullable(refundResultState).ifPresent(e -> builder.setRefundResultState(RefundResultState.toProtoMessage(refundResultState))); Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes))); Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); - Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.toProtoMessage())); + Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name())); return builder.build(); } @@ -591,7 +592,7 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); - trade.setAssetTxProofResult(AssetTxProofResult.fromProto(proto.getAssetTxProofResult(), checkNotNull(trade.getOffer()).getCurrencyCode())); + trade.setAssetTxProofResult(ProtoUtil.enumFromProto(AssetTxProofResult.class, proto.getAssetTxProofResult())); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) @@ -875,7 +876,7 @@ public void setErrorMessage(String errorMessage) { errorMessageProperty.set(errorMessage); } - public void setAssetTxProofResult(AssetTxProofResult assetTxProofResult) { + public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResult) { this.assetTxProofResult = assetTxProofResult; assetTxProofResultProperty.setValue(assetTxProofResult); } @@ -1092,12 +1093,6 @@ public String getErrorMessage() { return errorMessageProperty.get(); } - @Nullable - public AssetTxProofResult getAssetTxProofResult() { - return assetTxProofResult != null ? assetTxProofResult : AssetTxProofResult.fromCurrencyCode(checkNotNull(offer).getCurrencyCode()); - } - - public byte[] getArbitratorBtcPubKey() { // In case we are already in a trade the arbitrator can have been revoked and we still can complete the trade // Only new trades cannot start without any arbitrator diff --git a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java index d8ea275e044..78a537fe500 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java @@ -17,62 +17,57 @@ package bisq.core.trade.autoconf; -import bisq.core.trade.autoconf.xmr.XmrTxProofResult; - -import lombok.EqualsAndHashCode; import lombok.Getter; -import javax.annotation.Nullable; +public enum AssetTxProofResult { + UNDEFINED, -/** - * Base class for AutoConfirm implementations - */ -@EqualsAndHashCode -@Getter -public abstract class AssetTxProofResult { + FEATURE_DISABLED, + TRADE_LIMIT_EXCEEDED, + INVALID_DATA, // Peer provided invalid data. Might be a scam attempt (e.g. txKey reused) + PAYOUT_TX_ALREADY_PUBLISHED, - @Nullable - public static AssetTxProofResult fromCurrencyCode(String currencyCode) { - //noinspection SwitchStatementWithTooFewBranches - switch (currencyCode) { - case "XMR": - return new XmrTxProofResult(); - default: - return null; - } - } + REQUEST_STARTED, + PENDING, - private final String stateName; - - protected AssetTxProofResult(String stateName) { - this.stateName = stateName; - } + // All services completed with a success state + COMPLETED, + // Any service had an error (network, API service) + ERROR, - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// + // Any service failed. Might be that the tx is invalid. + FAILED; - // We use fromProto as kind of factory method to get the specific AutoConfirmResult - @Nullable - public static AssetTxProofResult fromProto(protobuf.AutoConfirmResult proto, String currencyCode) { - //noinspection SwitchStatementWithTooFewBranches - switch (currencyCode) { - case "XMR": - return XmrTxProofResult.fromProto(proto); - default: - return null; - } - } + @Getter + private transient int numSuccessResults; + @Getter + private transient int requiredSuccessResults; + @Getter + private transient String details = ""; - public abstract protobuf.AutoConfirmResult toProtoMessage(); + public AssetTxProofResult numSuccessResults(int numSuccessResults) { + this.numSuccessResults = numSuccessResults; + return this; + } - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// + public AssetTxProofResult requiredSuccessResults(int requiredSuccessResults) { + this.requiredSuccessResults = requiredSuccessResults; + return this; + } - abstract public boolean isSuccessState(); + public AssetTxProofResult details(String details) { + this.details = details; + return this; + } - abstract public String getStatusAsDisplayString(); + @Override + public String toString() { + return "AssetTxProofResult{" + + "\n numSuccessResults=" + numSuccessResults + + ",\n requiredSuccessResults=" + requiredSuccessResults + + ",\n details='" + details + '\'' + + "\n} " + super.toString(); + } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java index 1221f602d7e..0d68f8cf119 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java @@ -17,6 +17,8 @@ package bisq.core.trade.autoconf.xmr; +import bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Result; + import bisq.asset.CryptoNoteUtils; import bisq.common.app.DevEnv; @@ -31,18 +33,20 @@ import lombok.extern.slf4j.Slf4j; +import static bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Detail; + @Slf4j class XmrTxProofParser { - static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { + static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { String txHash = xmrTxProofModel.getTxHash(); try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Empty json"); + return Result.ERROR.with(Detail.API_INVALID.error("Empty json")); } // there should always be "data" and "status" at the top level if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing data / status fields"); + return Result.ERROR.with(Detail.API_INVALID.error("Missing data / status fields")); } JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); @@ -50,42 +54,42 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // The API returns "fail" until the transaction has successfully reached the mempool or if request // contained invalid data. // We return TX_NOT_FOUND which will cause a retry later - return new XmrTxProofResult(XmrTxProofResult.State.TX_NOT_FOUND); + return Result.PENDING.with(Detail.TX_NOT_FOUND); } else if (!jsonStatus.matches("success")) { - return new XmrTxProofResult(XmrTxProofResult.State.API_FAILURE, "Unhandled status value"); + return Result.ERROR.with(Detail.API_INVALID.error("Unhandled status value")); } // validate that the address matches JsonElement jsonAddress = jsonData.get("address"); if (jsonAddress == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing address field"); + return Result.ERROR.with(Detail.API_INVALID.error("Missing address field")); } else { String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrTxProofModel.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return new XmrTxProofResult(XmrTxProofResult.State.ADDRESS_INVALID); + return Result.FAILED.with(Detail.ADDRESS_INVALID); } } // validate that the txHash matches JsonElement jsonTxHash = jsonData.get("tx_hash"); if (jsonTxHash == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_hash field"); + return Result.ERROR.with(Detail.API_INVALID.error("Missing tx_hash field")); } else { if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return new XmrTxProofResult(XmrTxProofResult.State.TX_HASH_INVALID); + return Result.FAILED.with(Detail.TX_HASH_INVALID); } } // validate that the txKey matches JsonElement jsonViewkey = jsonData.get("viewkey"); if (jsonViewkey == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing viewkey field"); + return Result.ERROR.with(Detail.API_INVALID.error("Missing viewkey field")); } else { if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrTxProofModel.getTxKey())) { log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrTxProofModel.getTxKey()); - return new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_INVALID); + return Result.FAILED.with(Detail.TX_KEY_INVALID); } } @@ -93,7 +97,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // (except that in dev mode we let this check pass anyway) JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); if (jsonTimestamp == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_timestamp field"); + return Result.ERROR.with(Detail.API_INVALID.error("Missing tx_timestamp field")); } else { long tradeDateSeconds = xmrTxProofModel.getTradeDate().getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); @@ -101,7 +105,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return new XmrTxProofResult(XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING); + return Result.FAILED.with(Detail.TRADE_DATE_NOT_MATCHING); } } @@ -109,7 +113,7 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { int confirmations; JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); if (jsonConfirmations == null) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, "Missing tx_confirmations field"); + return Result.ERROR.with(Detail.API_INVALID.error("Missing tx_confirmations field")); } else { confirmations = jsonConfirmations.getAsInt(); log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); @@ -135,26 +139,23 @@ static XmrTxProofResult parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // None of the outputs had a match entry if (!anyMatchFound) { - return new XmrTxProofResult(XmrTxProofResult.State.NO_MATCH_FOUND); + return Result.FAILED.with(Detail.NO_MATCH_FOUND); } // None of the outputs had a match entry if (!amountMatches) { - return new XmrTxProofResult(XmrTxProofResult.State.AMOUNT_NOT_MATCHING); + return Result.FAILED.with(Detail.AMOUNT_NOT_MATCHING); } int confirmsRequired = xmrTxProofModel.getConfirmsRequired(); if (confirmations < confirmsRequired) { - XmrTxProofResult xmrTxProofResult = new XmrTxProofResult(XmrTxProofResult.State.PENDING_CONFIRMATIONS); - xmrTxProofResult.setNumConfirmations(confirmations); - xmrTxProofResult.setRequiredConfirmations(confirmsRequired); - return xmrTxProofResult; + return Result.PENDING.with(Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); } else { - return new XmrTxProofResult(XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED); + return Result.SUCCESS; } } catch (JsonParseException | NullPointerException e) { - return new XmrTxProofResult(XmrTxProofResult.State.API_INVALID, e.toString()); + return Result.ERROR.with(Detail.API_INVALID.error(e.toString())); } } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java index 0cf8d809b2c..5fb20635f9e 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java @@ -32,20 +32,99 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; + /** * Requests for the XMR tx proof for a particular trade and one service. * Repeats requests if tx is not confirmed yet. */ @Slf4j class XmrTxProofRequest { - // these settings are not likely to change and therefore not put into Config + /////////////////////////////////////////////////////////////////////////////////////////// + // Enums + /////////////////////////////////////////////////////////////////////////////////////////// + + enum Result { + PENDING, // Tx not visible in network yet, unconfirmed or not enough confirmations + SUCCESS, // Proof succeeded + FAILED, // Proof failed + ERROR; // Error from service, does not mean that proof failed + + @Nullable + @Getter + private Detail detail; + + Result with(Detail detail) { + this.detail = detail; + return this; + } + + @Override + public String toString() { + return "Result{" + + "\n detail=" + detail + + "\n} " + super.toString(); + } + } + + enum Detail { + // Pending + TX_NOT_FOUND, // Tx not visible in network yet. Could be also other error + PENDING_CONFIRMATIONS, + + // Error states + CONNECTION_FAILURE, + API_FAILURE, + API_INVALID, + + // Failure states + TX_HASH_INVALID, + TX_KEY_INVALID, + ADDRESS_INVALID, + NO_MATCH_FOUND, + AMOUNT_NOT_MATCHING, + TRADE_DATE_NOT_MATCHING; + + @Getter + private int numConfirmations; + @Nullable + @Getter + private String errorMsg; + + public Detail error(String errorMsg) { + this.errorMsg = errorMsg; + return this; + } + + public Detail numConfirmations(int numConfirmations) { + this.numConfirmations = numConfirmations; + return this; + } + + @Override + public String toString() { + return "Detail{" + + "\n numConfirmations=" + numConfirmations + + ",\n errorMsg='" + errorMsg + '\'' + + "\n} " + super.toString(); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Class fields + /////////////////////////////////////////////////////////////////////////////////////////// + private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); @@ -53,11 +132,14 @@ class XmrTxProofRequest { "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; private final XmrTxProofModel xmrTxProofModel; - private final Consumer resultHandler; - private final FaultHandler faultHandler; + private final long firstRequest; private boolean terminated; - private final long firstRequest; + @Getter + private List results = new ArrayList<>(); + @Getter + @Nullable + private Result result; /////////////////////////////////////////////////////////////////////////////////////////// @@ -65,9 +147,7 @@ class XmrTxProofRequest { /////////////////////////////////////////////////////////////////////////////////////////// XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofModel xmrTxProofModel, - Consumer resultHandler, - FaultHandler faultHandler) { + XmrTxProofModel xmrTxProofModel) { this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); if (xmrTxProofModel.getServiceAddress().matches("^192.*|^localhost.*")) { @@ -75,8 +155,6 @@ class XmrTxProofRequest { this.httpClient.setIgnoreSocks5Proxy(true); } this.xmrTxProofModel = xmrTxProofModel; - this.resultHandler = resultHandler; - this.faultHandler = faultHandler; this.terminated = false; firstRequest = System.currentTimeMillis(); } @@ -90,14 +168,15 @@ void stop() { terminated = true; } - public void request() { + public void start(Consumer resultHandler, FaultHandler faultHandler) { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls // this scenario may happen if a re-request is scheduled from the callback below log.info("Request() aborted, this object has been terminated. Service: {}", httpClient.getBaseUrl()); return; } - ListenableFuture future = executorService.submit(() -> { + + ListenableFuture future = executorService.submit(() -> { Thread.currentThread().setName("XmrTransferProofRequest-" + xmrTxProofModel.getUID()); String param = "/api/outputs?txhash=" + xmrTxProofModel.getTxHash() + "&address=" + xmrTxProofModel.getRecipientAddress() + @@ -107,33 +186,67 @@ public void request() { String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); log.info("Response json\n{}", prettyJson); - XmrTxProofResult xmrTxProofResult = XmrTxProofParser.parse(xmrTxProofModel, json); - log.info("xmrTxProofResult {}", xmrTxProofResult); - return xmrTxProofResult; + Result result = XmrTxProofParser.parse(xmrTxProofModel, json); + log.info("xmrTxProofResult {}", result); + return result; }); Futures.addCallback(future, new FutureCallback<>() { - public void onSuccess(XmrTxProofResult result) { + public void onSuccess(Result result) { + XmrTxProofRequest.this.result = result; + if (terminated) { - log.info("API terminated from higher level: {}", httpClient.getBaseUrl()); - return; - } - if (System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD) { - log.warn("We have tried requesting from {} for too long, giving up.", httpClient.getBaseUrl()); + log.info("We received result {} but request to {} was terminated already.", result, httpClient.getBaseUrl()); return; } - if (result.isPendingState()) { - UserThread.runAfter(() -> request(), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); + results.add(result); + switch (result) { + case PENDING: + if (System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD) { + log.warn("We have tried requesting from {} for too long, giving up.", httpClient.getBaseUrl()); + return; + } else { + UserThread.runAfter(() -> start(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); + } + UserThread.execute(() -> resultHandler.accept(result)); + break; + case SUCCESS: + case FAILED: + case ERROR: + UserThread.execute(() -> resultHandler.accept(result)); + break; } - UserThread.execute(() -> resultHandler.accept(result)); } public void onFailure(@NotNull Throwable throwable) { String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; faultHandler.handleFault(errorMessage, throwable); - UserThread.execute(() -> resultHandler.accept( - new XmrTxProofResult(XmrTxProofResult.State.CONNECTION_FAIL, errorMessage))); + UserThread.execute(() -> + resultHandler.accept(Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage)))); } }); } + + String getUID() { + return xmrTxProofModel.getUID(); + } + + String getServiceAddress() { + return xmrTxProofModel.getServiceAddress(); + } + + int numSuccessResults() { + return (int) results.stream().filter(e -> e == XmrTxProofRequest.Result.SUCCESS).count(); + } + + @Override + public String toString() { + return "XmrTxProofRequest{" + + ",\n httpClient=" + httpClient + + ",\n xmrTxProofModel=" + xmrTxProofModel + + ",\n firstRequest=" + firstRequest + + ",\n terminated=" + terminated + + ",\n result=" + result + + "\n}"; + } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java index 91c562370c2..6851139c8fa 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java @@ -19,16 +19,15 @@ import bisq.network.Socks5ProxyProvider; -import bisq.common.handlers.FaultHandler; - import javax.inject.Inject; import java.util.HashMap; import java.util.Map; -import java.util.function.Consumer; import lombok.extern.slf4j.Slf4j; +import javax.annotation.Nullable; + /** * Handles the XMR tx proof requests for multiple trades and multiple services. */ @@ -38,49 +37,32 @@ class XmrTxProofRequestService { private final Socks5ProxyProvider socks5ProxyProvider; @Inject - private XmrTxProofRequestService(Socks5ProxyProvider provider) { + public XmrTxProofRequestService(Socks5ProxyProvider provider) { socks5ProxyProvider = provider; } - void requestProof(XmrTxProofModel xmrTxProofModel, - Consumer resultHandler, - FaultHandler faultHandler) { - String uid = xmrTxProofModel.getUID(); + @Nullable + XmrTxProofRequest getRequest(XmrTxProofModel model) { + XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); + String uid = request.getUID(); if (map.containsKey(uid)) { log.warn("We started a proof request for uid {} already", uid); - return; + return null; } - log.info("requesting tx proof with uid {}", uid); - XmrTxProofRequest requester = new XmrTxProofRequest( - socks5ProxyProvider, - xmrTxProofModel, - result -> { - if (result.getState() == XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED) { - cleanup(uid); - } - resultHandler.accept(result); - }, - (errorMsg, throwable) -> { - cleanup(uid); - faultHandler.handleFault(errorMsg, throwable); - }); - map.put(uid, requester); - - requester.request(); + map.put(uid, request); + return request; } - void terminateRequest(XmrTxProofModel xmrTxProofModel) { - String uid = xmrTxProofModel.getUID(); - XmrTxProofRequest requester = map.getOrDefault(uid, null); - if (requester != null) { - log.info("Terminating API request for request with uid {}", uid); - requester.stop(); - cleanup(uid); - } + // Get number of requests with at least 1 SUCCESS result + int numRequestsWithSuccessResult() { + return (int) map.values().stream() + .filter(request -> request.numSuccessResults() > 0) + .count(); } - private void cleanup(String key) { - map.remove(key); + void stopAllRequest() { + map.values().forEach(XmrTxProofRequest::stop); + map.clear(); } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java deleted file mode 100644 index f0776a2a1ab..00000000000 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofResult.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.autoconf.xmr; - -import bisq.core.locale.Res; -import bisq.core.trade.autoconf.AssetTxProofResult; - -import bisq.common.proto.ProtoUtil; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Nullable; - -@Slf4j -@EqualsAndHashCode(callSuper = true) -@ToString -public class XmrTxProofResult extends AssetTxProofResult { - public enum State { - UNDEFINED, - - // Feature disable cases - FEATURE_DISABLED, - TRADE_LIMIT_EXCEEDED, - - // Pending state - REQUEST_STARTED, - TX_NOT_FOUND, - PENDING_SERVICE_RESULTS, - PENDING_CONFIRMATIONS, - - SINGLE_SERVICE_SUCCEEDED, // Individual service has delivered proof ok - - // Success state - ALL_SERVICES_SUCCEEDED, // All services delivered PROOF_OK - - // Error state - CONNECTION_FAIL, - API_FAILURE, - API_INVALID, - TX_KEY_REUSED, - TX_HASH_INVALID, - TX_KEY_INVALID, - ADDRESS_INVALID, - NO_MATCH_FOUND, - AMOUNT_NOT_MATCHING, - TRADE_DATE_NOT_MATCHING - } - - @Getter - private transient final State state; - @Setter - private transient int numConfirmations; - @Setter - private transient int requiredConfirmations; - @Nullable - private transient String errorMsg; - @Setter - private transient int pendingServiceResults; - @Setter - private transient int requiredServiceResults; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructors - /////////////////////////////////////////////////////////////////////////////////////////// - - public XmrTxProofResult() { - this(State.UNDEFINED); - } - - XmrTxProofResult(State state) { - super(state.name()); - - this.state = state; - } - - XmrTxProofResult(State state, String errorMsg) { - this(state); - - this.errorMsg = errorMsg; - log.error(errorMsg); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // PROTO BUFFER - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public protobuf.AutoConfirmResult toProtoMessage() { - return protobuf.AutoConfirmResult.newBuilder().setStateName(state.name()).build(); - } - - public static XmrTxProofResult fromProto(protobuf.AutoConfirmResult proto) { - XmrTxProofResult.State state = ProtoUtil.enumFromProto(XmrTxProofResult.State.class, proto.getStateName()); - return state != null ? new XmrTxProofResult(state) : new XmrTxProofResult(State.UNDEFINED); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public String getStatusAsDisplayString() { - String key = "portfolio.pending.autoConf.state." + state; - switch (state) { - // Invalid protobuf data - case UNDEFINED: - return state.toString(); - - // Feature disable cases - case FEATURE_DISABLED: - case TRADE_LIMIT_EXCEEDED: - - // Pending state - case REQUEST_STARTED: - case TX_NOT_FOUND: // Tx still not confirmed and not in mempool - return Res.get(key); - case PENDING_SERVICE_RESULTS: - return Res.get(key, pendingServiceResults, requiredServiceResults); - case PENDING_CONFIRMATIONS: - return Res.get(key, numConfirmations, requiredConfirmations); - case SINGLE_SERVICE_SUCCEEDED: - - // Success state - case ALL_SERVICES_SUCCEEDED: - return Res.get(key); - - // Error state - case CONNECTION_FAIL: - case API_FAILURE: - case API_INVALID: - case TX_KEY_REUSED: - case TX_HASH_INVALID: - case TX_KEY_INVALID: - case ADDRESS_INVALID: - case NO_MATCH_FOUND: - case AMOUNT_NOT_MATCHING: - case TRADE_DATE_NOT_MATCHING: - return getErrorMsg(); - - default: - return state.toString(); - } - } - - @Override - public boolean isSuccessState() { - return (state == State.ALL_SERVICES_SUCCEEDED); - } - - boolean isErrorState() { - switch (state) { - case CONNECTION_FAIL: - case API_FAILURE: - case API_INVALID: - case TX_KEY_REUSED: - case TX_HASH_INVALID: - case TX_KEY_INVALID: - case ADDRESS_INVALID: - case NO_MATCH_FOUND: - case AMOUNT_NOT_MATCHING: - case TRADE_DATE_NOT_MATCHING: - return true; - - default: - return false; - } - } - - boolean isPendingState() { - switch (state) { - case REQUEST_STARTED: - case TX_NOT_FOUND: - case PENDING_SERVICE_RESULTS: - case PENDING_CONFIRMATIONS: - return true; - - default: - return false; - } - } - - private String getErrorMsg() { - return errorMsg != null ? errorMsg : state.name(); - } -} diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java index bbd451ef9b7..bd5099fedfd 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java @@ -20,11 +20,13 @@ import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; +import bisq.core.locale.Res; import bisq.core.monetary.Volume; import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.user.Preferences; @@ -48,6 +50,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import static bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Result; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -65,7 +68,9 @@ public class XmrTxProofService { private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; - private final Map requestInfoByTxIdMap = new HashMap<>(); + private final Map> listenerByTxId = new HashMap<>(); + @Getter + private int requiredSuccessResults; /////////////////////////////////////////////////////////////////////////////////////////// @@ -73,14 +78,14 @@ public class XmrTxProofService { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private XmrTxProofService(FilterManager filterManager, - Preferences preferences, - XmrTxProofRequestService xmrTxProofRequestService, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager, - P2PService p2PService, - WalletsSetup walletsSetup, - AccountAgeWitnessService accountAgeWitnessService) { + public XmrTxProofService(FilterManager filterManager, + Preferences preferences, + XmrTxProofRequestService xmrTxProofRequestService, + ClosedTradableManager closedTradableManager, + FailedTradesManager failedTradesManager, + P2PService p2PService, + WalletsSetup walletsSetup, + AccountAgeWitnessService accountAgeWitnessService) { this.filterManager = filterManager; this.preferences = preferences; this.xmrTxProofRequestService = xmrTxProofRequestService; @@ -105,7 +110,7 @@ public void maybeStartRequestTxProofProcess(Trade trade, List activeTrade return; } - if (!featureEnabled(trade)) { + if (!isFeatureEnabled(trade)) { return; } @@ -121,6 +126,10 @@ public void maybeStartRequestTxProofProcess(Trade trade, List activeTrade return; } + if (isPayoutPublished(trade)) { + return; + } + Coin tradeAmount = trade.getTradeAmount(); Volume volume = checkNotNull(trade.getOffer()).getVolumeByAmount(tradeAmount); // XMR satoshis have 12 decimal places vs. bitcoin's 8 @@ -133,23 +142,17 @@ public void maybeStartRequestTxProofProcess(Trade trade, List activeTrade recipientAddress = XmrTxProofModel.DEV_ADDRESS; amountXmr = XmrTxProofModel.DEV_AMOUNT; } - int confirmsRequired = preferences.getAutoConfirmSettings().requiredConfirmations; String txHash = trade.getCounterCurrencyTxId(); String txKey = trade.getCounterCurrencyExtraData(); - List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; + List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; + requiredSuccessResults = serviceAddresses.size(); - ChangeListener listener = (observable, oldValue, newValue) -> { - if (trade.isPayoutPublished()) { - log.warn("Trade payout already published, shutting down all open API requests for trade {}", - trade.getShortId()); - cleanup(trade); - } - }; + ChangeListener listener = (observable, oldValue, newValue) -> isPayoutPublished(trade); trade.stateProperty().addListener(listener); - requestInfoByTxIdMap.put(trade.getId(), new RequestInfo(serviceAddresses.size(), listener)); // need result from each service address + listenerByTxId.put(trade.getId(), listener); + - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.REQUEST_STARTED)); for (String serviceAddress : serviceAddresses) { XmrTxProofModel xmrTxProofModel = new XmrTxProofModel( txHash, @@ -157,92 +160,142 @@ public void maybeStartRequestTxProofProcess(Trade trade, List activeTrade recipientAddress, amountXmr, trade.getDate(), - confirmsRequired, + getRequiredConfirmations(), serviceAddress); - xmrTxProofRequestService.requestProof(xmrTxProofModel, - result -> { - if (!handleProofResult(result, trade)) { - xmrTxProofRequestService.terminateRequest(xmrTxProofModel); - } - }, - (errorMsg, throwable) -> { - log.warn(errorMsg); - } - ); + XmrTxProofRequest request = xmrTxProofRequestService.getRequest(xmrTxProofModel); + if (request != null) { + request.start(result -> handleResult(request, trade, result), + (errorMsg, throwable) -> log.warn(errorMsg)); + trade.setAssetTxProofResult(AssetTxProofResult.REQUEST_STARTED); + } } } - private boolean handleProofResult(XmrTxProofResult result, Trade trade) { - // here we count the Trade's API results from all - // different serviceAddress and figure out when all have finished - if (!requestInfoByTxIdMap.containsKey(trade.getId())) { - // We have cleaned up our map in the meantime - return false; - } - RequestInfo requestInfo = requestInfoByTxIdMap.get(trade.getId()); + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// - if (requestInfo.isInvalid()) { - log.info("Ignoring stale API result [{}], tradeId {} due to previous error", - result.getState(), trade.getShortId()); - return false; // terminate any pending responses + private void handleResult(XmrTxProofRequest request, + Trade trade, + Result result) { + if (isPayoutPublished(trade)) { + return; } - if (trade.isPayoutPublished()) { - log.warn("Trade payout already published, shutting down all open API requests for trade {}", - trade.getShortId()); - cleanup(trade); + + // If one service fails we consider all failed as we require that all of our defined services result + // successfully. For now we keep it that way as it reduces complexity but it might be useful to + // support a min number of services which need to succeed from a larger set of services. + // E.g. 2 out of 3 services need to succeed. + + int numRequestsWithSuccessResult = xmrTxProofRequestService.numRequestsWithSuccessResult(); + switch (result) { + case PENDING: + applyPending(trade, result); + + // Repeating the requests is handled in XmrTransferProofRequester + return; + case SUCCESS: + if (numRequestsWithSuccessResult < requiredSuccessResults) { + log.info("Tx proof request to service {} for trade {} succeeded. We have {} remaining request(s) to other service(s).", + request.getServiceAddress(), trade.getShortId(), numRequestsWithSuccessResult); + + applyPending(trade, result); + return; // not all APIs have confirmed yet + } + + // All our services have returned a SUCCESS result so we have completed. + trade.setAssetTxProofResult(AssetTxProofResult.COMPLETED); + log.info("All {} tx proof requests for trade {} have been successful.", + requiredSuccessResults, trade.getShortId()); + removeListener(trade); + + if (!isPayoutPublished(trade)) { + log.info("We auto-confirm XMR receipt to complete trade {}.", trade.getShortId()); + // Trade state update is handled in the trade protocol method triggered by the onFiatPaymentReceived call + // This triggers the completion of the trade with signing and publishing the payout tx + ((SellerTrade) trade).onFiatPaymentReceived(() -> { + }, + errorMessage -> { + }); + accountAgeWitnessService.maybeSignWitness(trade); + } else { + log.info("Trade {} have been completed in the meantime.", trade.getShortId()); + } + terminate(trade); + return; + case FAILED: + log.warn("Tx proof request to service {} for trade {} failed. " + + "This might not mean that the XMR transfer was invalid but you have to check yourself " + + "if the XMR transfer was correct. Result={}", + request.getServiceAddress(), trade.getShortId(), result); + trade.setAssetTxProofResult(AssetTxProofResult.FAILED); + terminate(trade); + return; + case ERROR: + log.warn("Tx proof request to service {} for trade {} resulted in an error. " + + "This might not mean that the XMR transfer was invalid but can be a network or " + + "service problem. Result={}", + request.getServiceAddress(), trade.getShortId(), result); + + trade.setAssetTxProofResult(AssetTxProofResult.ERROR); + terminate(trade); + return; } + } - if (result.isErrorState()) { - log.warn("Tx Proof Failure {}, shutting down all open API requests for this trade {}", - result.getState(), trade.getShortId()); - trade.setAssetTxProofResult(result); // this updates the GUI with the status.. - requestInfo.invalidate(); - return false; + private void applyPending(Trade trade, Result result) { + int numRequestsWithSuccessResult = xmrTxProofRequestService.numRequestsWithSuccessResult(); + XmrTxProofRequest.Detail detail = result.getDetail(); + int numConfirmations = detail != null ? detail.getNumConfirmations() : 0; + log.info("Tx proof service received a {} result for trade {}. numConfirmations={}", + result, trade.getShortId(), numConfirmations); + + String details = ""; + if (XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS == detail) { + details = Res.get("portfolio.pending.autoConf.state.confirmations", numConfirmations, getRequiredConfirmations()); + + } else if (XmrTxProofRequest.Detail.TX_NOT_FOUND == detail) { + details = Res.get("portfolio.pending.autoConf.state.txNotFound"); } + trade.setAssetTxProofResult(AssetTxProofResult.PENDING + .numSuccessResults(numRequestsWithSuccessResult) + .requiredSuccessResults(requiredSuccessResults) + .details(details)); + } - if (result.isPendingState()) { - log.info("Auto confirm received a {} message for tradeId {}, retry will happen automatically", - result.getState(), trade.getShortId()); - trade.setAssetTxProofResult(result); // this updates the GUI with the status.. - // Repeating the requests is handled in XmrTransferProofRequester - return true; + private void terminate(Trade trade) { + xmrTxProofRequestService.stopAllRequest(); + removeListener(trade); + } + + private void removeListener(Trade trade) { + ChangeListener listener = listenerByTxId.get(trade.getId()); + if (listener != null) { + trade.stateProperty().removeListener(listener); + listenerByTxId.remove(trade.getId()); } + } + private int getRequiredConfirmations() { + return preferences.getAutoConfirmSettings().requiredConfirmations; + } - if (result.getState() == XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED) { - int resultsCountdown = requestInfo.decrementAndGet(); - log.info("Received a {} result, remaining proofs needed: {}, tradeId {}", - result.getState(), resultsCountdown, trade.getShortId()); - if (requestInfo.hasPendingResults()) { - XmrTxProofResult assetTxProofResult = new XmrTxProofResult(XmrTxProofResult.State.PENDING_SERVICE_RESULTS); - assetTxProofResult.setPendingServiceResults(requestInfo.getPendingResults()); - assetTxProofResult.setRequiredServiceResults(requestInfo.getNumServices()); - trade.setAssetTxProofResult(assetTxProofResult); - return true; // not all APIs have confirmed yet - } - // All our services have returned a PROOF_OK result so we have succeeded. - cleanup(trade); - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.ALL_SERVICES_SUCCEEDED)); - log.info("Auto confirm was successful, transitioning trade {} to next step...", trade.getShortId()); - if (!trade.isPayoutPublished()) { - // Trade state update is handled in the trade protocol method triggered by the onFiatPaymentReceived call - // This triggers the completion of the trade with signing and publishing the payout tx - ((SellerTrade) trade).onFiatPaymentReceived(() -> { - }, - errorMessage -> { - }); - } - accountAgeWitnessService.maybeSignWitness(trade); + /////////////////////////////////////////////////////////////////////////////////////////// + // Validation + /////////////////////////////////////////////////////////////////////////////////////////// + private boolean isPayoutPublished(Trade trade) { + if (trade.isPayoutPublished()) { + log.warn("Trade payout already published, shutting down all open API requests for trade {}", + trade.getShortId()); + trade.setAssetTxProofResult(AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); + terminate(trade); return true; - } else { - //TODO check if that can happen - log.error("Unexpected state {}", result.getState()); - return false; } + return false; } private boolean dataValid(Trade trade) { @@ -283,10 +336,10 @@ private boolean networkAndWalletReady() { walletsSetup.hasSufficientPeersForBroadcast(); } - private boolean featureEnabled(Trade trade) { + private boolean isFeatureEnabled(Trade trade) { boolean isEnabled = preferences.getAutoConfirmSettings().enabled && !isAutoConfDisabledByFilter(); if (!isEnabled) { - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.FEATURE_DISABLED)); + trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED); } return isEnabled; } @@ -303,17 +356,12 @@ private boolean isTradeAmountAboveLimit(Trade trade) { log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TRADE_LIMIT_EXCEEDED)); + trade.setAssetTxProofResult(AssetTxProofResult.TRADE_LIMIT_EXCEEDED); return true; } return false; } - private void cleanup(Trade trade) { - trade.stateProperty().removeListener(requestInfoByTxIdMap.get(trade.getId()).getListener()); - requestInfoByTxIdMap.remove(trade.getId()); - } - private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { if (DevEnv.isDevMode()) { return false; @@ -338,41 +386,11 @@ private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { boolean alreadyUsed = extra.equals(txKey); if (alreadyUsed) { - String message = "Peer used the XMR tx key already at another trade with trade ID " + - t.getId() + ". This might be a scam attempt."; - trade.setAssetTxProofResult(new XmrTxProofResult(XmrTxProofResult.State.TX_KEY_REUSED, message)); + log.warn("Peer used the XMR tx key already at another trade with trade ID {}. " + + "This might be a scam attempt.", t.getId()); + trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused"))); } return alreadyUsed; }); } - - @Getter - private static class RequestInfo { - private final int numServices; - private int pendingResults; - private ChangeListener listener; - - RequestInfo(int numServices, ChangeListener listener) { - this.numServices = numServices; - this.pendingResults = numServices; - this.listener = listener; - } - - int decrementAndGet() { - pendingResults--; - return pendingResults; - } - - void invalidate() { - pendingResults = -1; - } - - boolean isInvalid() { - return pendingResults < 0; - } - - boolean hasPendingResults() { - return pendingResults > 0; - } - } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 8a2af031599..aee829d2e5b 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -573,14 +573,20 @@ portfolio.pending.step5.completed=Completed portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status portfolio.pending.autoConf=Auto-confirmed + +portfolio.pending.autoConf.state.xmr.txKeyReused=The peer re-used a transaction key. This might be a scam attempt. Open a dispute. +portfolio.pending.autoConf.state.confirmations=Confirmations: {0}; Required: {1} +portfolio.pending.autoConf.state.txNotFound=Transaction not confirmed yet + portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabled portfolio.pending.autoConf.state.TRADE_LIMIT_EXCEEDED=Trade amount exceeds auto-confirm amount limit +portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data: {0} +portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published. portfolio.pending.autoConf.state.REQUEST_STARTED=Proof request started -portfolio.pending.autoConf.state.TX_NOT_FOUND=Transaction not confirmed yet -portfolio.pending.autoConf.state.PENDING_SERVICE_RESULTS=Pending service results: {0} of {1} -portfolio.pending.autoConf.state.PENDING_CONFIRMATIONS=Confirmations: {0} of {1} required -portfolio.pending.autoConf.state.SINGLE_SERVICE_SUCCEEDED=A service succeeded. Pending other services. -portfolio.pending.autoConf.state.ALL_SERVICES_SUCCEEDED=All proof services succeeded +portfolio.pending.autoConf.state.PENDING={0} service(s) out of {1} have succeeded. {2} +portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded +portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. +portfolio.pending.autoConf.state.FAILED=A service returned with a failure. portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for at least one blockchain confirmation before starting the payment. portfolio.pending.step1.warn=The deposit transaction is still not confirmed. This sometimes happens in rare cases when the funding fee of one trader from an external wallet was too low. diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java index 10c3c84ea2f..bdbbda4a9a7 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java @@ -45,13 +45,13 @@ public void testKey() { public void testJsonRoot() { // checking what happens when bad input is provided assertTrue(XmrTxProofParser.parse(xmrTxProofModel, - "invalid json data").getState() == XmrTxProofResult.State.API_INVALID); + "invalid json data").getDetail() == XmrTxProofRequest.Detail.API_INVALID); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, - "").getState() == XmrTxProofResult.State.API_INVALID); + "").getDetail() == XmrTxProofRequest.Detail.API_INVALID); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, - "[]").getState() == XmrTxProofResult.State.API_INVALID); + "[]").getDetail() == XmrTxProofRequest.Detail.API_INVALID); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, - "{}").getState() == XmrTxProofResult.State.API_INVALID); + "{}").getDetail() == XmrTxProofRequest.Detail.API_INVALID); } @Test @@ -59,48 +59,48 @@ public void testJsonTopLevel() { // testing the top level fields: data and status assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'title':''},'status':'fail'}" ) - .getState() == XmrTxProofResult.State.TX_NOT_FOUND); + .getDetail() == XmrTxProofRequest.Detail.TX_NOT_FOUND); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'title':''},'missingstatus':'success'}" ) - .getState() == XmrTxProofResult.State.API_INVALID); + .getDetail() == XmrTxProofRequest.Detail.API_INVALID); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'missingdata':{'title':''},'status':'success'}" ) - .getState() == XmrTxProofResult.State.API_INVALID); + .getDetail() == XmrTxProofRequest.Detail.API_INVALID); } @Test public void testJsonAddress() { assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'missingaddress':'irrelevant'},'status':'success'}" ) - .getState() == XmrTxProofResult.State.API_INVALID); + .getDetail() == XmrTxProofRequest.Detail.API_INVALID); assertTrue(XmrTxProofParser.parse(xmrTxProofModel, "{'data':{'address':'e957dac7'},'status':'success'}" ) - .getState() == XmrTxProofResult.State.ADDRESS_INVALID); + .getDetail() == XmrTxProofRequest.Detail.ADDRESS_INVALID); } @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_hash).getState() - == XmrTxProofResult.State.API_INVALID); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_hash).getDetail() + == XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_hash).getState() - == XmrTxProofResult.State.TX_HASH_INVALID); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_hash).getDetail() + == XmrTxProofRequest.Detail.TX_HASH_INVALID); } @Test public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_key).getState() - == XmrTxProofResult.State.API_INVALID); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_key).getDetail() + == XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_key).getState() - == XmrTxProofResult.State.TX_KEY_INVALID); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_key).getDetail() + == XmrTxProofRequest.Detail.TX_KEY_INVALID); } @Test @@ -108,15 +108,15 @@ public void testJsonTxTimestamp() { String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_timestamp).getState() - == XmrTxProofResult.State.API_INVALID); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail() + == XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_timestamp).getState() - == XmrTxProofResult.State.TRADE_DATE_NOT_MATCHING); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail() + == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); } @Test @@ -133,28 +133,28 @@ public void testJsonTxConfirmation() { "'viewkey':'" + txKey + "', " + "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() - == XmrTxProofResult.State.SINGLE_SERVICE_SUCCEEDED); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json) + == XmrTxProofRequest.Result.SUCCESS); json = json.replaceFirst("777", "0"); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() - == XmrTxProofResult.State.PENDING_CONFIRMATIONS); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getDetail() + == XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS); json = json.replaceFirst("100000000000", "100000000001"); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() - == XmrTxProofResult.State.AMOUNT_NOT_MATCHING); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getDetail() + == XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getState() - == XmrTxProofResult.State.NO_MATCH_FOUND); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getDetail() + == XmrTxProofRequest.Detail.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, failedJson).getState() - == XmrTxProofResult.State.API_INVALID); + assertTrue(XmrTxProofParser.parse(xmrTxProofModel, failedJson).getDetail() + == XmrTxProofRequest.Detail.API_INVALID); } } diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 7a13fcca02b..0104b2a4611 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -22,6 +22,7 @@ import bisq.desktop.main.MainView; import bisq.desktop.main.overlays.Overlay; import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.core.account.witness.AccountAgeWitnessService; @@ -33,7 +34,7 @@ import bisq.core.trade.Contract; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.autoconf.xmr.XmrTxProofResult; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; @@ -236,12 +237,12 @@ private void addContent() { if (checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR") && trade.getAssetTxProofResult() != null && - ((XmrTxProofResult) trade.getAssetTxProofResult()).getState() != XmrTxProofResult.State.UNDEFINED) { + trade.getAssetTxProofResult() != AssetTxProofResult.UNDEFINED) { // As the window is already overloaded we replace the tradingPeersPubKeyHash field with the auto-conf state // if XMR is the currency addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("portfolio.pending.step3_seller.autoConf.status.label"), - trade.getAssetTxProofResult().getStatusAsDisplayString()); + GUIUtil.getProofResultAsString(trade.getAssetTxProofResult())); } else { addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradingPeersPubKeyHash"), diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index e6137a1efa3..785eb2bd9fb 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -37,6 +37,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Res; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinUtil; @@ -117,9 +118,7 @@ protected void addContent() { GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); gridPane.getChildren().add(hBox2); GridPane.setRowSpan(hBox2, 5); - if (trade.getAssetTxProofResult() != null && !trade.getAssetTxProofResult().isSuccessState()) { - autoConfBadge.setVisible(false); - } + autoConfBadge.setVisible(AssetTxProofResult.COMPLETED == trade.getAssetTxProofResult()); addCompactTopLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.TWICE_FIRST_ROW_DISTANCE); addCompactTopLabelTextField(gridPane, ++gridRow, getFiatTradeAmountLabel(), model.getFiatVolume()); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index d6c7d944d1d..209fc83c3e8 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -24,6 +24,7 @@ import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import bisq.desktop.util.DisplayUtils; +import bisq.desktop.util.GUIUtil; import bisq.desktop.util.Layout; import bisq.core.locale.CurrencyUtil; @@ -76,8 +77,9 @@ public class SellerStep3View extends TradeStepView { private BusyAnimation busyAnimation; private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; - private TextFieldWithCopyIcon autoConfirmStatusField; - private final ChangeListener autoConfirmResultListener; + private TextFieldWithCopyIcon assetTxProofResultField; + private final ChangeListener proofResultListener; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, Initialisation @@ -86,9 +88,8 @@ public class SellerStep3View extends TradeStepView { public SellerStep3View(PendingTradesViewModel model) { super(model); - // we listen for updates on the trade autoConfirmResult field - autoConfirmResultListener = (observable, oldValue, newValue) -> { - autoConfirmStatusField.setText(newValue.getStatusAsDisplayString()); + proofResultListener = (observable, oldValue, newValue) -> { + assetTxProofResultField.setText(GUIUtil.getProofResultAsString(newValue)); }; } @@ -151,9 +152,9 @@ public void activate() { }); // we listen for updates on the trade autoConfirmResult field - if (trade.getAssetTxProofResult() != null && autoConfirmStatusField != null) { - trade.getAssetTxProofResultProperty().addListener(autoConfirmResultListener); - autoConfirmStatusField.setText(trade.getAssetTxProofResult().getStatusAsDisplayString()); + if (assetTxProofResultField != null) { + trade.getAssetTxProofResultProperty().addListener(proofResultListener); + assetTxProofResultField.setText(GUIUtil.getProofResultAsString(trade.getAssetTxProofResult())); } } @@ -168,10 +169,13 @@ public void deactivate() { busyAnimation.stop(); - if (timeoutTimer != null) + if (timeoutTimer != null) { timeoutTimer.stop(); + } - trade.getAssetTxProofResultProperty().removeListener(autoConfirmResultListener); + if (assetTxProofResultField != null) { + trade.getAssetTxProofResultProperty().removeListener(proofResultListener); + } } /////////////////////////////////////////////////////////////////////////////////////////// @@ -225,7 +229,7 @@ protected void addContent() { } if (isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR")) { - autoConfirmStatusField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, + assetTxProofResultField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1, Res.get("portfolio.pending.step3_seller.autoConf.status.label"), "", Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE).second; } diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 04ae97f7b39..f8d17492a00 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -46,6 +46,7 @@ import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.user.User; @@ -143,6 +144,8 @@ import org.jetbrains.annotations.NotNull; +import javax.annotation.Nullable; + import static bisq.desktop.util.FormBuilder.addTopLabelComboBoxComboBox; import static com.google.common.base.Preconditions.checkArgument; @@ -1157,4 +1160,31 @@ public static RegexValidator addressRegexValidator() { onionV2RegexPattern, onionV3RegexPattern, ipv4RegexPattern, ipv6RegexPattern, fqdnRegexPattern)); return regexValidator; } + + public static String getProofResultAsString(@Nullable AssetTxProofResult result) { + if (result == null) { + return ""; + } + String key = "portfolio.pending.autoConf.state." + result.name(); + switch (result) { + case UNDEFINED: + return ""; + case FEATURE_DISABLED: + case TRADE_LIMIT_EXCEEDED: + return Res.get(key); + case INVALID_DATA: + return Res.get(key, result.getDetails()); + case PAYOUT_TX_ALREADY_PUBLISHED: + case REQUEST_STARTED: + return Res.get(key); + case PENDING: + return Res.get(key, result.getNumSuccessResults(), result.getRequiredSuccessResults(), result.getDetails()); + case COMPLETED: + case ERROR: + case FAILED: + return Res.get(key); + default: + return result.name(); + } + } } diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 9774e90b10e..a9b768fc1f0 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1393,7 +1393,7 @@ message Trade { RefundResultState refund_result_state = 35; int64 last_refresh_request_date = 36; string counter_currency_extra_data = 37; - AutoConfirmResult asset_tx_proof_result = 38; + string asset_tx_proof_result = 38; // name of AssetTxProofResult enum } message BuyerAsMakerTrade { @@ -1587,10 +1587,6 @@ message UserPayload { RefundAgent registered_refund_agent = 15; } -message AutoConfirmResult { - string stateName = 1; // name of state enum -} - /////////////////////////////////////////////////////////////////////////////////////////// // DAO /////////////////////////////////////////////////////////////////////////////////////////// From 123183e5e127214959c5e46a1ee56cc21f6963f5 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 01:24:14 -0500 Subject: [PATCH 49/85] Remove CryptoNoteException again as it caused failed tests Damn codacy forces one to not use Exception ;-( --- .../asset/CryptoNoteAddressValidator.java | 2 +- .../main/java/bisq/asset/CryptoNoteUtils.java | 42 ++++--------------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java index cc88ed72218..295c1e0af8a 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteAddressValidator.java @@ -46,7 +46,7 @@ public AddressValidationResult validate(String address) { } } return AddressValidationResult.invalidAddress(String.format("invalid address prefix %x", prefix)); - } catch (CryptoNoteUtils.CryptoNoteException e) { + } catch (Exception e) { return AddressValidationResult.invalidStructure(); } } diff --git a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java index c89f73aaad7..425fbe19b2e 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java @@ -34,7 +34,7 @@ public static String convertToRawHex(String address) { // omit the type (1st byte) and checksum (last 4 byte) byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); return Utils.HEX.encode(slice); - } catch (CryptoNoteException e) { + } catch (Exception e) { e.printStackTrace(); } return null; @@ -155,7 +155,7 @@ private static void decodeChunk(String input, int inputLength, byte[] decoded, int decodedOffset, - int decodedLength) throws CryptoNoteException { + int decodedLength) throws Exception { BigInteger result = BigInteger.ZERO; @@ -164,17 +164,17 @@ private static void decodeChunk(String input, char character = input.charAt(--index); int digit = ALPHABET.indexOf(character); if (digit == -1) { - throw new CryptoNoteException("invalid character " + character); + throw new Exception("invalid character " + character); } result = result.add(order.multiply(BigInteger.valueOf(digit))); if (result.compareTo(UINT64_MAX) > 0) { - throw new CryptoNoteException("64-bit unsigned integer overflow " + result.toString()); + throw new Exception("64-bit unsigned integer overflow " + result.toString()); } } BigInteger maxCapacity = BigInteger.ONE.shiftLeft(8 * decodedLength); if (result.compareTo(maxCapacity) >= 0) { - throw new CryptoNoteException("capacity overflow " + result.toString()); + throw new Exception("capacity overflow " + result.toString()); } for (int index = decodedOffset + decodedLength; index != decodedOffset; result = result.shiftRight(8)) { @@ -182,7 +182,7 @@ private static void decodeChunk(String input, } } - public static byte[] decode(String input) throws CryptoNoteException { + public static byte[] decode(String input) throws Exception { if (input.length() == 0) { return new byte[0]; } @@ -218,12 +218,12 @@ private static long readVarInt(ByteBuffer buffer) { return result; } - static long decodeAddress(String address, boolean validateChecksum) throws CryptoNoteException { + static long decodeAddress(String address, boolean validateChecksum) throws Exception { byte[] decoded = decode(address); int checksumSize = 4; if (decoded.length < checksumSize) { - throw new CryptoNoteException("invalid length"); + throw new Exception("invalid length"); } ByteBuffer decodedAddress = ByteBuffer.wrap(decoded, 0, decoded.length - checksumSize); @@ -237,35 +237,11 @@ static long decodeAddress(String address, boolean validateChecksum) throws Crypt int checksum = fastHash.getInt(); int expected = ByteBuffer.wrap(decoded, decoded.length - checksumSize, checksumSize).getInt(); if (checksum != expected) { - throw new CryptoNoteException(String.format("invalid checksum %08X, expected %08X", checksum, expected)); + throw new Exception(String.format("invalid checksum %08X, expected %08X", checksum, expected)); } return prefix; } } - - static class CryptoNoteException extends Exception { - public CryptoNoteException() { - } - - public CryptoNoteException(String message) { - super(message); - } - - public CryptoNoteException(String message, Throwable cause) { - super(message, cause); - } - - public CryptoNoteException(Throwable cause) { - super(cause); - } - - public CryptoNoteException(String message, - Throwable cause, - boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - } } From 6cc074a15cdc9f12633c5771535b6517023be176 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 10:44:50 -0500 Subject: [PATCH 50/85] Add default clause (codacy complaint) --- .../java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java | 2 ++ .../java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java index 5fb20635f9e..2a0733c7032 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java @@ -215,6 +215,8 @@ public void onSuccess(Result result) { case ERROR: UserThread.execute(() -> resultHandler.accept(result)); break; + default: + log.warn("Unexpected result {}", result); } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java index bd5099fedfd..05547eb3218 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java @@ -242,6 +242,8 @@ private void handleResult(XmrTxProofRequest request, trade.setAssetTxProofResult(AssetTxProofResult.ERROR); terminate(trade); return; + default: + log.warn("Unexpected result {}", result); } } From 3ec216dcd68d5aecdfdc056aa8761630f9e05313 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 10:47:28 -0500 Subject: [PATCH 51/85] Replace Exception with CryptoNoteException (codacy complaint) --- .../main/java/bisq/asset/CryptoNoteUtils.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java index 425fbe19b2e..7540092465d 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java @@ -34,12 +34,18 @@ public static String convertToRawHex(String address) { // omit the type (1st byte) and checksum (last 4 byte) byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); return Utils.HEX.encode(slice); - } catch (Exception e) { + } catch (CryptoNoteException e) { e.printStackTrace(); } return null; } + public static class CryptoNoteException extends Exception { + CryptoNoteException(String msg) { + super(msg); + } + } + static class Keccak { private static final int BLOCK_SIZE = 136; private static final int LONGS_PER_BLOCK = BLOCK_SIZE / 8; @@ -155,7 +161,7 @@ private static void decodeChunk(String input, int inputLength, byte[] decoded, int decodedOffset, - int decodedLength) throws Exception { + int decodedLength) throws CryptoNoteException { BigInteger result = BigInteger.ZERO; @@ -164,17 +170,17 @@ private static void decodeChunk(String input, char character = input.charAt(--index); int digit = ALPHABET.indexOf(character); if (digit == -1) { - throw new Exception("invalid character " + character); + throw new CryptoNoteException("invalid character " + character); } result = result.add(order.multiply(BigInteger.valueOf(digit))); if (result.compareTo(UINT64_MAX) > 0) { - throw new Exception("64-bit unsigned integer overflow " + result.toString()); + throw new CryptoNoteException("64-bit unsigned integer overflow " + result.toString()); } } BigInteger maxCapacity = BigInteger.ONE.shiftLeft(8 * decodedLength); if (result.compareTo(maxCapacity) >= 0) { - throw new Exception("capacity overflow " + result.toString()); + throw new CryptoNoteException("capacity overflow " + result.toString()); } for (int index = decodedOffset + decodedLength; index != decodedOffset; result = result.shiftRight(8)) { @@ -182,7 +188,7 @@ private static void decodeChunk(String input, } } - public static byte[] decode(String input) throws Exception { + public static byte[] decode(String input) throws CryptoNoteException { if (input.length() == 0) { return new byte[0]; } @@ -218,12 +224,12 @@ private static long readVarInt(ByteBuffer buffer) { return result; } - static long decodeAddress(String address, boolean validateChecksum) throws Exception { + static long decodeAddress(String address, boolean validateChecksum) throws CryptoNoteException { byte[] decoded = decode(address); int checksumSize = 4; if (decoded.length < checksumSize) { - throw new Exception("invalid length"); + throw new CryptoNoteException("invalid length"); } ByteBuffer decodedAddress = ByteBuffer.wrap(decoded, 0, decoded.length - checksumSize); @@ -237,7 +243,7 @@ static long decodeAddress(String address, boolean validateChecksum) throws Excep int checksum = fastHash.getInt(); int expected = ByteBuffer.wrap(decoded, decoded.length - checksumSize, checksumSize).getInt(); if (checksum != expected) { - throw new Exception(String.format("invalid checksum %08X, expected %08X", checksum, expected)); + throw new CryptoNoteException(String.format("invalid checksum %08X, expected %08X", checksum, expected)); } return prefix; From 77841e230fc8fec13c08beaf9004c8c23d041f46 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 10:58:00 -0500 Subject: [PATCH 52/85] Change log level In cased new fields are added to PB this case is expected as well in development if protbuf definitions have changed over time and persisted data do not match the new definition. We don't want this log to be that verbose. --- common/src/main/java/bisq/common/proto/ProtoUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/bisq/common/proto/ProtoUtil.java b/common/src/main/java/bisq/common/proto/ProtoUtil.java index da3cf029776..3d15db91f30 100644 --- a/common/src/main/java/bisq/common/proto/ProtoUtil.java +++ b/common/src/main/java/bisq/common/proto/ProtoUtil.java @@ -72,9 +72,9 @@ public static > E enumFromProto(Class enumType, String name E result = Enums.getIfPresent(enumType, name).orNull(); if (result == null) { - log.error("Invalid value for enum " + enumType.getSimpleName() + ": " + name); + log.debug("Invalid value for enum " + enumType.getSimpleName() + ": " + name); result = Enums.getIfPresent(enumType, "UNDEFINED").orNull(); - log.error("We try to lookup for an enum entry with name 'UNDEFINED' and use that if available, " + + log.debug("We try to lookup for an enum entry with name 'UNDEFINED' and use that if available, " + "otherwise the enum is null. enum={}", result); return result; } From 3b4e18363357ad67f22833e6ec13331b32aa49b1 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 11:18:09 -0500 Subject: [PATCH 53/85] Use tradeId for prefix for id of request. Remove testKey test (tested uid of model) Refactor: - Rename uid to id (we do not have a strict guarantee for uniqueness) - Move id from model to request (its the id of the request) --- .../core/trade/autoconf/xmr/XmrTxProofModel.java | 7 +++---- .../core/trade/autoconf/xmr/XmrTxProofRequest.java | 14 +++++++++----- .../autoconf/xmr/XmrTxProofRequestService.java | 2 +- .../core/trade/autoconf/xmr/XmrTxProofService.java | 2 +- .../trade/autoconf/xmr/XmrTxProofParserTest.java | 9 +-------- 5 files changed, 15 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java index 97162b26714..bedddd4da77 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java @@ -31,6 +31,7 @@ public class XmrTxProofModel { public static final String DEV_TX_HASH = "5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802"; public static final long DEV_AMOUNT = 8902597360000L; + private final String tradeId; private final String txHash; private final String txKey; private final String recipientAddress; @@ -40,6 +41,7 @@ public class XmrTxProofModel { private final String serviceAddress; XmrTxProofModel( + String tradeId, String txHash, String txKey, String recipientAddress, @@ -47,6 +49,7 @@ public class XmrTxProofModel { Date tradeDate, int confirmsRequired, String serviceAddress) { + this.tradeId = tradeId; this.txHash = txHash; this.txKey = txKey; this.recipientAddress = recipientAddress; @@ -55,8 +58,4 @@ public class XmrTxProofModel { this.confirmsRequired = confirmsRequired; this.serviceAddress = serviceAddress; } - - String getUID() { - return txHash + "|" + serviceAddress; - } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java index 2a0733c7032..b5c00121f4e 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java @@ -50,6 +50,7 @@ */ @Slf4j class XmrTxProofRequest { + /////////////////////////////////////////////////////////////////////////////////////////// // Enums /////////////////////////////////////////////////////////////////////////////////////////// @@ -133,6 +134,8 @@ public String toString() { private final XmrTxProofHttpClient httpClient; private final XmrTxProofModel xmrTxProofModel; private final long firstRequest; + @Getter + private final String id; private boolean terminated; @Getter @@ -148,15 +151,19 @@ public String toString() { XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, XmrTxProofModel xmrTxProofModel) { + this.xmrTxProofModel = xmrTxProofModel; + this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); this.httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); if (xmrTxProofModel.getServiceAddress().matches("^192.*|^localhost.*")) { log.info("Ignoring Socks5 proxy for local net address: {}", xmrTxProofModel.getServiceAddress()); this.httpClient.setIgnoreSocks5Proxy(true); } - this.xmrTxProofModel = xmrTxProofModel; + this.terminated = false; firstRequest = System.currentTimeMillis(); + + id = xmrTxProofModel.getTradeId() + "@" + xmrTxProofModel.getServiceAddress(); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -177,7 +184,7 @@ public void start(Consumer resultHandler, FaultHandler faultHandler) { } ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("XmrTransferProofRequest-" + xmrTxProofModel.getUID()); + Thread.currentThread().setName("XmrTransferProofRequest-" + id); String param = "/api/outputs?txhash=" + xmrTxProofModel.getTxHash() + "&address=" + xmrTxProofModel.getRecipientAddress() + "&viewkey=" + xmrTxProofModel.getTxKey() + @@ -229,9 +236,6 @@ public void onFailure(@NotNull Throwable throwable) { }); } - String getUID() { - return xmrTxProofModel.getUID(); - } String getServiceAddress() { return xmrTxProofModel.getServiceAddress(); diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java index 6851139c8fa..4f1cbfb5c94 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java @@ -44,7 +44,7 @@ public XmrTxProofRequestService(Socks5ProxyProvider provider) { @Nullable XmrTxProofRequest getRequest(XmrTxProofModel model) { XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); - String uid = request.getUID(); + String uid = request.getId(); if (map.containsKey(uid)) { log.warn("We started a proof request for uid {} already", uid); return null; diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java index 05547eb3218..e6c90eb38f4 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java @@ -154,7 +154,7 @@ public void maybeStartRequestTxProofProcess(Trade trade, List activeTrade for (String serviceAddress : serviceAddresses) { - XmrTxProofModel xmrTxProofModel = new XmrTxProofModel( + XmrTxProofModel xmrTxProofModel = new XmrTxProofModel(trade.getId(), txHash, txKey, recipientAddress, diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java index bdbbda4a9a7..1f80392245e 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java @@ -7,7 +7,6 @@ import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class XmrTxProofParserTest { @@ -25,6 +24,7 @@ public void prepareMocksAndObjects() { String serviceAddress = "127.0.0.1:8081"; xmrTxProofModel = new XmrTxProofModel( + "dummyTest", txHash, txKey, recipientAddress, @@ -34,13 +34,6 @@ public void prepareMocksAndObjects() { serviceAddress); } - @Test - public void testKey() { - assertTrue(xmrTxProofModel.getUID().contains(xmrTxProofModel.getTxHash())); - assertTrue(xmrTxProofModel.getUID().contains(xmrTxProofModel.getServiceAddress())); - assertFalse(xmrTxProofModel.getUID().contains(xmrTxProofModel.getRecipientAddress())); - } - @Test public void testJsonRoot() { // checking what happens when bad input is provided From 580b05968f9c3effe1339b677fd3f6316b5f15d4 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 11:43:56 -0500 Subject: [PATCH 54/85] Increase timeouts from 10 sec to 30 sec. Over tor connections are slower. Use TimeUnit.SECONDS.toMillis() for better readability and making it more clear whats the unit used in the method. --- .../java/bisq/network/http/HttpClient.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/p2p/src/main/java/bisq/network/http/HttpClient.java b/p2p/src/main/java/bisq/network/http/HttpClient.java index 3740a0977f0..1890a3eaddd 100644 --- a/p2p/src/main/java/bisq/network/http/HttpClient.java +++ b/p2p/src/main/java/bisq/network/http/HttpClient.java @@ -46,6 +46,7 @@ import java.io.InputStreamReader; import java.util.UUID; +import java.util.concurrent.TimeUnit; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -83,7 +84,9 @@ public void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy) { this.ignoreSocks5Proxy = ignoreSocks5Proxy; } - public String requestWithGET(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException { + public String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { checkNotNull(baseUrl, "baseUrl must be set before calling requestWithGET"); Socks5Proxy socks5Proxy = null; @@ -107,15 +110,17 @@ public String requestWithGET(String param, @Nullable String headerKey, @Nullable /** * Make an HTTP Get request directly (not routed over socks5 proxy). */ - public String requestWithGETNoProxy(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException { + public String requestWithGETNoProxy(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { HttpURLConnection connection = null; log.debug("Executing HTTP request " + baseUrl + param + " proxy: none."); URL url = new URL(baseUrl + param); try { connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); - connection.setConnectTimeout(10_000); - connection.setReadTimeout(10_000); + connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(30)); + connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(30)); connection.setRequestProperty("User-Agent", "bisq/" + Version.VERSION); if (headerKey != null && headerValue != null) connection.setRequestProperty(headerKey, headerValue); @@ -148,7 +153,10 @@ public String getUid() { /** * Make an HTTP Get request routed over socks5 proxy. */ - private String requestWithGETProxy(String param, Socks5Proxy socks5Proxy, @Nullable String headerKey, @Nullable String headerValue) throws IOException { + private String requestWithGETProxy(String param, + Socks5Proxy socks5Proxy, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { log.debug("requestWithGETProxy param=" + param); // This code is adapted from: // http://stackoverflow.com/a/25203021/5616248 From 512f1a5972f828bf25322c84a7685d441f9e98b7 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 16:00:02 -0500 Subject: [PATCH 55/85] Refactor AutoConfirmSettings handling in preferences - Do not use immutability for AutoConfirmSettings as it makes setting values cumbersome. - Add btc validator for trade limit - Make AutoConfirmSettings an optional and add find method by currency code to be better prepared when used for other coins. - Add static getDefaultForXmr to AutoConfirmSettings - Move listener creation to init method --- .../bisq/core/user/AutoConfirmSettings.java | 36 +++-- .../main/java/bisq/core/user/Preferences.java | 58 ++++---- .../util/validation/IntegerValidator.java | 5 + .../settings/preferences/PreferencesView.java | 133 +++++++++--------- .../main/java/bisq/desktop/util/GUIUtil.java | 4 +- 5 files changed, 130 insertions(+), 106 deletions(-) diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java index e83acb7c6d3..06d16f846a4 100644 --- a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -21,21 +21,37 @@ import com.google.protobuf.Message; +import org.bitcoinj.core.Coin; + import java.util.ArrayList; import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter public final class AutoConfirmSettings implements PersistablePayload { - public final boolean enabled; - public final int requiredConfirmations; - public final long tradeLimit; - public final List serviceAddresses; - public final String currencyCode; + private boolean enabled; + private int requiredConfirmations; + private long tradeLimit; + private List serviceAddresses; + private String currencyCode; + + public static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { + return new AutoConfirmSettings( + false, + 5, + Coin.COIN.value, + serviceAddresses, + "XMR"); + } - public AutoConfirmSettings(boolean enabled, - int requiredConfirmations, - long tradeLimit, - List serviceAddresses, - String currencyCode) { + AutoConfirmSettings(boolean enabled, + int requiredConfirmations, + long tradeLimit, + List serviceAddresses, + String currencyCode) { this.enabled = enabled; this.requiredConfirmations = requiredConfirmations; this.tradeLimit = tradeLimit; diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 1493e779c6c..597e0c95e62 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -40,8 +40,6 @@ import bisq.common.storage.Storage; import bisq.common.util.Utilities; -import org.bitcoinj.core.Coin; - import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; @@ -62,6 +60,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.Random; import java.util.stream.Collectors; @@ -321,6 +320,10 @@ public void readPersisted() { setUsePriceNotifications(true); } + if (prefPayload.getAutoConfirmSettingsList().isEmpty()) { + prefPayload.getAutoConfirmSettingsList().add(AutoConfirmSettings.getDefaultForXmr(getDefaultXmrProofProviders())); + } + // We set the capability in CoreNetworkCapabilities if the program argument is set. // If we have set it in the preferences view we handle it here. CoreNetworkCapabilities.maybeApplyDaoFullMode(config); @@ -410,33 +413,38 @@ public void setTacAcceptedV120(boolean tacAccepted) { persist(); } - // AutoConfirmSettings is currently only used for XMR. Although it could - // potentially in the future be used for others too. In the interest of flexibility - // we store it as a list in the proto definition, but in practical terms the - // application is not coded to handle more than one entry. For now this API - // to get/set AutoConfirmSettings is the gatekeeper. If in the future we adapt - // the application to manage more than one altcoin AutoConfirmSettings then - // this API will need to incorporate lookup by coin. - public AutoConfirmSettings getAutoConfirmSettings() { - if (prefPayload.getAutoConfirmSettingsList().size() == 0) { - // default values for AutoConfirmSettings when persisted payload is empty: - prefPayload.getAutoConfirmSettingsList().add(new AutoConfirmSettings( - false, 5, Coin.COIN.value, getDefaultXmrProofProviders(), "XMR")); - } - return prefPayload.getAutoConfirmSettingsList().get(0); + public Optional findAutoConfirmSettings(String currencyCode) { + return prefPayload.getAutoConfirmSettingsList().stream() + .filter(e -> e.getCurrencyCode().equals(currencyCode)) + .findAny(); } - public void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings) { - // see above comment regarding only one entry in this list currently - prefPayload.getAutoConfirmSettingsList().clear(); - prefPayload.getAutoConfirmSettingsList().add(autoConfirmSettings); - persist(); + public void setAutoConfServiceAddresses(String currencyCode, List serviceAddresses) { + findAutoConfirmSettings(currencyCode).ifPresent(e -> { + e.setServiceAddresses(serviceAddresses); + persist(); + }); } - public void setAutoConfServiceAddresses(List serviceAddresses) { - AutoConfirmSettings x = this.getAutoConfirmSettings(); - this.setAutoConfirmSettings(new AutoConfirmSettings( - x.enabled, x.requiredConfirmations, x.tradeLimit, serviceAddresses, x.currencyCode)); + public void setAutoConfEnabled(String currencyCode, boolean enabled) { + findAutoConfirmSettings(currencyCode).ifPresent(e -> { + e.setEnabled(enabled); + persist(); + }); + } + + public void setAutoConfRequiredConfirmations(String currencyCode, int requiredConfirmations) { + findAutoConfirmSettings(currencyCode).ifPresent(e -> { + e.setRequiredConfirmations(requiredConfirmations); + persist(); + }); + } + + public void setAutoConfTradeLimit(String currencyCode, long tradeLimit) { + findAutoConfirmSettings(currencyCode).ifPresent(e -> { + e.setTradeLimit(tradeLimit); + persist(); + }); } private void persist() { diff --git a/core/src/main/java/bisq/core/util/validation/IntegerValidator.java b/core/src/main/java/bisq/core/util/validation/IntegerValidator.java index d2392e142e5..d904eb497a3 100644 --- a/core/src/main/java/bisq/core/util/validation/IntegerValidator.java +++ b/core/src/main/java/bisq/core/util/validation/IntegerValidator.java @@ -32,6 +32,11 @@ public class IntegerValidator extends InputValidator { public IntegerValidator() { } + public IntegerValidator(int minValue, int maxValue) { + this.minValue = minValue; + this.maxValue = maxValue; + } + public ValidationResult validate(String input) { ValidationResult validationResult = super.validate(input); if (!validationResult.isValid) diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 7f80fdc27f5..58a39ad6503 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -29,6 +29,7 @@ import bisq.desktop.util.GUIUtil; import bisq.desktop.util.ImageUtil; import bisq.desktop.util.Layout; +import bisq.desktop.util.validation.BtcValidator; import bisq.desktop.util.validation.RegexValidator; import bisq.core.btc.wallet.Restrictions; @@ -44,7 +45,6 @@ import bisq.core.locale.Res; import bisq.core.locale.TradeCurrency; import bisq.core.provider.fee.FeeService; -import bisq.core.user.AutoConfirmSettings; import bisq.core.user.BlockChainExplorer; import bisq.core.user.Preferences; import bisq.core.util.FormattingUtils; @@ -111,12 +111,12 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox; private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically, - avoidStandbyMode, useCustomFee, autoConfirmXmr; + avoidStandbyMode, useCustomFee, autoConfirmXmrToggle; private int gridRow = 0; private int displayCurrenciesGridRowIndex = 0; private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, ignoreDustThresholdInputTextField, - autoConfRequiredConfirmations, autoConfServiceAddress, autoConfTradeLimit, /*referralIdInputTextField,*/ - rpcUserTextField, blockNotifyPortTextField; + autoConfRequiredConfirmationsTf, autoConfServiceAddressTf, autoConfTradeLimitTf, /*referralIdInputTextField,*/ + rpcUserTextField, blockNotifyPortTextField; private ToggleButton isDaoFullNodeToggleButton; private PasswordTextField rpcPwTextField; private TitledGroupBg daoOptionsTitledGroupBg; @@ -656,19 +656,52 @@ private void initializeAutoConfirmOptions() { root.add(subGrid, 2, displayCurrenciesGridRowIndex, 2, 10); addTitledGroupBg(subGrid, 0, 4, Res.get("setting.preferences.autoConfirmXMR"), 0); int localRowIndex = 0; - autoConfirmXmr = addSlideToggleButton(subGrid, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); - autoConfRequiredConfirmations = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); - autoConfTradeLimit = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); - autoConfServiceAddress = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmServiceAddresses")); - GridPane.setHgrow(autoConfServiceAddress, Priority.ALWAYS); + autoConfirmXmrToggle = addSlideToggleButton(subGrid, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); + + autoConfRequiredConfirmationsTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); + autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(0, 1000)); + + autoConfTradeLimitTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); + autoConfTradeLimitTf.setValidator(new BtcValidator(formatter)); + + autoConfServiceAddressTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmServiceAddresses")); + autoConfServiceAddressTf.setValidator(GUIUtil.addressRegexValidator()); + autoConfServiceAddressTf.setErrorMessage(Res.get("validation.invalidAddressList")); + GridPane.setHgrow(autoConfServiceAddressTf, Priority.ALWAYS); displayCurrenciesGridRowIndex += 4; + autoConfServiceAddressListener = (observable, oldValue, newValue) -> { + if (!newValue.equals(oldValue) && autoConfServiceAddressTf.getValidator().validate(newValue).isValid) { + List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); + // revert to default service providers when user empties the list + if (serviceAddresses.size() == 1 && serviceAddresses.get(0).isEmpty()) { + serviceAddresses = Preferences.getDefaultXmrProofProviders(); + } + preferences.setAutoConfServiceAddresses("XMR", serviceAddresses); + } + }; + + autoConfRequiredConfirmationsListener = (observable, oldValue, newValue) -> { + if (!newValue.equals(oldValue) && autoConfRequiredConfirmationsTf.getValidator().validate(newValue).isValid) { + int requiredConfirmations = Integer.parseInt(newValue); + preferences.setAutoConfRequiredConfirmations("XMR", requiredConfirmations); + } + }; + autoConfTradeLimitListener = (observable, oldValue, newValue) -> { + if (!newValue.equals(oldValue) && autoConfTradeLimitTf.getValidator().validate(newValue).isValid) { + Coin amountAsCoin = ParsingUtils.parseToCoin(newValue, formatter); + preferences.setAutoConfTradeLimit("XMR", amountAsCoin.value); + } + }; + autoConfFocusOutListener = (observable, oldValue, newValue) -> { if (oldValue && !newValue) { log.info("Service address focus out, check and re-display default option"); - if (autoConfServiceAddress.getText().length() == 0) { - autoConfServiceAddress.setText(String.join(", ", - preferences.getAutoConfirmSettings().serviceAddresses)); + if (autoConfServiceAddressTf.getText().isEmpty()) { + preferences.findAutoConfirmSettings("XMR").ifPresent(autoConfirmSettings -> { + List serviceAddresses = autoConfirmSettings.getServiceAddresses(); + autoConfServiceAddressTf.setText(String.join(", ", serviceAddresses)); + }); } } }; @@ -926,59 +959,21 @@ private void activateDaoPreferences() { } private void activateAutoConfirmPreferences() { - AutoConfirmSettings init = preferences.getAutoConfirmSettings(); - autoConfirmXmr.setSelected(init.enabled); - autoConfRequiredConfirmations.setText(String.valueOf(init.requiredConfirmations)); - autoConfTradeLimit.setText(formatter.formatCoin(Coin.valueOf(init.tradeLimit))); - autoConfServiceAddress.setText(String.join(", ", init.serviceAddresses)); - - autoConfirmXmr.setOnAction(e -> { - boolean enabled = autoConfirmXmr.isSelected(); - AutoConfirmSettings x = preferences.getAutoConfirmSettings(); - preferences.setAutoConfirmSettings( - new AutoConfirmSettings(enabled, x.requiredConfirmations, x.tradeLimit, x.serviceAddresses, x.currencyCode)); + preferences.findAutoConfirmSettings("XMR").ifPresent(autoConfirmSettings -> { + autoConfirmXmrToggle.setSelected(autoConfirmSettings.isEnabled()); + autoConfRequiredConfirmationsTf.setText(String.valueOf(autoConfirmSettings.getRequiredConfirmations())); + autoConfTradeLimitTf.setText(formatter.formatCoin(Coin.valueOf(autoConfirmSettings.getTradeLimit()))); + autoConfServiceAddressTf.setText(String.join(", ", autoConfirmSettings.getServiceAddresses())); + + autoConfRequiredConfirmationsTf.textProperty().addListener(autoConfRequiredConfirmationsListener); + autoConfTradeLimitTf.textProperty().addListener(autoConfTradeLimitListener); + autoConfServiceAddressTf.textProperty().addListener(autoConfServiceAddressListener); + autoConfServiceAddressTf.focusedProperty().addListener(autoConfFocusOutListener); + + autoConfirmXmrToggle.setOnAction(e -> { + preferences.setAutoConfEnabled(autoConfirmSettings.getCurrencyCode(), autoConfirmXmrToggle.isSelected()); + }); }); - - autoConfServiceAddress.setValidator(GUIUtil.addressRegexValidator()); - autoConfServiceAddress.setErrorMessage(Res.get("validation.invalidAddressList")); - autoConfServiceAddressListener = (observable, oldValue, newValue) -> { - if (GUIUtil.addressRegexValidator().validate(newValue).isValid && !newValue.equals(oldValue)) { - List serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(",")); - // revert to default service providers when user empties the list - if (serviceAddresses.size() == 1 && serviceAddresses.get(0).length() == 0) - serviceAddresses = Preferences.getDefaultXmrProofProviders(); - preferences.setAutoConfServiceAddresses(serviceAddresses); - } - }; - - IntegerValidator validator = new IntegerValidator(); - validator.setMinValue(1); validator.setMaxValue(10000); - autoConfRequiredConfirmations.setValidator(validator); - autoConfRequiredConfirmationsListener = (observable, oldValue, newValue) -> { - try { - int value = Integer.parseInt(newValue); - if (!newValue.equals(oldValue)) { - AutoConfirmSettings x = preferences.getAutoConfirmSettings(); - preferences.setAutoConfirmSettings( - new AutoConfirmSettings(x.enabled, value, x.tradeLimit, x.serviceAddresses, x.currencyCode)); - } - } catch (Throwable ignore) { - } - }; - autoConfTradeLimitListener = (observable, oldValue, newValue) -> { - try { - Coin amountAsCoin = ParsingUtils.parseToCoin(newValue, formatter); - AutoConfirmSettings x = preferences.getAutoConfirmSettings(); - preferences.setAutoConfirmSettings( - new AutoConfirmSettings(x.enabled, x.requiredConfirmations, amountAsCoin.value, x.serviceAddresses, x.currencyCode)); - } catch (Throwable ignore) { - } - }; - - autoConfRequiredConfirmations.textProperty().addListener(autoConfRequiredConfirmationsListener); - autoConfTradeLimit.textProperty().addListener(autoConfTradeLimitListener); - autoConfServiceAddress.textProperty().addListener(autoConfServiceAddressListener); - autoConfServiceAddress.focusedProperty().addListener(autoConfFocusOutListener); } private void updateDaoFields() { @@ -1051,10 +1046,10 @@ private void deactivateDaoPreferences() { } private void deactivateAutoConfirmPreferences() { - autoConfirmXmr.setOnAction(null); - autoConfRequiredConfirmations.textProperty().removeListener(autoConfRequiredConfirmationsListener); - autoConfTradeLimit.textProperty().removeListener(autoConfTradeLimitListener); - autoConfServiceAddress.textProperty().removeListener(autoConfServiceAddressListener); - autoConfServiceAddress.focusedProperty().removeListener(autoConfFocusOutListener); + autoConfirmXmrToggle.setOnAction(null); + autoConfRequiredConfirmationsTf.textProperty().removeListener(autoConfRequiredConfirmationsListener); + autoConfTradeLimitTf.textProperty().removeListener(autoConfTradeLimitListener); + autoConfServiceAddressTf.textProperty().removeListener(autoConfServiceAddressListener); + autoConfServiceAddressTf.focusedProperty().removeListener(autoConfFocusOutListener); } } diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index f8d17492a00..8879495a5b7 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -1175,10 +1175,10 @@ public static String getProofResultAsString(@Nullable AssetTxProofResult result) case INVALID_DATA: return Res.get(key, result.getDetails()); case PAYOUT_TX_ALREADY_PUBLISHED: - case REQUEST_STARTED: + case REQUESTS_STARTED: return Res.get(key); case PENDING: - return Res.get(key, result.getNumSuccessResults(), result.getRequiredSuccessResults(), result.getDetails()); + return Res.get(key, result.getNumSuccessResults(), result.getNumRequiredSuccessResults(), result.getDetails()); case COMPLETED: case ERROR: case FAILED: From 10dcc705388a359d867311a28c5f1d1fe24d97ec Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 17:14:01 -0500 Subject: [PATCH 56/85] Change log level if banned seed or price relay is seen from filter. Change log text. Those banned nodes are quite old and no need to restart the app. In case we need to really ban a node (the one we banned are just revoked) we can use the global notification to alert users to restart. --- core/src/main/java/bisq/core/app/BisqSetup.java | 4 ++-- core/src/main/resources/i18n/displayStrings.properties | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/bisq/core/app/BisqSetup.java b/core/src/main/java/bisq/core/app/BisqSetup.java index 36ae441eb08..18873821a02 100644 --- a/core/src/main/java/bisq/core/app/BisqSetup.java +++ b/core/src/main/java/bisq/core/app/BisqSetup.java @@ -786,13 +786,13 @@ private void initDomainServices() { filterManager.addListener(filter -> { if (filter != null && filterWarningHandler != null) { if (filter.getSeedNodes() != null && !filter.getSeedNodes().isEmpty()) { - log.warn(Res.get("popup.warning.nodeBanned", Res.get("popup.warning.seed"))); + log.info(Res.get("popup.warning.nodeBanned", Res.get("popup.warning.seed"))); // Let's keep that more silent. Might be used in case a node is unstable and we don't want to confuse users. // filterWarningHandler.accept(Res.get("popup.warning.nodeBanned", Res.get("popup.warning.seed"))); } if (filter.getPriceRelayNodes() != null && !filter.getPriceRelayNodes().isEmpty()) { - log.warn(Res.get("popup.warning.nodeBanned", Res.get("popup.warning.priceRelay"))); + log.info(Res.get("popup.warning.nodeBanned", Res.get("popup.warning.priceRelay"))); // Let's keep that more silent. Might be used in case a node is unstable and we don't want to confuse users. // filterWarningHandler.accept(Res.get("popup.warning.nodeBanned", Res.get("popup.warning.priceRelay"))); } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index aee829d2e5b..594b1b09ef9 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -582,7 +582,7 @@ portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabl portfolio.pending.autoConf.state.TRADE_LIMIT_EXCEEDED=Trade amount exceeds auto-confirm amount limit portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data: {0} portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published. -portfolio.pending.autoConf.state.REQUEST_STARTED=Proof request started +portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started portfolio.pending.autoConf.state.PENDING={0} service(s) out of {1} have succeeded. {2} portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. @@ -2666,7 +2666,7 @@ popup.warning.lockedUpFunds=You have locked up funds from a failed trade.\n\ Trade ID: {2}.\n\n\ Please open a support ticket by selecting the trade in the open trades screen and pressing \"alt + o\" or \"option + o\"." -popup.warning.nodeBanned=One of the {0} nodes got banned. Please restart your application to be sure to not be connected to the banned node. +popup.warning.nodeBanned=One of the {0} nodes got banned. popup.warning.priceRelay=price relay popup.warning.seed=seed popup.warning.mandatoryUpdate.trading=Please update to the latest Bisq version. \ From 28c8150b6b87b74005b0d349ce914b5651233aef Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 19:10:43 -0500 Subject: [PATCH 57/85] Refactoring This is another larger refactoring, sorry ;-) But the structure was just not correct before. We had handled multiple trades with multiple results and that is error prone. Now each class has much more clear responsibilities. Also the result enums are not changes so that they are better separated between result based and service bases states. All different states are still hard to test. We should set up some mock service to simulate confirmations counting up and error scenarios. --- .../java/bisq/core/trade/TradeManager.java | 40 ++- .../trade/autoconf/AssetTxProofResult.java | 27 +- .../trade/autoconf/xmr/XmrTxProofModel.java | 42 +-- .../trade/autoconf/xmr/XmrTxProofRequest.java | 93 ++++--- .../xmr/XmrTxProofRequestService.java | 68 ----- .../xmr/XmrTxProofRequestsPerTrade.java | 217 +++++++++++++++ .../trade/autoconf/xmr/XmrTxProofService.java | 261 +++--------------- ...CounterCurrencyTransferStartedMessage.java | 9 +- .../pendingtrades/PendingTradesDataModel.java | 8 +- .../steps/seller/SellerStep3View.java | 2 - 10 files changed, 397 insertions(+), 370 deletions(-) delete mode 100644 core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java create mode 100644 core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 29d608c4413..eeb8b3e608f 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -38,6 +38,7 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; +import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.trade.autoconf.xmr.XmrTxProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; @@ -58,6 +59,7 @@ import bisq.network.p2p.SendMailboxMessageListener; import bisq.common.ClockWatcher; +import bisq.common.UserThread; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import bisq.common.handlers.ErrorMessageHandler; @@ -109,6 +111,8 @@ import javax.annotation.Nullable; +import static com.google.common.base.Preconditions.checkArgument; + public class TradeManager implements PersistedDataHost { private static final Logger log = LoggerFactory.getLogger(TradeManager.class); @@ -282,6 +286,7 @@ public void onUpdatedDataReceived() { } public void shutDown() { + xmrTxProofService.shutDown(); } private void initPendingTrades() { @@ -327,7 +332,9 @@ private void initPendingTrades() { if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG && trade.getCounterCurrencyExtraData() != null) { - xmrTxProofService.maybeStartRequestTxProofProcess(trade, tradableList.getList()); + checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); + // We delay a bit as at startup lots of stuff is happening + UserThread.runAfter(() -> maybeStartXmrTxProofServices((SellerTrade) trade), 1); } } ); @@ -353,6 +360,37 @@ private void initPendingTrades() { pendingTradesInitialized.set(true); } + public void maybeStartXmrTxProofServices(SellerTrade sellerTrade) { + xmrTxProofService.maybeStartRequests(sellerTrade, tradableList.getList(), + assetTxProofResult -> { + if (assetTxProofResult == AssetTxProofResult.COMPLETED) { + log.info("###########################################################################################"); + log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", sellerTrade.getShortId()); + log.info("###########################################################################################"); + autoConfirmFiatPaymentReceived(sellerTrade); + } + }, + (errorMessage, throwable) -> { + log.error(errorMessage); + }); + } + + private void autoConfirmFiatPaymentReceived(SellerTrade sellerTrade) { + onFiatPaymentReceived(sellerTrade, + () -> { + }, errorMessage -> { + }); + } + + public void onFiatPaymentReceived(SellerTrade sellerTrade, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { + sellerTrade.onFiatPaymentReceived(resultHandler, errorMessageHandler); + + //TODO move to trade protocol task + accountAgeWitnessService.maybeSignWitness(sellerTrade); + } + private void initPendingTrade(Trade trade) { initTrade(trade, trade.getProcessModel().isUseSavingsWallet(), trade.getProcessModel().getFundsNeededForTradeAsLong()); diff --git a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java index 78a537fe500..6a489f136c2 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java @@ -27,8 +27,8 @@ public enum AssetTxProofResult { INVALID_DATA, // Peer provided invalid data. Might be a scam attempt (e.g. txKey reused) PAYOUT_TX_ALREADY_PUBLISHED, - REQUEST_STARTED, - PENDING, + REQUESTS_STARTED(false), + PENDING(false), // All services completed with a success state COMPLETED, @@ -40,11 +40,22 @@ public enum AssetTxProofResult { FAILED; @Getter - private transient int numSuccessResults; + private int numSuccessResults; @Getter - private transient int requiredSuccessResults; + private int numRequiredSuccessResults; @Getter - private transient String details = ""; + private String details = ""; + // If isTerminal is set it means that we stop the service + @Getter + private final boolean isTerminal; + + AssetTxProofResult() { + this(false); + } + + AssetTxProofResult(boolean isTerminal) { + this.isTerminal = isTerminal; + } public AssetTxProofResult numSuccessResults(int numSuccessResults) { @@ -52,8 +63,8 @@ public AssetTxProofResult numSuccessResults(int numSuccessResults) { return this; } - public AssetTxProofResult requiredSuccessResults(int requiredSuccessResults) { - this.requiredSuccessResults = requiredSuccessResults; + public AssetTxProofResult numRequiredSuccessResults(int numRequiredSuccessResults) { + this.numRequiredSuccessResults = numRequiredSuccessResults; return this; } @@ -66,7 +77,7 @@ public AssetTxProofResult details(String details) { public String toString() { return "AssetTxProofResult{" + "\n numSuccessResults=" + numSuccessResults + - ",\n requiredSuccessResults=" + requiredSuccessResults + + ",\n requiredSuccessResults=" + numRequiredSuccessResults + ",\n details='" + details + '\'' + "\n} " + super.toString(); } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java index bedddd4da77..f8812c8f076 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java @@ -17,11 +17,22 @@ package bisq.core.trade.autoconf.xmr; +import bisq.core.monetary.Volume; +import bisq.core.payment.payload.AssetsAccountPayload; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.Trade; + +import bisq.common.app.DevEnv; + +import org.bitcoinj.core.Coin; + import java.util.Date; import lombok.Value; import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkNotNull; + @Slf4j @Value public class XmrTxProofModel { @@ -40,22 +51,21 @@ public class XmrTxProofModel { private final int confirmsRequired; private final String serviceAddress; - XmrTxProofModel( - String tradeId, - String txHash, - String txKey, - String recipientAddress, - long amount, - Date tradeDate, - int confirmsRequired, - String serviceAddress) { - this.tradeId = tradeId; - this.txHash = txHash; - this.txKey = txKey; - this.recipientAddress = recipientAddress; - this.amount = amount; - this.tradeDate = tradeDate; - this.confirmsRequired = confirmsRequired; + public XmrTxProofModel(Trade trade, String serviceAddress, int confirmsRequired) { this.serviceAddress = serviceAddress; + this.confirmsRequired = confirmsRequired; + Coin tradeAmount = trade.getTradeAmount(); + Volume volume = checkNotNull(trade.getOffer()).getVolumeByAmount(tradeAmount); + amount = DevEnv.isDevMode() ? + XmrTxProofModel.DEV_AMOUNT : // For dev testing we need to add the matching address to the dev tx key and dev view key + volume != null ? volume.getValue() * 10000L : 0L; // XMR satoshis have 12 decimal places vs. bitcoin's 8 + PaymentAccountPayload sellersPaymentAccountPayload = checkNotNull(trade.getContract()).getSellerPaymentAccountPayload(); + recipientAddress = DevEnv.isDevMode() ? + XmrTxProofModel.DEV_ADDRESS : // For dev testing we need to add the matching address to the dev tx key and dev view key + ((AssetsAccountPayload) sellersPaymentAccountPayload).getAddress(); + txHash = trade.getCounterCurrencyTxId(); + txKey = trade.getCounterCurrencyExtraData(); + tradeDate = trade.getDate(); + tradeId = trade.getId(); } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java index b5c00121f4e..f95a35d0dca 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java @@ -32,11 +32,10 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -45,10 +44,11 @@ import javax.annotation.Nullable; /** - * Requests for the XMR tx proof for a particular trade and one service. - * Repeats requests if tx is not confirmed yet. + * Requests for the XMR tx proof for a particular trade from a particular service. + * Repeats every 90 sec requests if tx is not confirmed or found yet until MAX_REQUEST_PERIOD of 12 hours is reached. */ @Slf4j +@EqualsAndHashCode class XmrTxProofRequest { /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,7 +94,8 @@ enum Detail { ADDRESS_INVALID, NO_MATCH_FOUND, AMOUNT_NOT_MATCHING, - TRADE_DATE_NOT_MATCHING; + TRADE_DATE_NOT_MATCHING, + NO_RESULTS_TIMEOUT; @Getter private int numConfirmations; @@ -134,13 +135,9 @@ public String toString() { private final XmrTxProofHttpClient httpClient; private final XmrTxProofModel xmrTxProofModel; private final long firstRequest; - @Getter - private final String id; private boolean terminated; @Getter - private List results = new ArrayList<>(); - @Getter @Nullable private Result result; @@ -149,52 +146,46 @@ public String toString() { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofModel xmrTxProofModel) { + XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, XmrTxProofModel xmrTxProofModel) { this.xmrTxProofModel = xmrTxProofModel; - this.httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); - this.httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); + httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); + httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); if (xmrTxProofModel.getServiceAddress().matches("^192.*|^localhost.*")) { log.info("Ignoring Socks5 proxy for local net address: {}", xmrTxProofModel.getServiceAddress()); - this.httpClient.setIgnoreSocks5Proxy(true); + httpClient.setIgnoreSocks5Proxy(true); } - this.terminated = false; + terminated = false; firstRequest = System.currentTimeMillis(); - - id = xmrTxProofModel.getTradeId() + "@" + xmrTxProofModel.getServiceAddress(); } /////////////////////////////////////////////////////////////////////////////////////////// // API /////////////////////////////////////////////////////////////////////////////////////////// - // used by the service to abort further automatic retries - void stop() { - terminated = true; - } - public void start(Consumer resultHandler, FaultHandler faultHandler) { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls // this scenario may happen if a re-request is scheduled from the callback below - log.info("Request() aborted, this object has been terminated. Service: {}", httpClient.getBaseUrl()); + log.warn("Not starting {} as we have already terminated.", this); return; } + // Timeout handing is delegated to the connection timeout handling in httpClient. + ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("XmrTransferProofRequest-" + id); + Thread.currentThread().setName("XmrTransferProofRequest-" + this.getShortId()); String param = "/api/outputs?txhash=" + xmrTxProofModel.getTxHash() + "&address=" + xmrTxProofModel.getRecipientAddress() + "&viewkey=" + xmrTxProofModel.getTxKey() + "&txprove=1"; - log.info("Requesting from {} with param {}", httpClient.getBaseUrl(), param); + log.info("Param {} for {}", param, this); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); - log.info("Response json\n{}", prettyJson); + log.info("Response json from {}\n{}", this, prettyJson); Result result = XmrTxProofParser.parse(xmrTxProofModel, json); - log.info("xmrTxProofResult {}", result); + log.info("Result from {}\n{}", this, result); return result; }); @@ -203,24 +194,32 @@ public void onSuccess(Result result) { XmrTxProofRequest.this.result = result; if (terminated) { - log.info("We received result {} but request to {} was terminated already.", result, httpClient.getBaseUrl()); + log.warn("We received {} but {} was terminated already. We do not process result.", result, this); return; } - results.add(result); + switch (result) { case PENDING: - if (System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD) { - log.warn("We have tried requesting from {} for too long, giving up.", httpClient.getBaseUrl()); - return; + if (isTimeOutReached()) { + log.warn("{} took too long without a success or failure/error result We give up. " + + "Might be that the transaction was never published.", this); + // If we reached out timeout we return with an error. + UserThread.execute(() -> resultHandler.accept(Result.ERROR.with(Detail.NO_RESULTS_TIMEOUT))); } else { UserThread.runAfter(() -> start(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); + // We update our listeners + UserThread.execute(() -> resultHandler.accept(result)); } - UserThread.execute(() -> resultHandler.accept(result)); break; case SUCCESS: + log.info("{} succeeded", result); + UserThread.execute(() -> resultHandler.accept(result)); + terminate(); + break; case FAILED: case ERROR: UserThread.execute(() -> resultHandler.accept(result)); + terminate(); break; default: log.warn("Unexpected result {}", result); @@ -228,7 +227,7 @@ public void onSuccess(Result result) { } public void onFailure(@NotNull Throwable throwable) { - String errorMessage = "Request to " + httpClient.getBaseUrl() + " failed"; + String errorMessage = this + " failed with error " + throwable.toString(); faultHandler.handleFault(errorMessage, throwable); UserThread.execute(() -> resultHandler.accept(Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage)))); @@ -236,23 +235,27 @@ public void onFailure(@NotNull Throwable throwable) { }); } + void terminate() { + terminated = true; + } String getServiceAddress() { return xmrTxProofModel.getServiceAddress(); } - int numSuccessResults() { - return (int) results.stream().filter(e -> e == XmrTxProofRequest.Result.SUCCESS).count(); - } - + // Convenient for logging @Override public String toString() { - return "XmrTxProofRequest{" + - ",\n httpClient=" + httpClient + - ",\n xmrTxProofModel=" + xmrTxProofModel + - ",\n firstRequest=" + firstRequest + - ",\n terminated=" + terminated + - ",\n result=" + result + - "\n}"; + return "Request at: " + xmrTxProofModel.getServiceAddress() + " for trade: " + xmrTxProofModel.getTradeId(); } + + private String getShortId() { + return Utilities.getShortId(xmrTxProofModel.getTradeId()) + " @ " + + xmrTxProofModel.getServiceAddress().substring(0, 6); + } + + private boolean isTimeOutReached() { + return System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD; + } + } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java deleted file mode 100644 index 4f1cbfb5c94..00000000000 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestService.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.autoconf.xmr; - -import bisq.network.Socks5ProxyProvider; - -import javax.inject.Inject; - -import java.util.HashMap; -import java.util.Map; - -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Nullable; - -/** - * Handles the XMR tx proof requests for multiple trades and multiple services. - */ -@Slf4j -class XmrTxProofRequestService { - private final Map map = new HashMap<>(); - private final Socks5ProxyProvider socks5ProxyProvider; - - @Inject - public XmrTxProofRequestService(Socks5ProxyProvider provider) { - socks5ProxyProvider = provider; - } - - @Nullable - XmrTxProofRequest getRequest(XmrTxProofModel model) { - XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); - String uid = request.getId(); - if (map.containsKey(uid)) { - log.warn("We started a proof request for uid {} already", uid); - return null; - } - - map.put(uid, request); - return request; - } - - // Get number of requests with at least 1 SUCCESS result - int numRequestsWithSuccessResult() { - return (int) map.values().stream() - .filter(request -> request.numSuccessResults() > 0) - .count(); - } - - void stopAllRequest() { - map.values().forEach(XmrTxProofRequest::stop); - map.clear(); - } -} diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java new file mode 100644 index 00000000000..962ce390c91 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java @@ -0,0 +1,217 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.autoconf.xmr; + +import bisq.core.locale.Res; +import bisq.core.trade.Trade; +import bisq.core.trade.autoconf.AssetTxProofResult; +import bisq.core.user.AutoConfirmSettings; + +import bisq.network.Socks5ProxyProvider; + +import bisq.common.handlers.FaultHandler; + +import org.bitcoinj.core.Coin; + +import javafx.beans.value.ChangeListener; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import lombok.extern.slf4j.Slf4j; + +import static bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Result; + +/** + * Handles the XMR tx proof requests for multiple services per trade. + */ +@Slf4j +class XmrTxProofRequestsPerTrade { + private final Socks5ProxyProvider socks5ProxyProvider; + private final Trade trade; + private final List serviceAddresses; + private final int numRequiredConfirmations; + private final long tradeLimit; + private final int numRequiredSuccessResults; + private final Set requests = new HashSet<>(); + + private int numSuccessResults; + private ChangeListener listener; + + XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, + Trade trade, + AutoConfirmSettings autoConfirmSettings) { + this.socks5ProxyProvider = socks5ProxyProvider; + this.trade = trade; + serviceAddresses = autoConfirmSettings.getServiceAddresses(); + numRequiredConfirmations = autoConfirmSettings.getRequiredConfirmations(); + tradeLimit = autoConfirmSettings.getTradeLimit(); + + numRequiredSuccessResults = serviceAddresses.size(); + } + + void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler) { + listener = (observable, oldValue, newValue) -> { + if (trade.isPayoutPublished()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); + } + }; + String txId = trade.getCounterCurrencyTxId(); + String txHash = trade.getCounterCurrencyExtraData(); + if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { + + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.INVALID_DATA); + return; + } + + if (isTradeAmountAboveLimit(trade)) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED); + return; + } + + if (trade.isPayoutPublished()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); + return; + } + + trade.stateProperty().addListener(listener); + + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED); + + for (String serviceAddress : serviceAddresses) { + XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, numRequiredSuccessResults); + XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); + + log.info("{} created", request); + requests.add(request); + + request.start(result -> { + AssetTxProofResult assetTxProofResult; + if (trade.isPayoutPublished()) { + assetTxProofResult = AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED; + callResultHandlerAndMaybeTerminate(resultHandler, assetTxProofResult); + return; + } + + switch (result) { + case PENDING: + // We expect repeated PENDING results with different details + assetTxProofResult = getAssetTxProofResultForPending(result); + break; + case SUCCESS: + numSuccessResults++; + if (numSuccessResults < numRequiredSuccessResults) { + // Request is success but not all have completed yet. + int remaining = numRequiredSuccessResults - numSuccessResults; + log.info("{} succeeded. We have {} remaining request(s) open.", + request, remaining); + assetTxProofResult = getAssetTxProofResultForPending(result); + } else { + // All our services have returned a SUCCESS result so we + // have completed on the service level. + log.info("All {} tx proof requests for trade {} have been successful.", + numRequiredSuccessResults, trade.getShortId()); + assetTxProofResult = AssetTxProofResult.COMPLETED; + } + break; + case FAILED: + log.warn("{} failed. " + + "This might not mean that the XMR transfer was invalid but you have to check yourself " + + "if the XMR transfer was correct. {}", + request, result); + + assetTxProofResult = AssetTxProofResult.FAILED; + break; + case ERROR: + default: + log.warn("{} resulted in an error. " + + "This might not mean that the XMR transfer was invalid but can be a network or " + + "service problem. {}", + request, result); + + assetTxProofResult = AssetTxProofResult.ERROR; + } + + callResultHandlerAndMaybeTerminate(resultHandler, assetTxProofResult); + }, + faultHandler); + } + } + + private void callResultHandlerAndMaybeTerminate(Consumer resultHandler, + AssetTxProofResult assetTxProofResult) { + resultHandler.accept(assetTxProofResult); + if (assetTxProofResult.isTerminal()) { + terminate(); + } + } + + private AssetTxProofResult getAssetTxProofResultForPending(Result result) { + XmrTxProofRequest.Detail detail = result.getDetail(); + int numConfirmations = detail != null ? detail.getNumConfirmations() : 0; + log.info("{} returned with numConfirmations {}", + result, numConfirmations); + + String detailString = ""; + if (XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS == detail) { + detailString = Res.get("portfolio.pending.autoConf.state.confirmations", + numConfirmations, numRequiredConfirmations); + + } else if (XmrTxProofRequest.Detail.TX_NOT_FOUND == detail) { + detailString = Res.get("portfolio.pending.autoConf.state.txNotFound"); + } + + return AssetTxProofResult.PENDING + .numSuccessResults(numSuccessResults) + .numRequiredSuccessResults(numRequiredSuccessResults) + .details(detailString); + } + + void terminate() { + requests.forEach(XmrTxProofRequest::terminate); + requests.clear(); + trade.stateProperty().removeListener(listener); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Validation + /////////////////////////////////////////////////////////////////////////////////////////// + + private boolean is32BitHexStringInValid(String hexString) { + if (hexString == null || hexString.isEmpty() || !hexString.matches("[a-fA-F0-9]{64}")) { + log.warn("Invalid hexString: {}", hexString); + return true; + } + + return false; + } + + private boolean isTradeAmountAboveLimit(Trade trade) { + Coin tradeAmount = trade.getTradeAmount(); + Coin tradeLimit = Coin.valueOf(this.tradeLimit); + if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { + log.warn("Trade amount {} is higher than limit from auto-conf setting {}.", + tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); + return true; + } + return false; + } +} diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java index e6c90eb38f4..62438468a12 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java @@ -17,40 +17,36 @@ package bisq.core.trade.autoconf.xmr; -import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; import bisq.core.locale.Res; -import bisq.core.monetary.Volume; import bisq.core.payment.payload.AssetsAccountPayload; -import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.user.AutoConfirmSettings; import bisq.core.user.Preferences; +import bisq.network.Socks5ProxyProvider; import bisq.network.p2p.P2PService; import bisq.common.app.DevEnv; - -import org.bitcoinj.core.Coin; +import bisq.common.handlers.FaultHandler; import javax.inject.Inject; import javax.inject.Singleton; -import javafx.beans.value.ChangeListener; - import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Stream; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import static bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Result; import static com.google.common.base.Preconditions.checkNotNull; /** @@ -61,16 +57,14 @@ @Singleton public class XmrTxProofService { private final FilterManager filterManager; + private final Socks5ProxyProvider socks5ProxyProvider; private final Preferences preferences; - private final XmrTxProofRequestService xmrTxProofRequestService; - private final AccountAgeWitnessService accountAgeWitnessService; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; - private final Map> listenerByTxId = new HashMap<>(); - @Getter - private int requiredSuccessResults; + + private final Map servicesByTradeId = new HashMap<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -80,20 +74,18 @@ public class XmrTxProofService { @Inject public XmrTxProofService(FilterManager filterManager, Preferences preferences, - XmrTxProofRequestService xmrTxProofRequestService, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, P2PService p2PService, WalletsSetup walletsSetup, - AccountAgeWitnessService accountAgeWitnessService) { + Socks5ProxyProvider socks5ProxyProvider) { this.filterManager = filterManager; this.preferences = preferences; - this.xmrTxProofRequestService = xmrTxProofRequestService; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; this.p2PService = p2PService; this.walletsSetup = walletsSetup; - this.accountAgeWitnessService = accountAgeWitnessService; + this.socks5ProxyProvider = socks5ProxyProvider; } @@ -101,16 +93,22 @@ public XmrTxProofService(FilterManager filterManager, // API /////////////////////////////////////////////////////////////////////////////////////////// - public void maybeStartRequestTxProofProcess(Trade trade, List activeTrades) { - if (!dataValid(trade)) { + public void maybeStartRequests(Trade trade, + List activeTrades, + Consumer resultHandler, + FaultHandler faultHandler) { + Optional optionalAutoConfirmSettings = preferences.findAutoConfirmSettings("XMR"); + if (!optionalAutoConfirmSettings.isPresent()) { + log.error("autoConfirmSettings is not present"); return; } + AutoConfirmSettings autoConfirmSettings = optionalAutoConfirmSettings.get(); if (!isXmrBuyer(trade)) { return; } - if (!isFeatureEnabled(trade)) { + if (!isFeatureEnabled(trade, autoConfirmSettings)) { return; } @@ -118,170 +116,34 @@ public void maybeStartRequestTxProofProcess(Trade trade, List activeTrade return; } - if (isTradeAmountAboveLimit(trade)) { - return; - } - if (wasTxKeyReUsed(trade, activeTrades)) { return; } - if (isPayoutPublished(trade)) { - return; - } - - Coin tradeAmount = trade.getTradeAmount(); - Volume volume = checkNotNull(trade.getOffer()).getVolumeByAmount(tradeAmount); - // XMR satoshis have 12 decimal places vs. bitcoin's 8 - long amountXmr = volume != null ? volume.getValue() * 10000L : 0L; - - PaymentAccountPayload sellersPaymentAccountPayload = checkNotNull(trade.getContract()).getSellerPaymentAccountPayload(); - String recipientAddress = ((AssetsAccountPayload) sellersPaymentAccountPayload).getAddress(); - if (DevEnv.isDevMode()) { - // For dev testing we need to add the matching address to the dev tx key and dev view key - recipientAddress = XmrTxProofModel.DEV_ADDRESS; - amountXmr = XmrTxProofModel.DEV_AMOUNT; - } - String txHash = trade.getCounterCurrencyTxId(); - String txKey = trade.getCounterCurrencyExtraData(); - - List serviceAddresses = preferences.getAutoConfirmSettings().serviceAddresses; - requiredSuccessResults = serviceAddresses.size(); - - ChangeListener listener = (observable, oldValue, newValue) -> isPayoutPublished(trade); - trade.stateProperty().addListener(listener); - listenerByTxId.put(trade.getId(), listener); - - - for (String serviceAddress : serviceAddresses) { - XmrTxProofModel xmrTxProofModel = new XmrTxProofModel(trade.getId(), - txHash, - txKey, - recipientAddress, - amountXmr, - trade.getDate(), - getRequiredConfirmations(), - serviceAddress); - XmrTxProofRequest request = xmrTxProofRequestService.getRequest(xmrTxProofModel); - if (request != null) { - request.start(result -> handleResult(request, trade, result), - (errorMsg, throwable) -> log.warn(errorMsg)); - trade.setAssetTxProofResult(AssetTxProofResult.REQUEST_STARTED); - } - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private void handleResult(XmrTxProofRequest request, - Trade trade, - Result result) { - if (isPayoutPublished(trade)) { - return; - } - - - // If one service fails we consider all failed as we require that all of our defined services result - // successfully. For now we keep it that way as it reduces complexity but it might be useful to - // support a min number of services which need to succeed from a larger set of services. - // E.g. 2 out of 3 services need to succeed. - - int numRequestsWithSuccessResult = xmrTxProofRequestService.numRequestsWithSuccessResult(); - switch (result) { - case PENDING: - applyPending(trade, result); - - // Repeating the requests is handled in XmrTransferProofRequester - return; - case SUCCESS: - if (numRequestsWithSuccessResult < requiredSuccessResults) { - log.info("Tx proof request to service {} for trade {} succeeded. We have {} remaining request(s) to other service(s).", - request.getServiceAddress(), trade.getShortId(), numRequestsWithSuccessResult); - - applyPending(trade, result); - return; // not all APIs have confirmed yet - } - - // All our services have returned a SUCCESS result so we have completed. - trade.setAssetTxProofResult(AssetTxProofResult.COMPLETED); - log.info("All {} tx proof requests for trade {} have been successful.", - requiredSuccessResults, trade.getShortId()); - removeListener(trade); - - if (!isPayoutPublished(trade)) { - log.info("We auto-confirm XMR receipt to complete trade {}.", trade.getShortId()); - // Trade state update is handled in the trade protocol method triggered by the onFiatPaymentReceived call - // This triggers the completion of the trade with signing and publishing the payout tx - ((SellerTrade) trade).onFiatPaymentReceived(() -> { - }, - errorMessage -> { - }); - accountAgeWitnessService.maybeSignWitness(trade); - } else { - log.info("Trade {} have been completed in the meantime.", trade.getShortId()); - } - terminate(trade); - return; - case FAILED: - log.warn("Tx proof request to service {} for trade {} failed. " + - "This might not mean that the XMR transfer was invalid but you have to check yourself " + - "if the XMR transfer was correct. Result={}", - request.getServiceAddress(), trade.getShortId(), result); - trade.setAssetTxProofResult(AssetTxProofResult.FAILED); - terminate(trade); - return; - case ERROR: - log.warn("Tx proof request to service {} for trade {} resulted in an error. " + - "This might not mean that the XMR transfer was invalid but can be a network or " + - "service problem. Result={}", - request.getServiceAddress(), trade.getShortId(), result); - - trade.setAssetTxProofResult(AssetTxProofResult.ERROR); - terminate(trade); - return; - default: - log.warn("Unexpected result {}", result); - } - } + XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(socks5ProxyProvider, + trade, + autoConfirmSettings); + servicesByTradeId.put(trade.getId(), service); + service.requestFromAllServices( + assetTxProofResult -> { + trade.setAssetTxProofResult(assetTxProofResult); - private void applyPending(Trade trade, Result result) { - int numRequestsWithSuccessResult = xmrTxProofRequestService.numRequestsWithSuccessResult(); - XmrTxProofRequest.Detail detail = result.getDetail(); - int numConfirmations = detail != null ? detail.getNumConfirmations() : 0; - log.info("Tx proof service received a {} result for trade {}. numConfirmations={}", - result, trade.getShortId(), numConfirmations); - - String details = ""; - if (XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS == detail) { - details = Res.get("portfolio.pending.autoConf.state.confirmations", numConfirmations, getRequiredConfirmations()); - - } else if (XmrTxProofRequest.Detail.TX_NOT_FOUND == detail) { - details = Res.get("portfolio.pending.autoConf.state.txNotFound"); - } - trade.setAssetTxProofResult(AssetTxProofResult.PENDING - .numSuccessResults(numRequestsWithSuccessResult) - .requiredSuccessResults(requiredSuccessResults) - .details(details)); - } + if (assetTxProofResult.isTerminal()) { + cleanUp(trade); + } - private void terminate(Trade trade) { - xmrTxProofRequestService.stopAllRequest(); - removeListener(trade); + resultHandler.accept(assetTxProofResult); + }, + faultHandler); } - private void removeListener(Trade trade) { - ChangeListener listener = listenerByTxId.get(trade.getId()); - if (listener != null) { - trade.stateProperty().removeListener(listener); - listenerByTxId.remove(trade.getId()); - } + public void shutDown() { + servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); + servicesByTradeId.clear(); } - private int getRequiredConfirmations() { - return preferences.getAutoConfirmSettings().requiredConfirmations; + private void cleanUp(Trade trade) { + servicesByTradeId.remove(trade.getId()); } @@ -289,37 +151,6 @@ private int getRequiredConfirmations() { // Validation /////////////////////////////////////////////////////////////////////////////////////////// - private boolean isPayoutPublished(Trade trade) { - if (trade.isPayoutPublished()) { - log.warn("Trade payout already published, shutting down all open API requests for trade {}", - trade.getShortId()); - trade.setAssetTxProofResult(AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); - terminate(trade); - return true; - } - return false; - } - - private boolean dataValid(Trade trade) { - String txKey = trade.getCounterCurrencyExtraData(); - String txHash = trade.getCounterCurrencyTxId(); - - if (txKey == null || txKey.isEmpty()) { - return false; - } - - if (txHash == null || txHash.isEmpty()) { - return false; - } - - if (!txHash.matches("[a-fA-F0-9]{64}") || !txKey.matches("[a-fA-F0-9]{64}")) { - log.error("Validation failed: txHash {} txKey {}", txHash, txKey); - return false; - } - - return true; - } - private boolean isXmrBuyer(Trade trade) { if (!checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) { return false; @@ -338,8 +169,8 @@ private boolean networkAndWalletReady() { walletsSetup.hasSufficientPeersForBroadcast(); } - private boolean isFeatureEnabled(Trade trade) { - boolean isEnabled = preferences.getAutoConfirmSettings().enabled && !isAutoConfDisabledByFilter(); + private boolean isFeatureEnabled(Trade trade, AutoConfirmSettings autoConfirmSettings) { + boolean isEnabled = checkNotNull(autoConfirmSettings).isEnabled() && !isAutoConfDisabledByFilter(); if (!isEnabled) { trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED); } @@ -351,20 +182,8 @@ private boolean isAutoConfDisabledByFilter() { filterManager.getFilter().isDisableAutoConf(); } - private boolean isTradeAmountAboveLimit(Trade trade) { - Coin tradeAmount = trade.getTradeAmount(); - Coin tradeLimit = Coin.valueOf(preferences.getAutoConfirmSettings().tradeLimit); - if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { - log.warn("Trade amount {} is higher than settings limit {}, will not attempt auto-confirm", - tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); - - trade.setAssetTxProofResult(AssetTxProofResult.TRADE_LIMIT_EXCEEDED); - return true; - } - return false; - } - private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { + // For dev testing we reuse test data so we ignore that check if (DevEnv.isDevMode()) { return false; } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index db3a27ea3ca..8d25bb1cb3f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -17,6 +17,7 @@ package bisq.core.trade.protocol.tasks.seller; +import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.TradeTask; @@ -26,6 +27,7 @@ import lombok.extern.slf4j.Slf4j; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j @@ -56,10 +58,11 @@ protected void run() { } String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); - if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { + if (counterCurrencyExtraData != null && + counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - processModel.getTradeManager().getXmrTxProofService().maybeStartRequestTxProofProcess( - trade, processModel.getTradeManager().getTradableList()); + checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); + processModel.getTradeManager().maybeStartXmrTxProofServices((SellerTrade) trade); } processModel.removeMailboxMessageAfterProcessing(trade); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java index e3cf3ec1140..19e2ec7e1ad 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -193,8 +193,8 @@ public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler er public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { checkNotNull(getTrade(), "trade must not be null"); - checkArgument(getTrade() instanceof SellerTrade, "Check failed: trade not instanceof SellerTrade"); - ((SellerTrade) getTrade()).onFiatPaymentReceived(resultHandler, errorMessageHandler); + checkArgument(getTrade() instanceof SellerTrade, "Trade must be instance of SellerTrade"); + tradeManager.onFiatPaymentReceived((SellerTrade) getTrade(), resultHandler, errorMessageHandler); } public void onWithdrawRequest(String toAddress, @@ -705,9 +705,5 @@ public void addTradeToFailedTrades() { public boolean isSignWitnessTrade() { return accountAgeWitnessService.isSignWitnessTrade(selectedTrade); } - - public void maybeSignWitness() { - accountAgeWitnessService.maybeSignWitness(selectedTrade); - } } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 209fc83c3e8..b44063bd64b 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -412,8 +412,6 @@ private void confirmPaymentReceived() { if (!trade.isPayoutPublished()) trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_FIAT_PAYMENT_RECEIPT); - model.dataModel.maybeSignWitness(); - model.dataModel.onFiatPaymentReceived(() -> { // In case the first send failed we got the support button displayed. // If it succeeds at a second try we remove the support button again. From 46394024e9aceca32fca33d7f96cffc54a844403 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 19:15:06 -0500 Subject: [PATCH 58/85] Dont restart seed if localhost is used --- .../main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java index 592f887dbc9..1f841e66cb9 100644 --- a/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java +++ b/core/src/main/java/bisq/core/app/misc/ExecutableForAppWithP2p.java @@ -117,7 +117,7 @@ public void gracefulShutDown(ResultHandler resultHandler) { } public void startShutDownInterval(GracefulShutDownHandler gracefulShutDownHandler) { - if (DevEnv.isDevMode()) { + if (DevEnv.isDevMode() || injector.getInstance(Config.class).useLocalhostForP2P) { return; } From 80d85e68ec6a258de26d77a0b9f4668a3333b2de Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 19:15:19 -0500 Subject: [PATCH 59/85] Make constructor private --- .../java/bisq/core/user/AutoConfirmSettings.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java index 06d16f846a4..40b31596225 100644 --- a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -38,7 +38,7 @@ public final class AutoConfirmSettings implements PersistablePayload { private List serviceAddresses; private String currencyCode; - public static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { + static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { return new AutoConfirmSettings( false, 5, @@ -47,11 +47,11 @@ public static AutoConfirmSettings getDefaultForXmr(List serviceAddresses "XMR"); } - AutoConfirmSettings(boolean enabled, - int requiredConfirmations, - long tradeLimit, - List serviceAddresses, - String currencyCode) { + private AutoConfirmSettings(boolean enabled, + int requiredConfirmations, + long tradeLimit, + List serviceAddresses, + String currencyCode) { this.enabled = enabled; this.requiredConfirmations = requiredConfirmations; this.tradeLimit = tradeLimit; From 7c0d24c5673a59e8f54d8135ed067e8e98074ee0 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 19:56:48 -0500 Subject: [PATCH 60/85] Refactoring: Rename package bisq.core.trade.autoconf to bisq.core.trade.txproof --- core/src/main/java/bisq/core/trade/Trade.java | 2 +- .../java/bisq/core/trade/TradeManager.java | 4 +- .../AssetTxProofResult.java | 2 +- .../xmr/XmrTxProofHttpClient.java | 2 +- .../xmr/XmrTxProofModel.java | 2 +- .../xmr/XmrTxProofParser.java | 44 +++++++++---------- .../xmr/XmrTxProofRequest.java | 2 +- .../xmr/XmrTxProofRequestsPerTrade.java | 8 ++-- .../xmr/XmrTxProofService.java | 4 +- .../xmr/XmrTxProofParserTest.java | 2 +- .../overlays/windows/SetXmrTxKeyWindow.java | 2 +- .../overlays/windows/TradeDetailsWindow.java | 2 +- .../steps/buyer/BuyerStep4View.java | 2 +- .../steps/seller/SellerStep3View.java | 2 +- .../main/java/bisq/desktop/util/GUIUtil.java | 2 +- 15 files changed, 38 insertions(+), 44 deletions(-) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/AssetTxProofResult.java (98%) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofHttpClient.java (96%) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofModel.java (99%) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofParser.java (72%) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofRequest.java (99%) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofRequestsPerTrade.java (97%) rename core/src/main/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofService.java (98%) rename core/src/test/java/bisq/core/trade/{autoconf => txproof}/xmr/XmrTxProofParserTest.java (99%) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index deba9fcf15e..1936f57111f 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -38,11 +38,11 @@ import bisq.core.support.dispute.refund.RefundResultState; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.messages.ChatMessage; -import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.trade.protocol.ProcessModel; import bisq.core.trade.protocol.TradeProtocol; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.User; import bisq.network.p2p.DecryptedMessageWithPubKey; diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index eeb8b3e608f..b02ac42aa91 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -38,8 +38,6 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.autoconf.AssetTxProofResult; -import bisq.core.trade.autoconf.xmr.XmrTxProofService; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.handlers.TradeResultHandler; @@ -48,6 +46,8 @@ import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.trade.txproof.AssetTxProofResult; +import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.user.User; import bisq.core.util.Validator; diff --git a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java similarity index 98% rename from core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java rename to core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java index 6a489f136c2..18960f58f37 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf; +package bisq.core.trade.txproof; import lombok.Getter; diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java similarity index 96% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java rename to core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java index b9e2f9ebe34..1d63da4fffd 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf.xmr; +package bisq.core.trade.txproof.xmr; import bisq.network.Socks5ProxyProvider; import bisq.network.http.HttpClient; diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java similarity index 99% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java rename to core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java index 27a21b55b17..52c8a72c667 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf.xmr; +package bisq.core.trade.txproof.xmr; import bisq.core.monetary.Volume; import bisq.core.payment.payload.AssetsAccountPayload; diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java similarity index 72% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java rename to core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index 0d68f8cf119..c91172d9ec1 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -15,9 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf.xmr; - -import bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Result; +package bisq.core.trade.txproof.xmr; import bisq.asset.CryptoNoteUtils; @@ -33,20 +31,18 @@ import lombok.extern.slf4j.Slf4j; -import static bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Detail; - @Slf4j class XmrTxProofParser { - static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { + static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { String txHash = xmrTxProofModel.getTxHash(); try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Empty json")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Empty json")); } // there should always be "data" and "status" at the top level if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Missing data / status fields")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing data / status fields")); } JsonObject jsonData = json.get("data").getAsJsonObject(); String jsonStatus = json.get("status").getAsString(); @@ -54,42 +50,42 @@ static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // The API returns "fail" until the transaction has successfully reached the mempool or if request // contained invalid data. // We return TX_NOT_FOUND which will cause a retry later - return Result.PENDING.with(Detail.TX_NOT_FOUND); + return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.TX_NOT_FOUND); } else if (!jsonStatus.matches("success")) { - return Result.ERROR.with(Detail.API_INVALID.error("Unhandled status value")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Unhandled status value")); } // validate that the address matches JsonElement jsonAddress = jsonData.get("address"); if (jsonAddress == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Missing address field")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing address field")); } else { String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrTxProofModel.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); - return Result.FAILED.with(Detail.ADDRESS_INVALID); + return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.ADDRESS_INVALID); } } // validate that the txHash matches JsonElement jsonTxHash = jsonData.get("tx_hash"); if (jsonTxHash == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Missing tx_hash field")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_hash field")); } else { if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return Result.FAILED.with(Detail.TX_HASH_INVALID); + return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TX_HASH_INVALID); } } // validate that the txKey matches JsonElement jsonViewkey = jsonData.get("viewkey"); if (jsonViewkey == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Missing viewkey field")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing viewkey field")); } else { if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrTxProofModel.getTxKey())) { log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrTxProofModel.getTxKey()); - return Result.FAILED.with(Detail.TX_KEY_INVALID); + return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TX_KEY_INVALID); } } @@ -97,7 +93,7 @@ static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // (except that in dev mode we let this check pass anyway) JsonElement jsonTimestamp = jsonData.get("tx_timestamp"); if (jsonTimestamp == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Missing tx_timestamp field")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_timestamp field")); } else { long tradeDateSeconds = xmrTxProofModel.getTradeDate().getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); @@ -105,7 +101,7 @@ static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return Result.FAILED.with(Detail.TRADE_DATE_NOT_MATCHING); + return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); } } @@ -113,7 +109,7 @@ static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { int confirmations; JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); if (jsonConfirmations == null) { - return Result.ERROR.with(Detail.API_INVALID.error("Missing tx_confirmations field")); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_confirmations field")); } else { confirmations = jsonConfirmations.getAsInt(); log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); @@ -139,23 +135,23 @@ static Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { // None of the outputs had a match entry if (!anyMatchFound) { - return Result.FAILED.with(Detail.NO_MATCH_FOUND); + return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.NO_MATCH_FOUND); } // None of the outputs had a match entry if (!amountMatches) { - return Result.FAILED.with(Detail.AMOUNT_NOT_MATCHING); + return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); } int confirmsRequired = xmrTxProofModel.getConfirmsRequired(); if (confirmations < confirmsRequired) { - return Result.PENDING.with(Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); + return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); } else { - return Result.SUCCESS; + return XmrTxProofRequest.Result.SUCCESS; } } catch (JsonParseException | NullPointerException e) { - return Result.ERROR.with(Detail.API_INVALID.error(e.toString())); + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error(e.toString())); } } } diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java similarity index 99% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java rename to core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index f95a35d0dca..2c70459e7d8 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf.xmr; +package bisq.core.trade.txproof.xmr; import bisq.network.Socks5ProxyProvider; diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java similarity index 97% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java rename to core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 962ce390c91..8d322083ef8 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -15,11 +15,11 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf.xmr; +package bisq.core.trade.txproof.xmr; import bisq.core.locale.Res; import bisq.core.trade.Trade; -import bisq.core.trade.autoconf.AssetTxProofResult; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.AutoConfirmSettings; import bisq.network.Socks5ProxyProvider; @@ -37,8 +37,6 @@ import lombok.extern.slf4j.Slf4j; -import static bisq.core.trade.autoconf.xmr.XmrTxProofRequest.Result; - /** * Handles the XMR tx proof requests for multiple services per trade. */ @@ -163,7 +161,7 @@ private void callResultHandlerAndMaybeTerminate(Consumer res } } - private AssetTxProofResult getAssetTxProofResultForPending(Result result) { + private AssetTxProofResult getAssetTxProofResultForPending(XmrTxProofRequest.Result result) { XmrTxProofRequest.Detail detail = result.getDetail(); int numConfirmations = detail != null ? detail.getNumConfirmations() : 0; log.info("{} returned with numConfirmations {}", diff --git a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java similarity index 98% rename from core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java rename to core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 62438468a12..0ada14a70bc 100644 --- a/core/src/main/java/bisq/core/trade/autoconf/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.core.trade.autoconf.xmr; +package bisq.core.trade.txproof.xmr; import bisq.core.btc.setup.WalletsSetup; import bisq.core.filter.FilterManager; @@ -23,9 +23,9 @@ import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.trade.SellerTrade; import bisq.core.trade.Trade; -import bisq.core.trade.autoconf.AssetTxProofResult; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.AutoConfirmSettings; import bisq.core.user.Preferences; diff --git a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java similarity index 99% rename from core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java rename to core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java index cbd34f2be9e..947f9177682 100644 --- a/core/src/test/java/bisq/core/trade/autoconf/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java @@ -1,4 +1,4 @@ -package bisq.core.trade.autoconf.xmr; +package bisq.core.trade.txproof.xmr; import java.time.Instant; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index 518b5da8d59..5d184acd7d4 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -22,7 +22,7 @@ import bisq.desktop.util.validation.RegexValidator; import bisq.core.locale.Res; -import bisq.core.trade.autoconf.xmr.XmrTxProofModel; +import bisq.core.trade.txproof.xmr.XmrTxProofModel; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java index 0104b2a4611..538c1f66d5f 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -34,7 +34,7 @@ import bisq.core.trade.Contract; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; -import bisq.core.trade.autoconf.AssetTxProofResult; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.util.FormattingUtils; import bisq.core.util.coin.CoinFormatter; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 785eb2bd9fb..b0d6295cfc2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -37,7 +37,7 @@ import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.Restrictions; import bisq.core.locale.Res; -import bisq.core.trade.autoconf.AssetTxProofResult; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinUtil; diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index b44063bd64b..8093a9c3772 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -43,7 +43,7 @@ import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.trade.Contract; import bisq.core.trade.Trade; -import bisq.core.trade.autoconf.AssetTxProofResult; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.common.Timer; diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 8879495a5b7..9c54aa13b17 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -46,7 +46,7 @@ import bisq.core.provider.fee.FeeService; import bisq.core.provider.price.MarketPrice; import bisq.core.provider.price.PriceFeedService; -import bisq.core.trade.autoconf.AssetTxProofResult; +import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.DontShowAgainLookup; import bisq.core.user.Preferences; import bisq.core.user.User; From 91cc14725cbfe58ec22652835deef2727848319a Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 20:03:32 -0500 Subject: [PATCH 61/85] Add annotation --- core/src/main/resources/i18n/displayStrings.properties | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index f0a41839077..5471624bc72 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -573,19 +573,27 @@ portfolio.pending.step5.completed=Completed portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status portfolio.pending.autoConf=Auto-confirmed - portfolio.pending.autoConf.state.xmr.txKeyReused=The peer re-used a transaction key. This might be a scam attempt. Open a dispute. portfolio.pending.autoConf.state.confirmations=Confirmations: {0}; Required: {1} portfolio.pending.autoConf.state.txNotFound=Transaction not confirmed yet +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabled +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.TRADE_LIMIT_EXCEEDED=Trade amount exceeds auto-confirm amount limit +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data: {0} +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published. +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.PENDING={0} service(s) out of {1} have succeeded. {2} +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.ERROR=An error at a service request occurred. +# suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.FAILED=A service returned with a failure. portfolio.pending.step1.info=Deposit transaction has been published.\n{0} need to wait for at least one blockchain confirmation before starting the payment. From 67aa46a03b0240ce046d9f2b34d8291b8278eb3f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 20:07:12 -0500 Subject: [PATCH 62/85] - Fix incorrect isTerminal default value. - Apply suggestions from code inspection --- .../java/bisq/core/trade/txproof/AssetTxProofResult.java | 2 +- .../java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java | 1 + .../java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java | 1 + .../java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java | 5 +---- .../java/bisq/core/trade/txproof/xmr/XmrTxProofService.java | 1 + 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java index 18960f58f37..0fc61d7cfd0 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofResult.java @@ -50,7 +50,7 @@ public enum AssetTxProofResult { private final boolean isTerminal; AssetTxProofResult() { - this(false); + this(true); } AssetTxProofResult(boolean isTerminal) { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java index 52c8a72c667..781cbad80f4 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java @@ -35,6 +35,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +@SuppressWarnings("SpellCheckingInspection") @Slf4j @Value public class XmrTxProofModel { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index c91172d9ec1..b2ab2884252 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -33,6 +33,7 @@ @Slf4j class XmrTxProofParser { + @SuppressWarnings("SpellCheckingInspection") static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { String txHash = xmrTxProofModel.getTxHash(); try { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index 2c70459e7d8..b33940d4894 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -164,6 +164,7 @@ public String toString() { // API /////////////////////////////////////////////////////////////////////////////////////////// + @SuppressWarnings("SpellCheckingInspection") public void start(Consumer resultHandler, FaultHandler faultHandler) { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls @@ -239,10 +240,6 @@ void terminate() { terminated = true; } - String getServiceAddress() { - return xmrTxProofModel.getServiceAddress(); - } - // Convenient for logging @Override public String toString() { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 0ada14a70bc..39f4019e8a2 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -71,6 +71,7 @@ public class XmrTxProofService { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// + @SuppressWarnings("WeakerAccess") @Inject public XmrTxProofService(FilterManager filterManager, Preferences preferences, From 7240bd62d1715b9026519f0247da2b84acc5a664 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 21:22:16 -0500 Subject: [PATCH 63/85] Read numRequiredConfirmations from autoConfirmSettings just in time. This allows the user to change required confirmation and it has impact on open requests. Will be read at parsing the result. Changed also the text field to not listen on text change but on focus out so that the values are only updated once the user has left the field and the value is valid. Otherwise small numbers like 1 might be written during typing. The other values are only read at request start and changes will have no impact on already started requests. Also applies previous value in case the user added an invalid value at focus out. - Made display text shorter and added a tooltip. - Allow very high numbers in devmode as max conf value --- .../trade/txproof/xmr/XmrTxProofModel.java | 24 ++++++---- .../trade/txproof/xmr/XmrTxProofParser.java | 2 +- .../xmr/XmrTxProofRequestsPerTrade.java | 26 +++++----- .../bisq/core/user/AutoConfirmSettings.java | 12 ++--- .../resources/i18n/displayStrings.properties | 6 +-- .../txproof/xmr/XmrTxProofParserTest.java | 13 +++-- .../steps/seller/SellerStep3View.java | 10 +++- .../settings/preferences/PreferencesView.java | 47 +++++++++++++------ 8 files changed, 89 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java index 781cbad80f4..5751427dc95 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java @@ -21,6 +21,7 @@ import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.Trade; +import bisq.core.user.AutoConfirmSettings; import bisq.common.app.DevEnv; @@ -45,18 +46,19 @@ public class XmrTxProofModel { public static final String DEV_TX_HASH = "5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802"; public static final long DEV_AMOUNT = 8902597360000L; + private final String serviceAddress; + private final AutoConfirmSettings autoConfirmSettings; private final String tradeId; private final String txHash; private final String txKey; private final String recipientAddress; private final long amount; private final Date tradeDate; - private final int confirmsRequired; - private final String serviceAddress; - XmrTxProofModel(Trade trade, String serviceAddress, int confirmsRequired) { + XmrTxProofModel(Trade trade, String serviceAddress, AutoConfirmSettings autoConfirmSettings) { this.serviceAddress = serviceAddress; - this.confirmsRequired = confirmsRequired; + this.autoConfirmSettings = autoConfirmSettings; + Coin tradeAmount = trade.getTradeAmount(); Volume volume = checkNotNull(trade.getOffer()).getVolumeByAmount(tradeAmount); amount = DevEnv.isDevMode() ? @@ -72,6 +74,12 @@ public class XmrTxProofModel { tradeId = trade.getId(); } + // NumRequiredConfirmations is read just in time. If user changes autoConfirmSettings during requests it will + // be reflected at next result parsing. + int getNumRequiredConfirmations() { + return autoConfirmSettings.getRequiredConfirmations(); + } + // Used only for testing @VisibleForTesting XmrTxProofModel(String tradeId, @@ -80,16 +88,14 @@ public class XmrTxProofModel { String recipientAddress, long amount, Date tradeDate, - int confirmsRequired, - String serviceAddress) { - + AutoConfirmSettings autoConfirmSettings) { this.tradeId = tradeId; this.txHash = txHash; this.txKey = txKey; this.recipientAddress = recipientAddress; this.amount = amount; this.tradeDate = tradeDate; - this.confirmsRequired = confirmsRequired; - this.serviceAddress = serviceAddress; + this.autoConfirmSettings = autoConfirmSettings; + this.serviceAddress = autoConfirmSettings.getServiceAddresses().get(0); } } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index b2ab2884252..594b34166c0 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -144,7 +144,7 @@ static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String js return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); } - int confirmsRequired = xmrTxProofModel.getConfirmsRequired(); + int confirmsRequired = xmrTxProofModel.getNumRequiredConfirmations(); if (confirmations < confirmsRequired) { return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); } else { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 8d322083ef8..b7406ab6d55 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -44,28 +44,30 @@ class XmrTxProofRequestsPerTrade { private final Socks5ProxyProvider socks5ProxyProvider; private final Trade trade; - private final List serviceAddresses; - private final int numRequiredConfirmations; - private final long tradeLimit; - private final int numRequiredSuccessResults; + private final AutoConfirmSettings autoConfirmSettings; + private int numRequiredSuccessResults; private final Set requests = new HashSet<>(); private int numSuccessResults; private ChangeListener listener; + private List serviceAddresses; XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, Trade trade, AutoConfirmSettings autoConfirmSettings) { this.socks5ProxyProvider = socks5ProxyProvider; this.trade = trade; - serviceAddresses = autoConfirmSettings.getServiceAddresses(); - numRequiredConfirmations = autoConfirmSettings.getRequiredConfirmations(); - tradeLimit = autoConfirmSettings.getTradeLimit(); - - numRequiredSuccessResults = serviceAddresses.size(); + this.autoConfirmSettings = autoConfirmSettings; } void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler) { + // We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started + // it will have no impact on serviceAddresses and numRequiredSuccessResults. + // Thought numRequiredConfirmations can be changed during request process and will be read from + // autoConfirmSettings at result parsing. + serviceAddresses = autoConfirmSettings.getServiceAddresses(); + numRequiredSuccessResults = serviceAddresses.size(); + listener = (observable, oldValue, newValue) -> { if (trade.isPayoutPublished()) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); @@ -94,7 +96,7 @@ void requestFromAllServices(Consumer resultHandler, FaultHan callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED); for (String serviceAddress : serviceAddresses) { - XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, numRequiredSuccessResults); + XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings); XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); log.info("{} created", request); @@ -170,7 +172,7 @@ private AssetTxProofResult getAssetTxProofResultForPending(XmrTxProofRequest.Res String detailString = ""; if (XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS == detail) { detailString = Res.get("portfolio.pending.autoConf.state.confirmations", - numConfirmations, numRequiredConfirmations); + numConfirmations, autoConfirmSettings.getRequiredConfirmations()); } else if (XmrTxProofRequest.Detail.TX_NOT_FOUND == detail) { detailString = Res.get("portfolio.pending.autoConf.state.txNotFound"); @@ -204,7 +206,7 @@ private boolean is32BitHexStringInValid(String hexString) { private boolean isTradeAmountAboveLimit(Trade trade) { Coin tradeAmount = trade.getTradeAmount(); - Coin tradeLimit = Coin.valueOf(this.tradeLimit); + Coin tradeLimit = Coin.valueOf(autoConfirmSettings.getTradeLimit()); if (tradeAmount != null && tradeAmount.isGreaterThan(tradeLimit)) { log.warn("Trade amount {} is higher than limit from auto-conf setting {}.", tradeAmount.toFriendlyString(), tradeLimit.toFriendlyString()); diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java index 40b31596225..24b43b50956 100644 --- a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -38,7 +38,7 @@ public final class AutoConfirmSettings implements PersistablePayload { private List serviceAddresses; private String currencyCode; - static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { + public static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { return new AutoConfirmSettings( false, 5, @@ -47,11 +47,11 @@ static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { "XMR"); } - private AutoConfirmSettings(boolean enabled, - int requiredConfirmations, - long tradeLimit, - List serviceAddresses, - String currencyCode) { + public AutoConfirmSettings(boolean enabled, + int requiredConfirmations, + long tradeLimit, + List serviceAddresses, + String currencyCode) { this.enabled = enabled; this.requiredConfirmations = requiredConfirmations; this.tradeLimit = tradeLimit; diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 5471624bc72..acc82433f26 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -573,8 +573,8 @@ portfolio.pending.step5.completed=Completed portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status portfolio.pending.autoConf=Auto-confirmed -portfolio.pending.autoConf.state.xmr.txKeyReused=The peer re-used a transaction key. This might be a scam attempt. Open a dispute. -portfolio.pending.autoConf.state.confirmations=Confirmations: {0}; Required: {1} +portfolio.pending.autoConf.state.xmr.txKeyReused=The peer re-used a transaction key. Please open a dispute. +portfolio.pending.autoConf.state.confirmations=Confirmations: {0}/{1} portfolio.pending.autoConf.state.txNotFound=Transaction not confirmed yet # suppress inspection "UnusedProperty" @@ -588,7 +588,7 @@ portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.PENDING={0} service(s) out of {1} have succeeded. {2} +portfolio.pending.autoConf.state.PENDING=Service results: {0}/{1} / {2} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded # suppress inspection "UnusedProperty" diff --git a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java index 947f9177682..bf0b7fb75d4 100644 --- a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java @@ -1,7 +1,10 @@ package bisq.core.trade.txproof.xmr; +import bisq.core.user.AutoConfirmSettings; + import java.time.Instant; +import java.util.Collections; import java.util.Date; import org.junit.Before; @@ -20,9 +23,14 @@ public class XmrTxProofParserTest { public void prepareMocksAndObjects() { long amount = 100000000000L; Date tradeDate = Date.from(Instant.now()); - int confirmsRequired = 10; String serviceAddress = "127.0.0.1:8081"; + AutoConfirmSettings autoConfirmSettings = new AutoConfirmSettings(true, + 10, + 1, + Collections.singletonList(serviceAddress), + "XMR"); + // TODO using the mocking framework would be better... xmrTxProofModel = new XmrTxProofModel( "dummyTest", txHash, @@ -30,8 +38,7 @@ public void prepareMocksAndObjects() { recipientAddress, amount, tradeDate, - confirmsRequired, - serviceAddress); + autoConfirmSettings); } @Test diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 8093a9c3772..6faa8d5d1b9 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -89,7 +89,7 @@ public SellerStep3View(PendingTradesViewModel model) { super(model); proofResultListener = (observable, oldValue, newValue) -> { - assetTxProofResultField.setText(GUIUtil.getProofResultAsString(newValue)); + applyAssetTxProofResult(newValue); }; } @@ -154,7 +154,7 @@ public void activate() { // we listen for updates on the trade autoConfirmResult field if (assetTxProofResultField != null) { trade.getAssetTxProofResultProperty().addListener(proofResultListener); - assetTxProofResultField.setText(GUIUtil.getProofResultAsString(trade.getAssetTxProofResult())); + applyAssetTxProofResult(trade.getAssetTxProofResult()); } } @@ -442,6 +442,12 @@ else if (paymentAccountPayload instanceof SepaInstantAccountPayload) } } + private void applyAssetTxProofResult(AssetTxProofResult result) { + String txt = GUIUtil.getProofResultAsString(result); + assetTxProofResultField.setText(txt); + assetTxProofResultField.setTooltip(new Tooltip(txt)); + } + @Override protected void deactivatePaymentButtons(boolean isDisabled) { confirmButton.setDisable(isDisabled); diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 58a39ad6503..17256e7467c 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -122,7 +122,7 @@ public class PreferencesView extends ActivatableViewAndModel transactionFeeFocusedListener; - private ChangeListener autoConfFocusOutListener; + private ChangeListener autoConfServiceAddressFocusOutListener, autoConfRequiredConfirmationsFocusOutListener; private final Preferences preferences; private final FeeService feeService; //private final ReferralIdService referralIdService; @@ -147,8 +147,8 @@ public class PreferencesView extends ActivatableViewAndModel tradeCurrencies; private InputTextField deviationInputTextField; private ChangeListener deviationListener, ignoreTradersListListener, ignoreDustThresholdListener, - /*referralIdListener,*/ rpcUserListener, rpcPwListener, blockNotifyPortListener, - autoConfRequiredConfirmationsListener, autoConfTradeLimitListener, autoConfServiceAddressListener; + rpcUserListener, rpcPwListener, blockNotifyPortListener, + autoConfTradeLimitListener, autoConfServiceAddressListener; private ChangeListener deviationFocusedListener; private ChangeListener useCustomFeeCheckboxListener; private ChangeListener transactionFeeChangeListener; @@ -659,7 +659,7 @@ private void initializeAutoConfirmOptions() { autoConfirmXmrToggle = addSlideToggleButton(subGrid, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); autoConfRequiredConfirmationsTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); - autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(0, 1000)); + autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(0, DevEnv.isDevMode() ? 100000000 : 1000)); autoConfTradeLimitTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); autoConfTradeLimitTf.setValidator(new BtcValidator(formatter)); @@ -681,12 +681,6 @@ private void initializeAutoConfirmOptions() { } }; - autoConfRequiredConfirmationsListener = (observable, oldValue, newValue) -> { - if (!newValue.equals(oldValue) && autoConfRequiredConfirmationsTf.getValidator().validate(newValue).isValid) { - int requiredConfirmations = Integer.parseInt(newValue); - preferences.setAutoConfRequiredConfirmations("XMR", requiredConfirmations); - } - }; autoConfTradeLimitListener = (observable, oldValue, newValue) -> { if (!newValue.equals(oldValue) && autoConfTradeLimitTf.getValidator().validate(newValue).isValid) { Coin amountAsCoin = ParsingUtils.parseToCoin(newValue, formatter); @@ -694,7 +688,7 @@ private void initializeAutoConfirmOptions() { } }; - autoConfFocusOutListener = (observable, oldValue, newValue) -> { + autoConfServiceAddressFocusOutListener = (observable, oldValue, newValue) -> { if (oldValue && !newValue) { log.info("Service address focus out, check and re-display default option"); if (autoConfServiceAddressTf.getText().isEmpty()) { @@ -705,6 +699,28 @@ private void initializeAutoConfirmOptions() { } } }; + + // We use a focus out handler to not update the data during entering text as that might lead to lower than + // intended numbers which could be lead in the worst case to auto completion as number of confirmations is + // reached. E.g. user had value 10 and wants to change it to 15 and deletes the 0, so current value would be 1. + // If the service result just comes in at that moment the service might be considered complete as 1 is at that + // moment used. We read the data just in time to make changes more flexible, otherwise user would need to + // restart to apply changes from the number of confirmations settings. + // Other fields like service addresses and limits are not affected and are taken at service start and cannot be + // changed for already started services. + autoConfRequiredConfirmationsFocusOutListener = (observable, oldValue, newValue) -> { + if (oldValue && !newValue) { + String txt = autoConfRequiredConfirmationsTf.getText(); + if (autoConfRequiredConfirmationsTf.getValidator().validate(txt).isValid) { + int requiredConfirmations = Integer.parseInt(txt); + preferences.setAutoConfRequiredConfirmations("XMR", requiredConfirmations); + } else { + preferences.findAutoConfirmSettings("XMR") + .ifPresent(e -> autoConfRequiredConfirmationsTf + .setText(String.valueOf(e.getRequiredConfirmations()))); + } + } + }; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -965,10 +981,10 @@ private void activateAutoConfirmPreferences() { autoConfTradeLimitTf.setText(formatter.formatCoin(Coin.valueOf(autoConfirmSettings.getTradeLimit()))); autoConfServiceAddressTf.setText(String.join(", ", autoConfirmSettings.getServiceAddresses())); - autoConfRequiredConfirmationsTf.textProperty().addListener(autoConfRequiredConfirmationsListener); + autoConfRequiredConfirmationsTf.focusedProperty().addListener(autoConfRequiredConfirmationsFocusOutListener); autoConfTradeLimitTf.textProperty().addListener(autoConfTradeLimitListener); autoConfServiceAddressTf.textProperty().addListener(autoConfServiceAddressListener); - autoConfServiceAddressTf.focusedProperty().addListener(autoConfFocusOutListener); + autoConfServiceAddressTf.focusedProperty().addListener(autoConfServiceAddressFocusOutListener); autoConfirmXmrToggle.setOnAction(e -> { preferences.setAutoConfEnabled(autoConfirmSettings.getCurrencyCode(), autoConfirmXmrToggle.isSelected()); @@ -1047,9 +1063,10 @@ private void deactivateDaoPreferences() { private void deactivateAutoConfirmPreferences() { autoConfirmXmrToggle.setOnAction(null); - autoConfRequiredConfirmationsTf.textProperty().removeListener(autoConfRequiredConfirmationsListener); autoConfTradeLimitTf.textProperty().removeListener(autoConfTradeLimitListener); autoConfServiceAddressTf.textProperty().removeListener(autoConfServiceAddressListener); - autoConfServiceAddressTf.focusedProperty().removeListener(autoConfFocusOutListener); + autoConfServiceAddressTf.focusedProperty().removeListener(autoConfServiceAddressFocusOutListener); + autoConfRequiredConfirmationsTf.focusedProperty().removeListener(autoConfRequiredConfirmationsFocusOutListener); + } } From aa802a3e0e2ed57c09c9d95d881533b4ad777cdc Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 21:35:44 -0500 Subject: [PATCH 64/85] Fix incorrect init of AutoConfirmSettings --- core/src/main/java/bisq/core/user/Preferences.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/user/Preferences.java b/core/src/main/java/bisq/core/user/Preferences.java index 597e0c95e62..d267cfa604e 100644 --- a/core/src/main/java/bisq/core/user/Preferences.java +++ b/core/src/main/java/bisq/core/user/Preferences.java @@ -321,7 +321,7 @@ public void readPersisted() { } if (prefPayload.getAutoConfirmSettingsList().isEmpty()) { - prefPayload.getAutoConfirmSettingsList().add(AutoConfirmSettings.getDefaultForXmr(getDefaultXmrProofProviders())); + getAutoConfirmSettingsList().add(AutoConfirmSettings.getDefaultForXmr(getDefaultXmrProofProviders())); } // We set the capability in CoreNetworkCapabilities if the program argument is set. From 70a0771af68cb6b7772a0cc45e9e26fd3372fb3c Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 21:39:55 -0500 Subject: [PATCH 65/85] Code style: Improve order of fields --- core/src/main/java/bisq/core/filter/Filter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/filter/Filter.java b/core/src/main/java/bisq/core/filter/Filter.java index d46c88862eb..a22ecda78af 100644 --- a/core/src/main/java/bisq/core/filter/Filter.java +++ b/core/src/main/java/bisq/core/filter/Filter.java @@ -240,13 +240,13 @@ public Filter(List bannedOfferIds, this.refundAgents = refundAgents; this.bannedAccountWitnessSignerPubKeys = bannedAccountWitnessSignerPubKeys; this.btcFeeReceiverAddresses = btcFeeReceiverAddresses; - this.disableAutoConf = disableAutoConf; this.ownerPubKeyBytes = ownerPubKeyBytes; this.creationDate = creationDate; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.signatureAsBase64 = signatureAsBase64; this.signerPubKeyAsHex = signerPubKeyAsHex; this.bannedPrivilegedDevPubKeys = bannedPrivilegedDevPubKeys; + this.disableAutoConf = disableAutoConf; // ownerPubKeyBytes can be null when called from tests if (ownerPubKeyBytes != null) { @@ -358,9 +358,9 @@ public String toString() { ",\n bannedAccountWitnessSignerPubKeys=" + bannedAccountWitnessSignerPubKeys + ",\n bannedPrivilegedDevPubKeys=" + bannedPrivilegedDevPubKeys + ",\n btcFeeReceiverAddresses=" + btcFeeReceiverAddresses + - ",\n disableAutoConf=" + disableAutoConf + ",\n creationDate=" + creationDate + ",\n extraDataMap=" + extraDataMap + + ",\n disableAutoConf=" + disableAutoConf + "\n}"; } } From 320179f5ec871871ddfb3f393d6ca3cdc87fb575 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Tue, 1 Sep 2020 22:29:17 -0500 Subject: [PATCH 66/85] Improve handling of invalid/empty tx hash/tx key --- .../java/bisq/core/trade/TradeManager.java | 4 +-- ...CounterCurrencyTransferStartedMessage.java | 10 +++--- .../xmr/XmrTxProofRequestsPerTrade.java | 16 ---------- .../trade/txproof/xmr/XmrTxProofService.java | 31 ++++++++++++++----- .../resources/i18n/displayStrings.properties | 3 +- .../overlays/windows/SetXmrTxKeyWindow.java | 16 ++++++++++ .../steps/buyer/BuyerStep2View.java | 15 --------- 7 files changed, 50 insertions(+), 45 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index b02ac42aa91..8d3c2d738ca 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -330,8 +330,8 @@ private void initPendingTrades() { } } - if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG && - trade.getCounterCurrencyExtraData() != null) { + if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) { + // This state can be only appear at a SellerTrade checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); // We delay a bit as at startup lots of stuff is happening UserThread.runAfter(() -> maybeStartXmrTxProofServices((SellerTrade) trade), 1); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java index 8d25bb1cb3f..29ca5bc63ea 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/seller/SellerProcessCounterCurrencyTransferStartedMessage.java @@ -58,12 +58,14 @@ protected void run() { } String counterCurrencyExtraData = message.getCounterCurrencyExtraData(); - if (counterCurrencyExtraData != null && - counterCurrencyExtraData.length() < 100) { + if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) { trade.setCounterCurrencyExtraData(counterCurrencyExtraData); - checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); - processModel.getTradeManager().maybeStartXmrTxProofServices((SellerTrade) trade); } + + checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade"); + // We return early in the service if its not XMR. We prefer to not have additional checks outside... + processModel.getTradeManager().maybeStartXmrTxProofServices((SellerTrade) trade); + processModel.removeMailboxMessageAfterProcessing(trade); trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index b7406ab6d55..284faaade30 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -73,13 +73,6 @@ void requestFromAllServices(Consumer resultHandler, FaultHan callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); } }; - String txId = trade.getCounterCurrencyTxId(); - String txHash = trade.getCounterCurrencyExtraData(); - if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { - - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.INVALID_DATA); - return; - } if (isTradeAmountAboveLimit(trade)) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED); @@ -195,15 +188,6 @@ void terminate() { // Validation /////////////////////////////////////////////////////////////////////////////////////////// - private boolean is32BitHexStringInValid(String hexString) { - if (hexString == null || hexString.isEmpty() || !hexString.matches("[a-fA-F0-9]{64}")) { - log.warn("Invalid hexString: {}", hexString); - return true; - } - - return false; - } - private boolean isTradeAmountAboveLimit(Trade trade) { Coin tradeAmount = trade.getTradeAmount(); Coin tradeLimit = Coin.valueOf(autoConfirmSettings.getTradeLimit()); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 39f4019e8a2..7536829de6b 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -98,22 +98,30 @@ public void maybeStartRequests(Trade trade, List activeTrades, Consumer resultHandler, FaultHandler faultHandler) { - Optional optionalAutoConfirmSettings = preferences.findAutoConfirmSettings("XMR"); - if (!optionalAutoConfirmSettings.isPresent()) { - log.error("autoConfirmSettings is not present"); + if (!isXmrBuyer(trade)) { return; } - AutoConfirmSettings autoConfirmSettings = optionalAutoConfirmSettings.get(); - if (!isXmrBuyer(trade)) { + String txId = trade.getCounterCurrencyTxId(); + String txHash = trade.getCounterCurrencyExtraData(); + if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { + trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.txKeyOrTxIdInvalid"))); return; } - if (!isFeatureEnabled(trade, autoConfirmSettings)) { + if (!networkAndWalletReady()) { return; } - if (!networkAndWalletReady()) { + Optional optionalAutoConfirmSettings = preferences.findAutoConfirmSettings("XMR"); + if (!optionalAutoConfirmSettings.isPresent()) { + // Not expected + log.error("autoConfirmSettings is not present"); + return; + } + AutoConfirmSettings autoConfirmSettings = optionalAutoConfirmSettings.get(); + + if (!isFeatureEnabled(trade, autoConfirmSettings)) { return; } @@ -164,6 +172,15 @@ private boolean isXmrBuyer(Trade trade) { return checkNotNull(trade.getContract()).getSellerPaymentAccountPayload() instanceof AssetsAccountPayload; } + private boolean is32BitHexStringInValid(String hexString) { + if (hexString == null || hexString.isEmpty() || !hexString.matches("[a-fA-F0-9]{64}")) { + log.warn("Invalid hexString: {}", hexString); + return true; + } + + return false; + } + private boolean networkAndWalletReady() { return p2PService.isBootstrapped() && walletsSetup.isDownloadComplete() && diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index acc82433f26..4f736c8623a 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -573,9 +573,10 @@ portfolio.pending.step5.completed=Completed portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status portfolio.pending.autoConf=Auto-confirmed -portfolio.pending.autoConf.state.xmr.txKeyReused=The peer re-used a transaction key. Please open a dispute. +portfolio.pending.autoConf.state.xmr.txKeyReused=Transaction key re-used. Please open a dispute. portfolio.pending.autoConf.state.confirmations=Confirmations: {0}/{1} portfolio.pending.autoConf.state.txNotFound=Transaction not confirmed yet +portfolio.pending.autoConf.state.txKeyOrTxIdInvalid=No valid transaction ID / transaction key # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabled diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index 5d184acd7d4..ab119d8bbb7 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -37,6 +37,7 @@ import static bisq.common.app.DevEnv.isDevMode; import static bisq.desktop.util.FormBuilder.addInputTextField; +import static javafx.beans.binding.Bindings.createBooleanBinding; public class SetXmrTxKeyWindow extends Overlay { @@ -69,6 +70,21 @@ public void show() { txKeyInputTextField.setText(XmrTxProofModel.DEV_TX_KEY); } + actionButton.disableProperty().bind(createBooleanBinding(() -> { + String txHash = txHashInputTextField.getText(); + String txKey = txKeyInputTextField.getText(); + + // If a field is empty we allow to continue. We do not enforce that users send the data. + if (txHash.isEmpty() || txKey.isEmpty()) { + return false; + } + + // Otherwise we require that input is valid + return !txHashInputTextField.getValidator().validate(txHash).isValid || + !txKeyInputTextField.getValidator().validate(txKey).isValid; + }, + txHashInputTextField.textProperty(), txKeyInputTextField.textProperty())); + applyStyles(); display(); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index 2f09cf258a9..562c716ebec 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -74,7 +74,6 @@ import bisq.core.trade.DelayedPayoutTxValidation; import bisq.core.trade.Trade; import bisq.core.user.DontShowAgainLookup; -import bisq.core.util.validation.InputValidator; import bisq.common.Timer; import bisq.common.UserThread; @@ -470,20 +469,6 @@ private void onPaymentStarted() { return; } - InputValidator.ValidationResult validateTxKey = setXmrTxKeyWindow.getRegexValidator().validate(txKey); - if (!validateTxKey.isValid) { - UserThread.runAfter(() -> new Popup().warning(validateTxKey.errorMessage).show(), - Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); - return; - } - - InputValidator.ValidationResult validateTxHash = setXmrTxKeyWindow.getRegexValidator().validate(txHash); - if (!validateTxHash.isValid) { - UserThread.runAfter(() -> new Popup().warning(validateTxHash.errorMessage).show(), - Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS); - return; - } - trade.setCounterCurrencyExtraData(txKey); trade.setCounterCurrencyTxId(txHash); showConfirmPaymentStartedPopup(); From a8968da23ffda481bfda2a5da10f540c50762d2b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 00:00:23 -0500 Subject: [PATCH 67/85] Add listener on autoConfirmSettings changes and terminate all services if feature got disabled in setting. --- .../xmr/XmrTxProofRequestsPerTrade.java | 37 +++++++++++---- .../trade/txproof/xmr/XmrTxProofService.java | 15 ++---- .../bisq/core/user/AutoConfirmSettings.java | 47 +++++++++++++++++-- .../resources/i18n/displayStrings.properties | 7 +-- .../overlays/windows/SetXmrTxKeyWindow.java | 2 +- .../main/java/bisq/desktop/util/GUIUtil.java | 1 + 6 files changed, 83 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 284faaade30..a4bceeddf77 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -24,6 +24,7 @@ import bisq.network.Socks5ProxyProvider; +import bisq.common.UserThread; import bisq.common.handlers.FaultHandler; import org.bitcoinj.core.Coin; @@ -45,12 +46,14 @@ class XmrTxProofRequestsPerTrade { private final Socks5ProxyProvider socks5ProxyProvider; private final Trade trade; private final AutoConfirmSettings autoConfirmSettings; + private int numRequiredSuccessResults; private final Set requests = new HashSet<>(); private int numSuccessResults; - private ChangeListener listener; + private ChangeListener tradeStateListener; private List serviceAddresses; + private AutoConfirmSettings.Listener autoConfirmSettingsListener; XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, Trade trade, @@ -68,12 +71,6 @@ void requestFromAllServices(Consumer resultHandler, FaultHan serviceAddresses = autoConfirmSettings.getServiceAddresses(); numRequiredSuccessResults = serviceAddresses.size(); - listener = (observable, oldValue, newValue) -> { - if (trade.isPayoutPublished()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); - } - }; - if (isTradeAmountAboveLimit(trade)) { callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED); return; @@ -84,7 +81,24 @@ void requestFromAllServices(Consumer resultHandler, FaultHan return; } - trade.stateProperty().addListener(listener); + // We will stop all our services if the user changes the enable state in the AutoConfirmSettings + autoConfirmSettingsListener = () -> { + if (!autoConfirmSettings.isEnabled()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); + } + }; + autoConfirmSettings.addListener(autoConfirmSettingsListener); + if (!autoConfirmSettings.isEnabled()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); + return; + } + + tradeStateListener = (observable, oldValue, newValue) -> { + if (trade.isPayoutPublished()) { + callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); + } + }; + trade.stateProperty().addListener(tradeStateListener); callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED); @@ -180,7 +194,12 @@ private AssetTxProofResult getAssetTxProofResultForPending(XmrTxProofRequest.Res void terminate() { requests.forEach(XmrTxProofRequest::terminate); requests.clear(); - trade.stateProperty().removeListener(listener); + if (tradeStateListener != null) { + UserThread.execute(() -> trade.stateProperty().removeListener(tradeStateListener)); + } + if (autoConfirmSettingsListener != null) { + UserThread.execute(() -> autoConfirmSettings.removeListener(autoConfirmSettingsListener)); + } } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 7536829de6b..576fcc076e9 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -121,11 +121,15 @@ public void maybeStartRequests(Trade trade, } AutoConfirmSettings autoConfirmSettings = optionalAutoConfirmSettings.get(); - if (!isFeatureEnabled(trade, autoConfirmSettings)) { + if (isAutoConfDisabledByFilter()) { + trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED + .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature"))); return; } if (wasTxKeyReUsed(trade, activeTrades)) { + trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA + .details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused"))); return; } @@ -187,14 +191,6 @@ private boolean networkAndWalletReady() { walletsSetup.hasSufficientPeersForBroadcast(); } - private boolean isFeatureEnabled(Trade trade, AutoConfirmSettings autoConfirmSettings) { - boolean isEnabled = checkNotNull(autoConfirmSettings).isEnabled() && !isAutoConfDisabledByFilter(); - if (!isEnabled) { - trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED); - } - return isEnabled; - } - private boolean isAutoConfDisabledByFilter() { return filterManager.getFilter() != null && filterManager.getFilter().isDisableAutoConf(); @@ -227,7 +223,6 @@ private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { if (alreadyUsed) { log.warn("Peer used the XMR tx key already at another trade with trade ID {}. " + "This might be a scam attempt.", t.getId()); - trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused"))); } return alreadyUsed; }); diff --git a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java index 24b43b50956..81fb2a57058 100644 --- a/core/src/main/java/bisq/core/user/AutoConfirmSettings.java +++ b/core/src/main/java/bisq/core/user/AutoConfirmSettings.java @@ -25,20 +25,24 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import lombok.Getter; -import lombok.Setter; @Getter -@Setter public final class AutoConfirmSettings implements PersistablePayload { + public interface Listener { + void onChange(); + } + private boolean enabled; private int requiredConfirmations; private long tradeLimit; private List serviceAddresses; private String currencyCode; + private List listeners = new CopyOnWriteArrayList<>(); - public static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { + static AutoConfirmSettings getDefaultForXmr(List serviceAddresses) { return new AutoConfirmSettings( false, 5, @@ -85,4 +89,41 @@ public static AutoConfirmSettings fromProto(protobuf.AutoConfirmSettings proto) serviceAddresses, proto.getCurrencyCode()); } + + public void addListener(Listener listener) { + listeners.add(listener); + } + + public void removeListener(Listener listener) { + listeners.remove(listener); + } + + private void notifyListeners() { + listeners.forEach(Listener::onChange); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + notifyListeners(); + } + + public void setRequiredConfirmations(int requiredConfirmations) { + this.requiredConfirmations = requiredConfirmations; + notifyListeners(); + } + + public void setTradeLimit(long tradeLimit) { + this.tradeLimit = tradeLimit; + notifyListeners(); + } + + public void setServiceAddresses(List serviceAddresses) { + this.serviceAddresses = serviceAddresses; + notifyListeners(); + } + + public void setCurrencyCode(String currencyCode) { + this.currencyCode = currencyCode; + notifyListeners(); + } } diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 4f736c8623a..44ff1f17bec 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -577,19 +577,20 @@ portfolio.pending.autoConf.state.xmr.txKeyReused=Transaction key re-used. Please portfolio.pending.autoConf.state.confirmations=Confirmations: {0}/{1} portfolio.pending.autoConf.state.txNotFound=Transaction not confirmed yet portfolio.pending.autoConf.state.txKeyOrTxIdInvalid=No valid transaction ID / transaction key +portfolio.pending.autoConf.state.filterDisabledFeature=Disabled by developers. # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabled +portfolio.pending.autoConf.state.FEATURE_DISABLED=Auto-confirm feature is disabled. {0} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.TRADE_LIMIT_EXCEEDED=Trade amount exceeds auto-confirm amount limit # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data: {0} +portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data. {0} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published. # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started # suppress inspection "UnusedProperty" -portfolio.pending.autoConf.state.PENDING=Service results: {0}/{1} / {2} +portfolio.pending.autoConf.state.PENDING=Service results: {0}/{1}; {2} # suppress inspection "UnusedProperty" portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded # suppress inspection "UnusedProperty" diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java index ab119d8bbb7..8862664104f 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/windows/SetXmrTxKeyWindow.java @@ -60,7 +60,7 @@ public void show() { addButtons(); regexValidator = new RegexValidator(); - regexValidator.setPattern("[a-fA-F0-9]{64}"); + regexValidator.setPattern("[a-fA-F0-9]{64}|^$"); regexValidator.setErrorMessage(Res.get("portfolio.pending.step2_buyer.confirmStart.proof.invalidInput")); txHashInputTextField.setValidator(regexValidator); txKeyInputTextField.setValidator(regexValidator); diff --git a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java index 9c54aa13b17..3fb3ad6b27f 100644 --- a/desktop/src/main/java/bisq/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/bisq/desktop/util/GUIUtil.java @@ -1170,6 +1170,7 @@ public static String getProofResultAsString(@Nullable AssetTxProofResult result) case UNDEFINED: return ""; case FEATURE_DISABLED: + return Res.get(key, result.getDetails()); case TRADE_LIMIT_EXCEEDED: return Res.get(key); case INVALID_DATA: From a9e61e409e472c947ecf343b05397981bc22c831 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 00:25:35 -0500 Subject: [PATCH 68/85] Replace ObjectProperty with IntegerProperty // ObjectProperty with AssetTxProofResult does not notify changeListeners. Probably because AssetTxProofResult is // an enum and enum does not support EqualsAndHashCode. Alternatively we could add a addListener and removeListener // method and a listener interface, but the IntegerProperty seems to be less boilerplate. --- core/src/main/java/bisq/core/trade/Trade.java | 9 +++++++-- .../pendingtrades/steps/seller/SellerStep3View.java | 11 ++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 1936f57111f..468f87658c2 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -67,9 +67,11 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -442,8 +444,11 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt @Nullable @Getter private AssetTxProofResult assetTxProofResult; + // ObjectProperty with AssetTxProofResult does not notify changeListeners. Probably because AssetTxProofResult is + // an enum and enum does not support EqualsAndHashCode. Alternatively we could add a addListener and removeListener + // method and a listener interface, but the IntegerProperty seems to be less boilerplate. @Getter - transient final private ObjectProperty assetTxProofResultProperty = new SimpleObjectProperty<>(); + transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -878,7 +883,7 @@ public void setErrorMessage(String errorMessage) { public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResult) { this.assetTxProofResult = assetTxProofResult; - assetTxProofResultProperty.setValue(assetTxProofResult); + assetTxProofResultUpdateProperty.set(assetTxProofResultUpdateProperty.get() + 1); } diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 6faa8d5d1b9..4bff3492dfe 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -78,7 +78,7 @@ public class SellerStep3View extends TradeStepView { private Subscription tradeStatePropertySubscription; private Timer timeoutTimer; private TextFieldWithCopyIcon assetTxProofResultField; - private final ChangeListener proofResultListener; + private final ChangeListener proofResultListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -89,7 +89,7 @@ public SellerStep3View(PendingTradesViewModel model) { super(model); proofResultListener = (observable, oldValue, newValue) -> { - applyAssetTxProofResult(newValue); + applyAssetTxProofResult(trade.getAssetTxProofResult()); }; } @@ -153,9 +153,11 @@ public void activate() { // we listen for updates on the trade autoConfirmResult field if (assetTxProofResultField != null) { - trade.getAssetTxProofResultProperty().addListener(proofResultListener); + trade.getAssetTxProofResultUpdateProperty().addListener(proofResultListener); applyAssetTxProofResult(trade.getAssetTxProofResult()); } + + applyAssetTxProofResult(trade.getAssetTxProofResult()); } @Override @@ -174,7 +176,7 @@ public void deactivate() { } if (assetTxProofResultField != null) { - trade.getAssetTxProofResultProperty().removeListener(proofResultListener); + trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener); } } @@ -184,7 +186,6 @@ public void deactivate() { @Override protected void addContent() { - gridPane.getColumnConstraints().get(1).setHgrow(Priority.ALWAYS); addTradeInfoBlock(); From adbb262c1bb1f47b517a06b3c5392f72a843b2e8 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 01:10:46 -0500 Subject: [PATCH 69/85] Add unnecessary break; at default clause to satisfy stupid codacy bot. Getting more an more annoyed by that inflexible and often pointless requirements. --- .../java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java | 1 + .../core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index b33940d4894..b5e3d72bfe9 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -224,6 +224,7 @@ public void onSuccess(Result result) { break; default: log.warn("Unexpected result {}", result); + break; } } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index a4bceeddf77..2a1edfd1070 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -52,7 +52,6 @@ class XmrTxProofRequestsPerTrade { private int numSuccessResults; private ChangeListener tradeStateListener; - private List serviceAddresses; private AutoConfirmSettings.Listener autoConfirmSettingsListener; XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, @@ -68,7 +67,7 @@ void requestFromAllServices(Consumer resultHandler, FaultHan // it will have no impact on serviceAddresses and numRequiredSuccessResults. // Thought numRequiredConfirmations can be changed during request process and will be read from // autoConfirmSettings at result parsing. - serviceAddresses = autoConfirmSettings.getServiceAddresses(); + List serviceAddresses = autoConfirmSettings.getServiceAddresses(); numRequiredSuccessResults = serviceAddresses.size(); if (isTradeAmountAboveLimit(trade)) { @@ -154,6 +153,7 @@ void requestFromAllServices(Consumer resultHandler, FaultHan request, result); assetTxProofResult = AssetTxProofResult.ERROR; + break; } callResultHandlerAndMaybeTerminate(resultHandler, assetTxProofResult); From 37241f98d87f093a83f278a48e983c2e639a2ba9 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 11:12:49 -0500 Subject: [PATCH 70/85] Add interfaces and generic support Make API more clear and helps for test setup --- .../core/trade/txproof/AssetTxProofModel.java | 21 ++++++++ .../trade/txproof/AssetTxProofParser.java | 22 ++++++++ .../trade/txproof/AssetTxProofRequest.java | 31 +++++++++++ .../txproof/AssetTxProofRequestsPerTrade.java | 28 ++++++++++ .../trade/txproof/AssetTxProofService.java | 34 +++++++++++++ .../trade/txproof/xmr/XmrTxProofModel.java | 4 +- .../trade/txproof/xmr/XmrTxProofParser.java | 32 ++++++++---- .../trade/txproof/xmr/XmrTxProofRequest.java | 36 +++++++++---- .../xmr/XmrTxProofRequestsPerTrade.java | 51 +++++++++++++------ .../trade/txproof/xmr/XmrTxProofService.java | 17 ++++--- 10 files changed, 234 insertions(+), 42 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/txproof/AssetTxProofModel.java create mode 100644 core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java create mode 100644 core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequest.java create mode 100644 core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequestsPerTrade.java create mode 100644 core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofModel.java new file mode 100644 index 00000000000..a153a99b289 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofModel.java @@ -0,0 +1,21 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof; + +public interface AssetTxProofModel { +} diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java new file mode 100644 index 00000000000..f128cfbc39a --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java @@ -0,0 +1,22 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof; + +public interface AssetTxProofParser { + R parse(T model, String jsonTxt); +} diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequest.java new file mode 100644 index 00000000000..44835749b0d --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequest.java @@ -0,0 +1,31 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof; + +import bisq.common.handlers.FaultHandler; + +import java.util.function.Consumer; + +public interface AssetTxProofRequest { + interface Result { + } + + void requestFromService(Consumer resultHandler, FaultHandler faultHandler); + + void terminate(); +} diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequestsPerTrade.java new file mode 100644 index 00000000000..8d1e969dbb6 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofRequestsPerTrade.java @@ -0,0 +1,28 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof; + +import bisq.common.handlers.FaultHandler; + +import java.util.function.Consumer; + +public interface AssetTxProofRequestsPerTrade { + void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler); + + void terminate(); +} diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java new file mode 100644 index 00000000000..56e2a7462e8 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofService.java @@ -0,0 +1,34 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof; + +import bisq.core.trade.Trade; + +import bisq.common.handlers.FaultHandler; + +import java.util.List; +import java.util.function.Consumer; + +public interface AssetTxProofService { + void maybeStartRequests(Trade trade, + List activeTrades, + Consumer resultHandler, + FaultHandler faultHandler); + + void shutDown(); +} diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java index 5751427dc95..f9451448af5 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofModel.java @@ -21,6 +21,7 @@ import bisq.core.payment.payload.AssetsAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.trade.Trade; +import bisq.core.trade.txproof.AssetTxProofModel; import bisq.core.user.AutoConfirmSettings; import bisq.common.app.DevEnv; @@ -39,7 +40,7 @@ @SuppressWarnings("SpellCheckingInspection") @Slf4j @Value -public class XmrTxProofModel { +public class XmrTxProofModel implements AssetTxProofModel { // Those are values from a valid tx which are set automatically if DevEnv.isDevMode is enabled public static final String DEV_ADDRESS = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub"; public static final String DEV_TX_KEY = "f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906"; @@ -81,6 +82,7 @@ int getNumRequiredConfirmations() { } // Used only for testing + // TODO Use mocking framework in testing to avoid that constructor... @VisibleForTesting XmrTxProofModel(String tradeId, String txHash, diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index 594b34166c0..dc9f7f87299 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -17,6 +17,8 @@ package bisq.core.trade.txproof.xmr; +import bisq.core.trade.txproof.AssetTxProofParser; + import bisq.asset.CryptoNoteUtils; import bisq.common.app.DevEnv; @@ -27,15 +29,27 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import javax.inject.Inject; + import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j -class XmrTxProofParser { +class XmrTxProofParser implements AssetTxProofParser { + @Inject + public XmrTxProofParser() { + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + @SuppressWarnings("SpellCheckingInspection") - static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String jsonTxt) { - String txHash = xmrTxProofModel.getTxHash(); + @Override + public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { + String txHash = model.getTxHash(); try { JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); if (json == null) { @@ -61,7 +75,7 @@ static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String js if (jsonAddress == null) { return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing address field")); } else { - String expectedAddressHex = CryptoNoteUtils.convertToRawHex(xmrTxProofModel.getRecipientAddress()); + String expectedAddressHex = CryptoNoteUtils.convertToRawHex(model.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.ADDRESS_INVALID); @@ -84,8 +98,8 @@ static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String js if (jsonViewkey == null) { return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing viewkey field")); } else { - if (!jsonViewkey.getAsString().equalsIgnoreCase(xmrTxProofModel.getTxKey())) { - log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), xmrTxProofModel.getTxKey()); + if (!jsonViewkey.getAsString().equalsIgnoreCase(model.getTxKey())) { + log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), model.getTxKey()); return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TX_KEY_INVALID); } } @@ -96,7 +110,7 @@ static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String js if (jsonTimestamp == null) { return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_timestamp field")); } else { - long tradeDateSeconds = xmrTxProofModel.getTradeDate().getTime() / 1000; + long tradeDateSeconds = model.getTradeDate().getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { @@ -127,7 +141,7 @@ static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String js if (out.get("match").getAsBoolean()) { anyMatchFound = true; long jsonAmount = out.get("amount").getAsLong(); - amountMatches = jsonAmount == xmrTxProofModel.getAmount(); + amountMatches = jsonAmount == model.getAmount(); if (amountMatches) { break; } @@ -144,7 +158,7 @@ static XmrTxProofRequest.Result parse(XmrTxProofModel xmrTxProofModel, String js return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); } - int confirmsRequired = xmrTxProofModel.getNumRequiredConfirmations(); + int confirmsRequired = model.getNumRequiredConfirmations(); if (confirmations < confirmsRequired) { return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); } else { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index b5e3d72bfe9..cbc208c6511 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -17,6 +17,8 @@ package bisq.core.trade.txproof.xmr; +import bisq.core.trade.txproof.AssetTxProofRequest; + import bisq.network.Socks5ProxyProvider; import bisq.common.UserThread; @@ -49,13 +51,13 @@ */ @Slf4j @EqualsAndHashCode -class XmrTxProofRequest { +class XmrTxProofRequest implements AssetTxProofRequest { /////////////////////////////////////////////////////////////////////////////////////////// // Enums /////////////////////////////////////////////////////////////////////////////////////////// - enum Result { + enum Result implements AssetTxProofRequest.Result { PENDING, // Tx not visible in network yet, unconfirmed or not enough confirmations SUCCESS, // Proof succeeded FAILED, // Proof failed @@ -124,15 +126,20 @@ public String toString() { /////////////////////////////////////////////////////////////////////////////////////////// - // Class fields + // Static fields /////////////////////////////////////////////////////////////////////////////////////////// private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); + /////////////////////////////////////////////////////////////////////////////////////////// + // Class fields + /////////////////////////////////////////////////////////////////////////////////////////// + private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofRequester", 3, 5, 10 * 60); private final XmrTxProofHttpClient httpClient; + private final XmrTxProofParser xmrTxProofParser; private final XmrTxProofModel xmrTxProofModel; private final long firstRequest; @@ -146,7 +153,10 @@ public String toString() { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, XmrTxProofModel xmrTxProofModel) { + XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, + XmrTxProofParser xmrTxProofParser, + XmrTxProofModel xmrTxProofModel) { + this.xmrTxProofParser = xmrTxProofParser; this.xmrTxProofModel = xmrTxProofModel; httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); @@ -165,7 +175,8 @@ public String toString() { /////////////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("SpellCheckingInspection") - public void start(Consumer resultHandler, FaultHandler faultHandler) { + @Override + public void requestFromService(Consumer resultHandler, FaultHandler faultHandler) { if (terminated) { // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls // this scenario may happen if a re-request is scheduled from the callback below @@ -185,7 +196,7 @@ public void start(Consumer resultHandler, FaultHandler faultHandler) { String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); log.info("Response json from {}\n{}", this, prettyJson); - Result result = XmrTxProofParser.parse(xmrTxProofModel, json); + Result result = xmrTxProofParser.parse(xmrTxProofModel, json); log.info("Result from {}\n{}", this, result); return result; }); @@ -205,9 +216,9 @@ public void onSuccess(Result result) { log.warn("{} took too long without a success or failure/error result We give up. " + "Might be that the transaction was never published.", this); // If we reached out timeout we return with an error. - UserThread.execute(() -> resultHandler.accept(Result.ERROR.with(Detail.NO_RESULTS_TIMEOUT))); + UserThread.execute(() -> resultHandler.accept(XmrTxProofRequest.Result.ERROR.with(Detail.NO_RESULTS_TIMEOUT))); } else { - UserThread.runAfter(() -> start(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); + UserThread.runAfter(() -> requestFromService(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); // We update our listeners UserThread.execute(() -> resultHandler.accept(result)); } @@ -232,12 +243,13 @@ public void onFailure(@NotNull Throwable throwable) { String errorMessage = this + " failed with error " + throwable.toString(); faultHandler.handleFault(errorMessage, throwable); UserThread.execute(() -> - resultHandler.accept(Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage)))); + resultHandler.accept(XmrTxProofRequest.Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage)))); } }); } - void terminate() { + @Override + public void terminate() { terminated = true; } @@ -247,6 +259,10 @@ public String toString() { return "Request at: " + xmrTxProofModel.getServiceAddress() + " for trade: " + xmrTxProofModel.getTradeId(); } + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + private String getShortId() { return Utilities.getShortId(xmrTxProofModel.getTradeId()) + " @ " + xmrTxProofModel.getServiceAddress().substring(0, 6); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 2a1edfd1070..946fe86e7d4 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -19,6 +19,7 @@ import bisq.core.locale.Res; import bisq.core.trade.Trade; +import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.AutoConfirmSettings; @@ -42,8 +43,9 @@ * Handles the XMR tx proof requests for multiple services per trade. */ @Slf4j -class XmrTxProofRequestsPerTrade { +class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { private final Socks5ProxyProvider socks5ProxyProvider; + private final XmrTxProofParser xmrTxProofParser; private final Trade trade; private final AutoConfirmSettings autoConfirmSettings; @@ -54,15 +56,28 @@ class XmrTxProofRequestsPerTrade { private ChangeListener tradeStateListener; private AutoConfirmSettings.Listener autoConfirmSettingsListener; + + /////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + /////////////////////////////////////////////////////////////////////////////////////////// + XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, + XmrTxProofParser xmrTxProofParser, Trade trade, AutoConfirmSettings autoConfirmSettings) { this.socks5ProxyProvider = socks5ProxyProvider; + this.xmrTxProofParser = xmrTxProofParser; this.trade = trade; this.autoConfirmSettings = autoConfirmSettings; } - void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler) { + + /////////////////////////////////////////////////////////////////////////////////////////// + // API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler) { // We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started // it will have no impact on serviceAddresses and numRequiredSuccessResults. // Thought numRequiredConfirmations can be changed during request process and will be read from @@ -103,12 +118,12 @@ void requestFromAllServices(Consumer resultHandler, FaultHan for (String serviceAddress : serviceAddresses) { XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings); - XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); + XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, xmrTxProofParser, model); log.info("{} created", request); requests.add(request); - request.start(result -> { + request.requestFromService(result -> { AssetTxProofResult assetTxProofResult; if (trade.isPayoutPublished()) { assetTxProofResult = AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED; @@ -162,6 +177,23 @@ void requestFromAllServices(Consumer resultHandler, FaultHan } } + @Override + public void terminate() { + requests.forEach(XmrTxProofRequest::terminate); + requests.clear(); + if (tradeStateListener != null) { + UserThread.execute(() -> trade.stateProperty().removeListener(tradeStateListener)); + } + if (autoConfirmSettingsListener != null) { + UserThread.execute(() -> autoConfirmSettings.removeListener(autoConfirmSettingsListener)); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private + /////////////////////////////////////////////////////////////////////////////////////////// + private void callResultHandlerAndMaybeTerminate(Consumer resultHandler, AssetTxProofResult assetTxProofResult) { resultHandler.accept(assetTxProofResult); @@ -191,17 +223,6 @@ private AssetTxProofResult getAssetTxProofResultForPending(XmrTxProofRequest.Res .details(detailString); } - void terminate() { - requests.forEach(XmrTxProofRequest::terminate); - requests.clear(); - if (tradeStateListener != null) { - UserThread.execute(() -> trade.stateProperty().removeListener(tradeStateListener)); - } - if (autoConfirmSettingsListener != null) { - UserThread.execute(() -> autoConfirmSettings.removeListener(autoConfirmSettingsListener)); - } - } - /////////////////////////////////////////////////////////////////////////////////////////// // Validation diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index 576fcc076e9..e0d4e67ab31 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -26,6 +26,7 @@ import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.txproof.AssetTxProofResult; +import bisq.core.trade.txproof.AssetTxProofService; import bisq.core.user.AutoConfirmSettings; import bisq.core.user.Preferences; @@ -55,9 +56,10 @@ */ @Slf4j @Singleton -public class XmrTxProofService { +public class XmrTxProofService implements AssetTxProofService { private final FilterManager filterManager; private final Socks5ProxyProvider socks5ProxyProvider; + private final XmrTxProofParser xmrTxProofParser; private final Preferences preferences; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; @@ -79,7 +81,8 @@ public XmrTxProofService(FilterManager filterManager, FailedTradesManager failedTradesManager, P2PService p2PService, WalletsSetup walletsSetup, - Socks5ProxyProvider socks5ProxyProvider) { + Socks5ProxyProvider socks5ProxyProvider, + XmrTxProofParser xmrTxProofParser) { this.filterManager = filterManager; this.preferences = preferences; this.closedTradableManager = closedTradableManager; @@ -87,6 +90,7 @@ public XmrTxProofService(FilterManager filterManager, this.p2PService = p2PService; this.walletsSetup = walletsSetup; this.socks5ProxyProvider = socks5ProxyProvider; + this.xmrTxProofParser = xmrTxProofParser; } @@ -94,6 +98,7 @@ public XmrTxProofService(FilterManager filterManager, // API /////////////////////////////////////////////////////////////////////////////////////////// + @Override public void maybeStartRequests(Trade trade, List activeTrades, Consumer resultHandler, @@ -134,6 +139,7 @@ public void maybeStartRequests(Trade trade, } XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(socks5ProxyProvider, + xmrTxProofParser, trade, autoConfirmSettings); servicesByTradeId.put(trade.getId(), service); @@ -142,7 +148,7 @@ public void maybeStartRequests(Trade trade, trade.setAssetTxProofResult(assetTxProofResult); if (assetTxProofResult.isTerminal()) { - cleanUp(trade); + servicesByTradeId.remove(trade.getId()); } resultHandler.accept(assetTxProofResult); @@ -150,15 +156,12 @@ public void maybeStartRequests(Trade trade, faultHandler); } + @Override public void shutDown() { servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); servicesByTradeId.clear(); } - private void cleanUp(Trade trade) { - servicesByTradeId.remove(trade.getId()); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Validation From d725590a9cee1b2fc26acdb50840ff85ad627549 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 11:16:06 -0500 Subject: [PATCH 71/85] Remove delay form UserThread.execute For tradeStateListener that would cause a wrong state as we would get called after we have completed and do the isPayoutPublished check. Keeping it in sync does remove the listener before we complete the trade. For autoConfirmSettings it would probably not cause and issue but as we use a CopyOnWriteArrayList there the remove is safe anyway (to not cause a ConcurrentListModification exception). --- .../core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 946fe86e7d4..382ee66a8fc 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -25,7 +25,6 @@ import bisq.network.Socks5ProxyProvider; -import bisq.common.UserThread; import bisq.common.handlers.FaultHandler; import org.bitcoinj.core.Coin; @@ -182,10 +181,10 @@ public void terminate() { requests.forEach(XmrTxProofRequest::terminate); requests.clear(); if (tradeStateListener != null) { - UserThread.execute(() -> trade.stateProperty().removeListener(tradeStateListener)); + trade.stateProperty().removeListener(tradeStateListener); } if (autoConfirmSettingsListener != null) { - UserThread.execute(() -> autoConfirmSettings.removeListener(autoConfirmSettingsListener)); + autoConfirmSettings.removeListener(autoConfirmSettingsListener); } } From 6c333857e9a5da8da640c89ab0561aeaaeb35326 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 11:18:45 -0500 Subject: [PATCH 72/85] Config XmrTxProofParser in guice module --- core/src/main/java/bisq/core/trade/TradeModule.java | 3 +++ .../java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/core/src/main/java/bisq/core/trade/TradeModule.java b/core/src/main/java/bisq/core/trade/TradeModule.java index a190a19f20b..75d851cf9be 100644 --- a/core/src/main/java/bisq/core/trade/TradeModule.java +++ b/core/src/main/java/bisq/core/trade/TradeModule.java @@ -26,6 +26,8 @@ import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatistics2StorageService; import bisq.core.trade.statistics.TradeStatisticsManager; +import bisq.core.trade.txproof.AssetTxProofParser; +import bisq.core.trade.txproof.xmr.XmrTxProofParser; import bisq.common.app.AppModule; import bisq.common.config.Config; @@ -55,6 +57,7 @@ protected void configure() { bind(SignedWitnessService.class).in(Singleton.class); bind(SignedWitnessStorageService.class).in(Singleton.class); bind(ReferralIdService.class).in(Singleton.class); + bind(AssetTxProofParser.class).to(XmrTxProofParser.class); bindConstant().annotatedWith(named(DUMP_STATISTICS)).to(config.dumpStatistics); bindConstant().annotatedWith(named(DUMP_DELAYED_PAYOUT_TXS)).to(config.dumpDelayedPayoutTxs); bindConstant().annotatedWith(named(ALLOW_FAULTY_DELAYED_TXS)).to(config.allowFaultyDelayedTxs); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index dc9f7f87299..4b3747e6868 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -30,13 +30,17 @@ import com.google.gson.JsonParseException; import javax.inject.Inject; +import javax.inject.Singleton; import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j +@Singleton +public class XmrTxProofParser implements AssetTxProofParser { + @Inject public XmrTxProofParser() { } From 7e6a7a5853b3c6db95202afcfdb6779e17537424 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 12:33:34 -0500 Subject: [PATCH 73/85] Add AssetTxProofHttpClient interface Use the AssetTxProofHttpClient as injected param instead of parser. Helpful for dev testing with mocked json response. --- .../java/bisq/core/trade/TradeModule.java | 8 ++-- .../trade/txproof/AssetTxProofHttpClient.java | 23 ++++++++++ .../trade/txproof/AssetTxProofParser.java | 2 +- .../txproof/xmr/XmrTxProofHttpClient.java | 12 +++++- .../trade/txproof/xmr/XmrTxProofParser.java | 11 +---- .../trade/txproof/xmr/XmrTxProofRequest.java | 42 +++++++++---------- .../xmr/XmrTxProofRequestsPerTrade.java | 14 +++---- .../trade/txproof/xmr/XmrTxProofService.java | 15 +++---- .../java/bisq/network/http/HttpClient.java | 17 +++++--- .../java/bisq/network/http/IHttpClient.java | 40 ++++++++++++++++++ 10 files changed, 123 insertions(+), 61 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java create mode 100644 p2p/src/main/java/bisq/network/http/IHttpClient.java diff --git a/core/src/main/java/bisq/core/trade/TradeModule.java b/core/src/main/java/bisq/core/trade/TradeModule.java index 75d851cf9be..422ec13a83c 100644 --- a/core/src/main/java/bisq/core/trade/TradeModule.java +++ b/core/src/main/java/bisq/core/trade/TradeModule.java @@ -26,8 +26,8 @@ import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.TradeStatistics2StorageService; import bisq.core.trade.statistics.TradeStatisticsManager; -import bisq.core.trade.txproof.AssetTxProofParser; -import bisq.core.trade.txproof.xmr.XmrTxProofParser; +import bisq.core.trade.txproof.AssetTxProofHttpClient; +import bisq.core.trade.txproof.xmr.XmrTxProofHttpClient; import bisq.common.app.AppModule; import bisq.common.config.Config; @@ -57,7 +57,9 @@ protected void configure() { bind(SignedWitnessService.class).in(Singleton.class); bind(SignedWitnessStorageService.class).in(Singleton.class); bind(ReferralIdService.class).in(Singleton.class); - bind(AssetTxProofParser.class).to(XmrTxProofParser.class); + + bind(AssetTxProofHttpClient.class).to(XmrTxProofHttpClient.class); + bindConstant().annotatedWith(named(DUMP_STATISTICS)).to(config.dumpStatistics); bindConstant().annotatedWith(named(DUMP_DELAYED_PAYOUT_TXS)).to(config.dumpDelayedPayoutTxs); bindConstant().annotatedWith(named(ALLOW_FAULTY_DELAYED_TXS)).to(config.allowFaultyDelayedTxs); diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java new file mode 100644 index 00000000000..e6f51fe6cfd --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java @@ -0,0 +1,23 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof; + +import bisq.network.http.IHttpClient; + +public interface AssetTxProofHttpClient extends IHttpClient { +} diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java index f128cfbc39a..ae0a803ff3a 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofParser.java @@ -17,6 +17,6 @@ package bisq.core.trade.txproof; -public interface AssetTxProofParser { +public interface AssetTxProofParser { R parse(T model, String jsonTxt); } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java index 1d63da4fffd..bf78570bdd4 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java @@ -17,11 +17,19 @@ package bisq.core.trade.txproof.xmr; +import bisq.core.trade.txproof.AssetTxProofHttpClient; + import bisq.network.Socks5ProxyProvider; import bisq.network.http.HttpClient; -class XmrTxProofHttpClient extends HttpClient { - XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { +import javax.inject.Inject; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class XmrTxProofHttpClient extends HttpClient implements AssetTxProofHttpClient { + @Inject + public XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); } } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index 4b3747e6868..ae223d22698 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -29,20 +29,13 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; -import javax.inject.Inject; -import javax.inject.Singleton; - import java.util.concurrent.TimeUnit; import lombok.extern.slf4j.Slf4j; @Slf4j -@Singleton -public -class XmrTxProofParser implements AssetTxProofParser { - - @Inject - public XmrTxProofParser() { +public class XmrTxProofParser implements AssetTxProofParser { + XmrTxProofParser() { } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index cbc208c6511..f0b84d6060a 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -17,10 +17,10 @@ package bisq.core.trade.txproof.xmr; +import bisq.core.trade.txproof.AssetTxProofHttpClient; +import bisq.core.trade.txproof.AssetTxProofParser; import bisq.core.trade.txproof.AssetTxProofRequest; -import bisq.network.Socks5ProxyProvider; - import bisq.common.UserThread; import bisq.common.app.Version; import bisq.common.handlers.FaultHandler; @@ -138,9 +138,10 @@ public String toString() { private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( "XmrTransferProofRequester", 3, 5, 10 * 60); - private final XmrTxProofHttpClient httpClient; - private final XmrTxProofParser xmrTxProofParser; - private final XmrTxProofModel xmrTxProofModel; + + private final AssetTxProofHttpClient httpClient; + private final AssetTxProofParser parser; + private final XmrTxProofModel model; private final long firstRequest; private boolean terminated; @@ -153,16 +154,15 @@ public String toString() { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofParser xmrTxProofParser, - XmrTxProofModel xmrTxProofModel) { - this.xmrTxProofParser = xmrTxProofParser; - this.xmrTxProofModel = xmrTxProofModel; + XmrTxProofRequest(AssetTxProofHttpClient httpClient, + XmrTxProofModel model) { + this.httpClient = httpClient; + this.parser = new XmrTxProofParser(); + this.model = model; - httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); - httpClient.setBaseUrl("http://" + xmrTxProofModel.getServiceAddress()); - if (xmrTxProofModel.getServiceAddress().matches("^192.*|^localhost.*")) { - log.info("Ignoring Socks5 proxy for local net address: {}", xmrTxProofModel.getServiceAddress()); + httpClient.setBaseUrl("http://" + model.getServiceAddress()); + if (model.getServiceAddress().matches("^192.*|^localhost.*")) { + log.info("Ignoring Socks5 proxy for local net address: {}", model.getServiceAddress()); httpClient.setIgnoreSocks5Proxy(true); } @@ -188,15 +188,15 @@ public void requestFromService(Consumer resultHandler, FaultHandler faul ListenableFuture future = executorService.submit(() -> { Thread.currentThread().setName("XmrTransferProofRequest-" + this.getShortId()); - String param = "/api/outputs?txhash=" + xmrTxProofModel.getTxHash() + - "&address=" + xmrTxProofModel.getRecipientAddress() + - "&viewkey=" + xmrTxProofModel.getTxKey() + + String param = "/api/outputs?txhash=" + model.getTxHash() + + "&address=" + model.getRecipientAddress() + + "&viewkey=" + model.getTxKey() + "&txprove=1"; log.info("Param {} for {}", param, this); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); log.info("Response json from {}\n{}", this, prettyJson); - Result result = xmrTxProofParser.parse(xmrTxProofModel, json); + Result result = parser.parse(model, json); log.info("Result from {}\n{}", this, result); return result; }); @@ -256,7 +256,7 @@ public void terminate() { // Convenient for logging @Override public String toString() { - return "Request at: " + xmrTxProofModel.getServiceAddress() + " for trade: " + xmrTxProofModel.getTradeId(); + return "Request at: " + model.getServiceAddress() + " for trade: " + model.getTradeId(); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -264,8 +264,8 @@ public String toString() { /////////////////////////////////////////////////////////////////////////////////////////// private String getShortId() { - return Utilities.getShortId(xmrTxProofModel.getTradeId()) + " @ " + - xmrTxProofModel.getServiceAddress().substring(0, 6); + return Utilities.getShortId(model.getTradeId()) + " @ " + + model.getServiceAddress().substring(0, 6); } private boolean isTimeOutReached() { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 382ee66a8fc..1a0d346fc76 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -19,12 +19,11 @@ import bisq.core.locale.Res; import bisq.core.trade.Trade; +import bisq.core.trade.txproof.AssetTxProofHttpClient; import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.user.AutoConfirmSettings; -import bisq.network.Socks5ProxyProvider; - import bisq.common.handlers.FaultHandler; import org.bitcoinj.core.Coin; @@ -43,10 +42,9 @@ */ @Slf4j class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { - private final Socks5ProxyProvider socks5ProxyProvider; - private final XmrTxProofParser xmrTxProofParser; private final Trade trade; private final AutoConfirmSettings autoConfirmSettings; + private final AssetTxProofHttpClient httpClient; private int numRequiredSuccessResults; private final Set requests = new HashSet<>(); @@ -60,12 +58,10 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofParser xmrTxProofParser, + XmrTxProofRequestsPerTrade(AssetTxProofHttpClient httpClient, Trade trade, AutoConfirmSettings autoConfirmSettings) { - this.socks5ProxyProvider = socks5ProxyProvider; - this.xmrTxProofParser = xmrTxProofParser; + this.httpClient = httpClient; this.trade = trade; this.autoConfirmSettings = autoConfirmSettings; } @@ -117,7 +113,7 @@ public void requestFromAllServices(Consumer resultHandler, F for (String serviceAddress : serviceAddresses) { XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings); - XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, xmrTxProofParser, model); + XmrTxProofRequest request = new XmrTxProofRequest(httpClient, model); log.info("{} created", request); requests.add(request); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index e0d4e67ab31..d426a0ae076 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -25,12 +25,12 @@ import bisq.core.trade.Trade; import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.failed.FailedTradesManager; +import bisq.core.trade.txproof.AssetTxProofHttpClient; import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.trade.txproof.AssetTxProofService; import bisq.core.user.AutoConfirmSettings; import bisq.core.user.Preferences; -import bisq.network.Socks5ProxyProvider; import bisq.network.p2p.P2PService; import bisq.common.app.DevEnv; @@ -58,14 +58,12 @@ @Singleton public class XmrTxProofService implements AssetTxProofService { private final FilterManager filterManager; - private final Socks5ProxyProvider socks5ProxyProvider; - private final XmrTxProofParser xmrTxProofParser; private final Preferences preferences; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; private final P2PService p2PService; private final WalletsSetup walletsSetup; - + private final AssetTxProofHttpClient httpClient; private final Map servicesByTradeId = new HashMap<>(); @@ -81,16 +79,14 @@ public XmrTxProofService(FilterManager filterManager, FailedTradesManager failedTradesManager, P2PService p2PService, WalletsSetup walletsSetup, - Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofParser xmrTxProofParser) { + AssetTxProofHttpClient httpClient) { this.filterManager = filterManager; this.preferences = preferences; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; this.p2PService = p2PService; this.walletsSetup = walletsSetup; - this.socks5ProxyProvider = socks5ProxyProvider; - this.xmrTxProofParser = xmrTxProofParser; + this.httpClient = httpClient; } @@ -138,8 +134,7 @@ public void maybeStartRequests(Trade trade, return; } - XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(socks5ProxyProvider, - xmrTxProofParser, + XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(httpClient, trade, autoConfirmSettings); servicesByTradeId.put(trade.getId(), service); diff --git a/p2p/src/main/java/bisq/network/http/HttpClient.java b/p2p/src/main/java/bisq/network/http/HttpClient.java index 1890a3eaddd..3bd3b583fad 100644 --- a/p2p/src/main/java/bisq/network/http/HttpClient.java +++ b/p2p/src/main/java/bisq/network/http/HttpClient.java @@ -57,7 +57,7 @@ // TODO close connection if failing @Slf4j -public class HttpClient { +public class HttpClient implements IHttpClient { @Nullable private Socks5ProxyProvider socks5ProxyProvider; @Getter @@ -76,14 +76,17 @@ public HttpClient(String baseUrl) { uid = UUID.randomUUID().toString(); } + @Override public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } + @Override public void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy) { this.ignoreSocks5Proxy = ignoreSocks5Proxy; } + @Override public String requestWithGET(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException { @@ -103,13 +106,14 @@ public String requestWithGET(String param, return requestWithGETNoProxy(param, headerKey, headerValue); } else { log.debug("Use socks5Proxy for HttpClient: " + socks5Proxy); - return requestWithGETProxy(param, socks5Proxy, headerKey, headerValue); + return doRequestWithGETProxy(param, socks5Proxy, headerKey, headerValue); } } /** * Make an HTTP Get request directly (not routed over socks5 proxy). */ + @Override public String requestWithGETNoProxy(String param, @Nullable String headerKey, @Nullable String headerValue) throws IOException { @@ -145,6 +149,7 @@ public String requestWithGETNoProxy(String param, } } + @Override public String getUid() { return uid; } @@ -153,10 +158,10 @@ public String getUid() { /** * Make an HTTP Get request routed over socks5 proxy. */ - private String requestWithGETProxy(String param, - Socks5Proxy socks5Proxy, - @Nullable String headerKey, - @Nullable String headerValue) throws IOException { + private String doRequestWithGETProxy(String param, + Socks5Proxy socks5Proxy, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { log.debug("requestWithGETProxy param=" + param); // This code is adapted from: // http://stackoverflow.com/a/25203021/5616248 diff --git a/p2p/src/main/java/bisq/network/http/IHttpClient.java b/p2p/src/main/java/bisq/network/http/IHttpClient.java new file mode 100644 index 00000000000..1fde6522527 --- /dev/null +++ b/p2p/src/main/java/bisq/network/http/IHttpClient.java @@ -0,0 +1,40 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.network.http; + +import java.io.IOException; + +import javax.annotation.Nullable; + +public interface IHttpClient { + void setBaseUrl(String baseUrl); + + void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy); + + String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException; + + String requestWithGETNoProxy(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException; + + String getUid(); + + String getBaseUrl(); +} From a0c75e96722677612d33f70c67ec9945201e1113 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 12:41:39 -0500 Subject: [PATCH 74/85] Rename class HttpClient to HttpClientImpl and interface IHttpClient to HttpClient. Use interface instead of class as type. Add default guice binding to HttpClientImpl for HttpClient (not singleton!) --- .../core/provider/PriceNodeHttpClient.java | 4 +- .../trade/txproof/AssetTxProofHttpClient.java | 4 +- .../txproof/xmr/XmrTxProofHttpClient.java | 4 +- .../java/bisq/network/http/HttpClient.java | 200 +--------------- .../bisq/network/http/HttpClientImpl.java | 220 ++++++++++++++++++ .../java/bisq/network/http/IHttpClient.java | 40 ---- .../main/java/bisq/network/p2p/P2PModule.java | 3 + 7 files changed, 239 insertions(+), 236 deletions(-) create mode 100644 p2p/src/main/java/bisq/network/http/HttpClientImpl.java delete mode 100644 p2p/src/main/java/bisq/network/http/IHttpClient.java diff --git a/core/src/main/java/bisq/core/provider/PriceNodeHttpClient.java b/core/src/main/java/bisq/core/provider/PriceNodeHttpClient.java index 1a9510f4677..fbe396f1d8a 100644 --- a/core/src/main/java/bisq/core/provider/PriceNodeHttpClient.java +++ b/core/src/main/java/bisq/core/provider/PriceNodeHttpClient.java @@ -18,13 +18,13 @@ package bisq.core.provider; import bisq.network.Socks5ProxyProvider; -import bisq.network.http.HttpClient; +import bisq.network.http.HttpClientImpl; import javax.inject.Inject; import javax.annotation.Nullable; -public class PriceNodeHttpClient extends HttpClient { +public class PriceNodeHttpClient extends HttpClientImpl { @Inject public PriceNodeHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); diff --git a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java index e6f51fe6cfd..3700ab21e7f 100644 --- a/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/AssetTxProofHttpClient.java @@ -17,7 +17,7 @@ package bisq.core.trade.txproof; -import bisq.network.http.IHttpClient; +import bisq.network.http.HttpClient; -public interface AssetTxProofHttpClient extends IHttpClient { +public interface AssetTxProofHttpClient extends HttpClient { } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java index bf78570bdd4..dce87473837 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofHttpClient.java @@ -20,14 +20,14 @@ import bisq.core.trade.txproof.AssetTxProofHttpClient; import bisq.network.Socks5ProxyProvider; -import bisq.network.http.HttpClient; +import bisq.network.http.HttpClientImpl; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; @Slf4j -public class XmrTxProofHttpClient extends HttpClient implements AssetTxProofHttpClient { +public class XmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { @Inject public XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { super(socks5ProxyProvider); diff --git a/p2p/src/main/java/bisq/network/http/HttpClient.java b/p2p/src/main/java/bisq/network/http/HttpClient.java index 3bd3b583fad..5f33db2a23b 100644 --- a/p2p/src/main/java/bisq/network/http/HttpClient.java +++ b/p2p/src/main/java/bisq/network/http/HttpClient.java @@ -17,204 +17,24 @@ package bisq.network.http; -import bisq.network.Socks5ProxyProvider; - -import bisq.common.app.Version; - -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; -import org.apache.http.ssl.SSLContexts; - -import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; - -import javax.inject.Inject; - -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.URL; - -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; -import static com.google.common.base.Preconditions.checkNotNull; - -// TODO close connection if failing -@Slf4j -public class HttpClient implements IHttpClient { - @Nullable - private Socks5ProxyProvider socks5ProxyProvider; - @Getter - private String baseUrl; - private boolean ignoreSocks5Proxy; - private final String uid; +public interface HttpClient { + void setBaseUrl(String baseUrl); - @Inject - public HttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { - this.socks5ProxyProvider = socks5ProxyProvider; - uid = UUID.randomUUID().toString(); - } + void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy); - public HttpClient(String baseUrl) { - this.baseUrl = baseUrl; - uid = UUID.randomUUID().toString(); - } + String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException; - @Override - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - @Override - public void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy) { - this.ignoreSocks5Proxy = ignoreSocks5Proxy; - } - - @Override - public String requestWithGET(String param, + String requestWithGETNoProxy(String param, @Nullable String headerKey, - @Nullable String headerValue) throws IOException { - checkNotNull(baseUrl, "baseUrl must be set before calling requestWithGET"); - - Socks5Proxy socks5Proxy = null; - if (socks5ProxyProvider != null) { - // We use the custom socks5ProxyHttp. If not set we request socks5ProxyProvider.getSocks5ProxyBtc() - // which delivers the btc proxy if set, otherwise the internal proxy. - socks5Proxy = socks5ProxyProvider.getSocks5ProxyHttp(); - if (socks5Proxy == null) - socks5Proxy = socks5ProxyProvider.getSocks5Proxy(); - } - if (ignoreSocks5Proxy || socks5Proxy == null || baseUrl.contains("localhost")) { - log.debug("Use clear net for HttpClient. socks5Proxy={}, ignoreSocks5Proxy={}, baseUrl={}", - socks5Proxy, ignoreSocks5Proxy, baseUrl); - return requestWithGETNoProxy(param, headerKey, headerValue); - } else { - log.debug("Use socks5Proxy for HttpClient: " + socks5Proxy); - return doRequestWithGETProxy(param, socks5Proxy, headerKey, headerValue); - } - } - - /** - * Make an HTTP Get request directly (not routed over socks5 proxy). - */ - @Override - public String requestWithGETNoProxy(String param, - @Nullable String headerKey, - @Nullable String headerValue) throws IOException { - HttpURLConnection connection = null; - log.debug("Executing HTTP request " + baseUrl + param + " proxy: none."); - URL url = new URL(baseUrl + param); - try { - connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(30)); - connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(30)); - connection.setRequestProperty("User-Agent", "bisq/" + Version.VERSION); - if (headerKey != null && headerValue != null) - connection.setRequestProperty(headerKey, headerValue); - - if (connection.getResponseCode() == 200) { - return convertInputStreamToString(connection.getInputStream()); - } else { - String error = convertInputStreamToString(connection.getErrorStream()); - connection.getErrorStream().close(); - throw new HttpException(error); - } - } catch (Throwable t) { - final String message = "Error at requestWithGETNoProxy with URL: " + (baseUrl + param) + ". Throwable=" + t.getMessage(); - log.error(message); - throw new IOException(message); - } finally { - try { - if (connection != null) - connection.getInputStream().close(); - } catch (Throwable ignore) { - } - } - } - - @Override - public String getUid() { - return uid; - } - - - /** - * Make an HTTP Get request routed over socks5 proxy. - */ - private String doRequestWithGETProxy(String param, - Socks5Proxy socks5Proxy, - @Nullable String headerKey, - @Nullable String headerValue) throws IOException { - log.debug("requestWithGETProxy param=" + param); - // This code is adapted from: - // http://stackoverflow.com/a/25203021/5616248 - - // Register our own SocketFactories to override createSocket() and connectSocket(). - // connectSocket does NOT resolve hostname before passing it to proxy. - Registry reg = RegistryBuilder.create() - .register("http", new SocksConnectionSocketFactory()) - .register("https", new SocksSSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build(); - - // Use FakeDNSResolver if not resolving DNS locally. - // This prevents a local DNS lookup (which would be ignored anyway) - PoolingHttpClientConnectionManager cm = socks5Proxy.resolveAddrLocally() ? - new PoolingHttpClientConnectionManager(reg) : - new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver()); - try (CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build()) { - InetSocketAddress socksAddress = new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort()); - - // remove me: Use this to test with system-wide Tor proxy, or change port for another proxy. - // InetSocketAddress socksAddress = new InetSocketAddress("127.0.0.1", 9050); - - HttpClientContext context = HttpClientContext.create(); - context.setAttribute("socks.address", socksAddress); - - HttpGet request = new HttpGet(baseUrl + param); - if (headerKey != null && headerValue != null) - request.setHeader(headerKey, headerValue); - - log.debug("Executing request " + request + " proxy: " + socksAddress); - try (CloseableHttpResponse response = httpclient.execute(request, context)) { - return convertInputStreamToString(response.getEntity().getContent()); - } - } catch (Throwable t) { - throw new IOException("Error at requestWithGETProxy with URL: " + (baseUrl + param) + ". Throwable=" + t.getMessage()); - } - } + @Nullable String headerValue) throws IOException; - private String convertInputStreamToString(InputStream inputStream) throws IOException { - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); - StringBuilder stringBuilder = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - stringBuilder.append(line); - } - return stringBuilder.toString(); - } + String getUid(); - @Override - public String toString() { - return "HttpClient{" + - "socks5ProxyProvider=" + socks5ProxyProvider + - ", baseUrl='" + baseUrl + '\'' + - ", ignoreSocks5Proxy=" + ignoreSocks5Proxy + - '}'; - } + String getBaseUrl(); } diff --git a/p2p/src/main/java/bisq/network/http/HttpClientImpl.java b/p2p/src/main/java/bisq/network/http/HttpClientImpl.java new file mode 100644 index 00000000000..e85c7e7efff --- /dev/null +++ b/p2p/src/main/java/bisq/network/http/HttpClientImpl.java @@ -0,0 +1,220 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.network.http; + +import bisq.network.Socks5ProxyProvider; + +import bisq.common.app.Version; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; + +import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy; + +import javax.inject.Inject; + +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.URL; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +// TODO close connection if failing +@Slf4j +public class HttpClientImpl implements HttpClient { + @Nullable + private Socks5ProxyProvider socks5ProxyProvider; + @Getter + private String baseUrl; + private boolean ignoreSocks5Proxy; + private final String uid; + + @Inject + public HttpClientImpl(@Nullable Socks5ProxyProvider socks5ProxyProvider) { + this.socks5ProxyProvider = socks5ProxyProvider; + uid = UUID.randomUUID().toString(); + } + + public HttpClientImpl(String baseUrl) { + this.baseUrl = baseUrl; + uid = UUID.randomUUID().toString(); + } + + @Override + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + @Override + public void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy) { + this.ignoreSocks5Proxy = ignoreSocks5Proxy; + } + + @Override + public String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { + checkNotNull(baseUrl, "baseUrl must be set before calling requestWithGET"); + + Socks5Proxy socks5Proxy = null; + if (socks5ProxyProvider != null) { + // We use the custom socks5ProxyHttp. If not set we request socks5ProxyProvider.getSocks5ProxyBtc() + // which delivers the btc proxy if set, otherwise the internal proxy. + socks5Proxy = socks5ProxyProvider.getSocks5ProxyHttp(); + if (socks5Proxy == null) + socks5Proxy = socks5ProxyProvider.getSocks5Proxy(); + } + if (ignoreSocks5Proxy || socks5Proxy == null || baseUrl.contains("localhost")) { + log.debug("Use clear net for HttpClient. socks5Proxy={}, ignoreSocks5Proxy={}, baseUrl={}", + socks5Proxy, ignoreSocks5Proxy, baseUrl); + return requestWithGETNoProxy(param, headerKey, headerValue); + } else { + log.debug("Use socks5Proxy for HttpClient: " + socks5Proxy); + return doRequestWithGETProxy(param, socks5Proxy, headerKey, headerValue); + } + } + + /** + * Make an HTTP Get request directly (not routed over socks5 proxy). + */ + @Override + public String requestWithGETNoProxy(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { + HttpURLConnection connection = null; + log.debug("Executing HTTP request " + baseUrl + param + " proxy: none."); + URL url = new URL(baseUrl + param); + try { + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); + connection.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(30)); + connection.setReadTimeout((int) TimeUnit.SECONDS.toMillis(30)); + connection.setRequestProperty("User-Agent", "bisq/" + Version.VERSION); + if (headerKey != null && headerValue != null) + connection.setRequestProperty(headerKey, headerValue); + + if (connection.getResponseCode() == 200) { + return convertInputStreamToString(connection.getInputStream()); + } else { + String error = convertInputStreamToString(connection.getErrorStream()); + connection.getErrorStream().close(); + throw new HttpException(error); + } + } catch (Throwable t) { + final String message = "Error at requestWithGETNoProxy with URL: " + (baseUrl + param) + ". Throwable=" + t.getMessage(); + log.error(message); + throw new IOException(message); + } finally { + try { + if (connection != null) + connection.getInputStream().close(); + } catch (Throwable ignore) { + } + } + } + + @Override + public String getUid() { + return uid; + } + + + /** + * Make an HTTP Get request routed over socks5 proxy. + */ + private String doRequestWithGETProxy(String param, + Socks5Proxy socks5Proxy, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { + log.debug("requestWithGETProxy param=" + param); + // This code is adapted from: + // http://stackoverflow.com/a/25203021/5616248 + + // Register our own SocketFactories to override createSocket() and connectSocket(). + // connectSocket does NOT resolve hostname before passing it to proxy. + Registry reg = RegistryBuilder.create() + .register("http", new SocksConnectionSocketFactory()) + .register("https", new SocksSSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build(); + + // Use FakeDNSResolver if not resolving DNS locally. + // This prevents a local DNS lookup (which would be ignored anyway) + PoolingHttpClientConnectionManager cm = socks5Proxy.resolveAddrLocally() ? + new PoolingHttpClientConnectionManager(reg) : + new PoolingHttpClientConnectionManager(reg, new FakeDnsResolver()); + try (CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build()) { + InetSocketAddress socksAddress = new InetSocketAddress(socks5Proxy.getInetAddress(), socks5Proxy.getPort()); + + // remove me: Use this to test with system-wide Tor proxy, or change port for another proxy. + // InetSocketAddress socksAddress = new InetSocketAddress("127.0.0.1", 9050); + + HttpClientContext context = HttpClientContext.create(); + context.setAttribute("socks.address", socksAddress); + + HttpGet request = new HttpGet(baseUrl + param); + if (headerKey != null && headerValue != null) + request.setHeader(headerKey, headerValue); + + log.debug("Executing request " + request + " proxy: " + socksAddress); + try (CloseableHttpResponse response = httpclient.execute(request, context)) { + return convertInputStreamToString(response.getEntity().getContent()); + } + } catch (Throwable t) { + throw new IOException("Error at requestWithGETProxy with URL: " + (baseUrl + param) + ". Throwable=" + t.getMessage()); + } + } + + private String convertInputStreamToString(InputStream inputStream) throws IOException { + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + return stringBuilder.toString(); + } + + @Override + public String toString() { + return "HttpClient{" + + "socks5ProxyProvider=" + socks5ProxyProvider + + ", baseUrl='" + baseUrl + '\'' + + ", ignoreSocks5Proxy=" + ignoreSocks5Proxy + + '}'; + } +} diff --git a/p2p/src/main/java/bisq/network/http/IHttpClient.java b/p2p/src/main/java/bisq/network/http/IHttpClient.java deleted file mode 100644 index 1fde6522527..00000000000 --- a/p2p/src/main/java/bisq/network/http/IHttpClient.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.network.http; - -import java.io.IOException; - -import javax.annotation.Nullable; - -public interface IHttpClient { - void setBaseUrl(String baseUrl); - - void setIgnoreSocks5Proxy(boolean ignoreSocks5Proxy); - - String requestWithGET(String param, - @Nullable String headerKey, - @Nullable String headerValue) throws IOException; - - String requestWithGETNoProxy(String param, - @Nullable String headerKey, - @Nullable String headerValue) throws IOException; - - String getUid(); - - String getBaseUrl(); -} diff --git a/p2p/src/main/java/bisq/network/p2p/P2PModule.java b/p2p/src/main/java/bisq/network/p2p/P2PModule.java index 61c63f1de02..896fe8098c2 100644 --- a/p2p/src/main/java/bisq/network/p2p/P2PModule.java +++ b/p2p/src/main/java/bisq/network/p2p/P2PModule.java @@ -18,6 +18,8 @@ package bisq.network.p2p; import bisq.network.Socks5ProxyProvider; +import bisq.network.http.HttpClient; +import bisq.network.http.HttpClientImpl; import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.NetworkNode; import bisq.network.p2p.peers.BanList; @@ -69,6 +71,7 @@ protected void configure() { bind(BanList.class).in(Singleton.class); bind(NetworkNode.class).toProvider(NetworkNodeProvider.class).in(Singleton.class); bind(Socks5ProxyProvider.class).in(Singleton.class); + bind(HttpClient.class).to(HttpClientImpl.class); requestStaticInjection(Connection.class); From d1379d1b6d02093c27656dbb01eaf4789c1c116d Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 12:51:44 -0500 Subject: [PATCH 75/85] Fix tests --- .../txproof/xmr/XmrTxProofParserTest.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java index bf0b7fb75d4..ae2354bd99c 100644 --- a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java @@ -18,6 +18,7 @@ public class XmrTxProofParserTest { private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f"; private String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF"; + private XmrTxProofParser parser; @Before public void prepareMocksAndObjects() { @@ -39,41 +40,43 @@ public void prepareMocksAndObjects() { amount, tradeDate, autoConfirmSettings); + + parser = new XmrTxProofParser(); } @Test public void testJsonRoot() { // checking what happens when bad input is provided - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "invalid json data").getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "").getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "[]").getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "{}").getDetail() == XmrTxProofRequest.Detail.API_INVALID); } @Test public void testJsonTopLevel() { // testing the top level fields: data and status - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "{'data':{'title':''},'status':'fail'}") .getDetail() == XmrTxProofRequest.Detail.TX_NOT_FOUND); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "{'data':{'title':''},'missingstatus':'success'}") .getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "{'missingdata':{'title':''},'status':'success'}") .getDetail() == XmrTxProofRequest.Detail.API_INVALID); } @Test public void testJsonAddress() { - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "{'data':{'missingaddress':'irrelevant'},'status':'success'}") .getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, + assertTrue(parser.parse(xmrTxProofModel, "{'data':{'address':'e957dac7'},'status':'success'}") .getDetail() == XmrTxProofRequest.Detail.ADDRESS_INVALID); } @@ -81,11 +84,11 @@ public void testJsonAddress() { @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_hash).getDetail() + assertTrue(parser.parse(xmrTxProofModel, missing_tx_hash).getDetail() == XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_hash).getDetail() + assertTrue(parser.parse(xmrTxProofModel, invalid_tx_hash).getDetail() == XmrTxProofRequest.Detail.TX_HASH_INVALID); } @@ -93,13 +96,13 @@ public void testJsonTxHash() { public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_key).getDetail() + assertTrue(parser.parse(xmrTxProofModel, missing_tx_key).getDetail() == XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_key).getDetail() + assertTrue(parser.parse(xmrTxProofModel, invalid_tx_key).getDetail() == XmrTxProofRequest.Detail.TX_KEY_INVALID); } @@ -108,14 +111,14 @@ public void testJsonTxTimestamp() { String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail() + assertTrue(parser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail() == XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail() + assertTrue(parser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail() == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); } @@ -133,28 +136,28 @@ public void testJsonTxConfirmation() { "'viewkey':'" + txKey + "', " + "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + "}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json) + assertTrue(parser.parse(xmrTxProofModel, json) == XmrTxProofRequest.Result.SUCCESS); json = json.replaceFirst("777", "0"); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getDetail() + assertTrue(parser.parse(xmrTxProofModel, json).getDetail() == XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS); json = json.replaceFirst("100000000000", "100000000001"); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getDetail() + assertTrue(parser.parse(xmrTxProofModel, json).getDetail() == XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, json).getDetail() + assertTrue(parser.parse(xmrTxProofModel, json).getDetail() == XmrTxProofRequest.Detail.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertTrue(XmrTxProofParser.parse(xmrTxProofModel, failedJson).getDetail() + assertTrue(parser.parse(xmrTxProofModel, failedJson).getDetail() == XmrTxProofRequest.Detail.API_INVALID); } } From 391852b5da42bb172d4835c36821911abcbb5965 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 12:55:13 -0500 Subject: [PATCH 76/85] Add DevTestXmrTxProofHttpClient --- .../xmr/test/DevTestXmrTxProofHttpClient.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java new file mode 100644 index 00000000000..48de7c27504 --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java @@ -0,0 +1,102 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof.xmr.test; + +import bisq.core.trade.txproof.AssetTxProofHttpClient; + +import bisq.network.Socks5ProxyProvider; +import bisq.network.http.HttpClientImpl; + +import javax.inject.Inject; + +import java.io.IOException; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +/** + * This should help to test error scenarios in dev testing the app. This is additional to unit test which test the + * correct data but do not test the context of the results and how it behaves in the UI. + * + * You have to change the binding in TradeModule to + * bind(AssetTxProofHttpClient.class).to(DevTestXmrTxProofHttpClient.class); to use that class. + * + * This class can be removed once done testing, but as multiple devs are testing its useful to share it for now. + */ +@Slf4j +public class DevTestXmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { + enum TestCase { + SUCCESS, + INVALID_ADDRESS, + STATUS_FAIL + } + + @Inject + public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { + super(socks5ProxyProvider); + } + + @Override + public String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { + TestCase testCase = TestCase.SUCCESS; + switch (testCase) { + case SUCCESS: + return validJson(); + case INVALID_ADDRESS: + return validJson().replace("590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571", + "invalidAddress"); + case STATUS_FAIL: + return validJson().replace("success", + "fail"); + default: + return testCase.toString(); + } + } + + private String validJson() { + return "{\n" + + " \"data\": {\n" + + " \"address\": \"590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571\",\n" + + " \"outputs\": [\n" + + " {\n" + + " \"amount\": 8902597360000,\n" + + " \"match\": true,\n" + + " \"output_idx\": 0,\n" + + " \"output_pubkey\": \"2b6d2296f2591c198cd1aa47de9a5d74270963412ed30bbcc63b8eff29f0d43e\"\n" + + " },\n" + + " {\n" + + " \"amount\": 0,\n" + + " \"match\": false,\n" + + " \"output_idx\": 1,\n" + + " \"output_pubkey\": \"f53271624847507d80b746e91e689e88bc41678d55246275f5ad3c0f7e8a9ced\"\n" + + " }\n" + + " ],\n" + + " \"tx_confirmations\": 201287,\n" + + " \"tx_hash\": \"5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802\",\n" + + " \"tx_prove\": true,\n" + + " \"tx_timestamp\": 1574922644,\n" + + " \"viewkey\": \"f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906\"\n" + + " },\n" + + " \"status\": \"success\"\n" + + "} "; + } + +} From 7b2823d835c95f05e61e535d07d648017a156f08 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 14:48:16 -0500 Subject: [PATCH 77/85] Add more tests for date check --- .../txproof/xmr/XmrTxProofParserTest.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java index ae2354bd99c..a213ce1cec4 100644 --- a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java @@ -10,6 +10,8 @@ import org.junit.Before; import org.junit.Test; +import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class XmrTxProofParserTest { @@ -19,11 +21,12 @@ public class XmrTxProofParserTest { private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f"; private String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF"; private XmrTxProofParser parser; + private Date tradeDate; @Before public void prepareMocksAndObjects() { long amount = 100000000000L; - Date tradeDate = Date.from(Instant.now()); + tradeDate = new Date(1574922644000L); String serviceAddress = "127.0.0.1:8081"; AutoConfirmSettings autoConfirmSettings = new AutoConfirmSettings(true, 10, @@ -120,6 +123,32 @@ public void testJsonTxTimestamp() { "'tx_timestamp':'12345'}, 'status':'success'}"; assertTrue(parser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail() == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + + long tradeTimeSec = tradeDate.getTime() / 1000; + String ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE - 1); + String invalid_tx_timestamp_1ms_too_old = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "', " + + "'viewkey':'" + txKey + "'," + + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; + assertTrue(parser.parse(xmrTxProofModel, invalid_tx_timestamp_1ms_too_old).getDetail() + == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + + ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE); + String valid_tx_timestamp_exact_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "', " + + "'viewkey':'" + txKey + "'," + + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; + XmrTxProofRequest.Result parse = parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE); + assertFalse(parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE).getDetail() + == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + + ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE + 1); + String valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " + + "'tx_hash':'" + txHash + "', " + + "'viewkey':'" + txKey + "'," + + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; + assertFalse(parser.parse(xmrTxProofModel, valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE).getDetail() + == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); } @Test From 8e46ba6e4e20ea0088b1cb451cbe738f49c319db Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 14:49:53 -0500 Subject: [PATCH 78/85] Simplify assertions --- .../txproof/xmr/XmrTxProofParserTest.java | 86 ++++++++----------- 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java index a213ce1cec4..1e8ebbf41b4 100644 --- a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java @@ -11,15 +11,14 @@ import org.junit.Test; import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; public class XmrTxProofParserTest { private XmrTxProofModel xmrTxProofModel; private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae"; private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f"; - private String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF"; private XmrTxProofParser parser; private Date tradeDate; @@ -35,6 +34,7 @@ public void prepareMocksAndObjects() { "XMR"); // TODO using the mocking framework would be better... + String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF"; xmrTxProofModel = new XmrTxProofModel( "dummyTest", txHash, @@ -50,63 +50,59 @@ public void prepareMocksAndObjects() { @Test public void testJsonRoot() { // checking what happens when bad input is provided - assertTrue(parser.parse(xmrTxProofModel, - "invalid json data").getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(parser.parse(xmrTxProofModel, - "").getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(parser.parse(xmrTxProofModel, - "[]").getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(parser.parse(xmrTxProofModel, - "{}").getDetail() == XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, + "invalid json data").getDetail(), XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, + "").getDetail(), XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, + "[]").getDetail(), XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, + "{}").getDetail(), XmrTxProofRequest.Detail.API_INVALID); } @Test public void testJsonTopLevel() { // testing the top level fields: data and status - assertTrue(parser.parse(xmrTxProofModel, + assertSame(parser.parse(xmrTxProofModel, "{'data':{'title':''},'status':'fail'}") - .getDetail() == XmrTxProofRequest.Detail.TX_NOT_FOUND); - assertTrue(parser.parse(xmrTxProofModel, + .getDetail(), XmrTxProofRequest.Detail.TX_NOT_FOUND); + assertSame(parser.parse(xmrTxProofModel, "{'data':{'title':''},'missingstatus':'success'}") - .getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(parser.parse(xmrTxProofModel, + .getDetail(), XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, "{'missingdata':{'title':''},'status':'success'}") - .getDetail() == XmrTxProofRequest.Detail.API_INVALID); + .getDetail(), XmrTxProofRequest.Detail.API_INVALID); } @Test public void testJsonAddress() { - assertTrue(parser.parse(xmrTxProofModel, + assertSame(parser.parse(xmrTxProofModel, "{'data':{'missingaddress':'irrelevant'},'status':'success'}") - .getDetail() == XmrTxProofRequest.Detail.API_INVALID); - assertTrue(parser.parse(xmrTxProofModel, + .getDetail(), XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, "{'data':{'address':'e957dac7'},'status':'success'}") - .getDetail() == XmrTxProofRequest.Detail.ADDRESS_INVALID); + .getDetail(), XmrTxProofRequest.Detail.ADDRESS_INVALID); } @Test public void testJsonTxHash() { String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, missing_tx_hash).getDetail() - == XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, missing_tx_hash).getDetail(), XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, invalid_tx_hash).getDetail() - == XmrTxProofRequest.Detail.TX_HASH_INVALID); + assertSame(parser.parse(xmrTxProofModel, invalid_tx_hash).getDetail(), XmrTxProofRequest.Detail.TX_HASH_INVALID); } @Test public void testJsonTxKey() { String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, missing_tx_key).getDetail() - == XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, missing_tx_key).getDetail(), XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'cdce04'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, invalid_tx_key).getDetail() - == XmrTxProofRequest.Detail.TX_KEY_INVALID); + assertSame(parser.parse(xmrTxProofModel, invalid_tx_key).getDetail(), XmrTxProofRequest.Detail.TX_KEY_INVALID); } @Test @@ -114,15 +110,13 @@ public void testJsonTxTimestamp() { String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "'," + "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail() - == XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail(), XmrTxProofRequest.Detail.API_INVALID); String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'12345'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail() - == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + assertSame(parser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); long tradeTimeSec = tradeDate.getTime() / 1000; String ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE - 1); @@ -130,8 +124,7 @@ public void testJsonTxTimestamp() { "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; - assertTrue(parser.parse(xmrTxProofModel, invalid_tx_timestamp_1ms_too_old).getDetail() - == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + assertSame(parser.parse(xmrTxProofModel, invalid_tx_timestamp_1ms_too_old).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE); String valid_tx_timestamp_exact_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " + @@ -139,16 +132,14 @@ public void testJsonTxTimestamp() { "'viewkey':'" + txKey + "'," + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; XmrTxProofRequest.Result parse = parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE); - assertFalse(parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE).getDetail() - == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE + 1); String valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; - assertFalse(parser.parse(xmrTxProofModel, valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE).getDetail() - == XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); + assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); } @Test @@ -163,30 +154,25 @@ public void testJsonTxConfirmation() { "'tx_confirmations':777, " + "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "', " + - "'tx_timestamp':'" + Long.toString(epochDate) + "'}" + + "'tx_timestamp':'" + epochDate + "'}" + "}"; - assertTrue(parser.parse(xmrTxProofModel, json) - == XmrTxProofRequest.Result.SUCCESS); + assertSame(parser.parse(xmrTxProofModel, json), XmrTxProofRequest.Result.SUCCESS); json = json.replaceFirst("777", "0"); - assertTrue(parser.parse(xmrTxProofModel, json).getDetail() - == XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS); + assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS); json = json.replaceFirst("100000000000", "100000000001"); - assertTrue(parser.parse(xmrTxProofModel, json).getDetail() - == XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); + assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); // Revert change of amount json = json.replaceFirst("100000000001", "100000000000"); json = json.replaceFirst("'match':true", "'match':false"); - assertTrue(parser.parse(xmrTxProofModel, json).getDetail() - == XmrTxProofRequest.Detail.NO_MATCH_FOUND); + assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.NO_MATCH_FOUND); } @Test public void testJsonFail() { String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertTrue(parser.parse(xmrTxProofModel, failedJson).getDetail() - == XmrTxProofRequest.Detail.API_INVALID); + assertSame(parser.parse(xmrTxProofModel, failedJson).getDetail(), XmrTxProofRequest.Detail.API_INVALID); } } From 109f298863ffc1c2f27630836723351fc4837612 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 15:17:52 -0500 Subject: [PATCH 79/85] Add more test cases to DevTestXmrTxProofHttpClient Tested sequence: 1. waiting for response 2. receive TX_NOT_FOUND 3. receive PENDING_CONFIRMATIONS with 0 conf counting up to defined requiredConf 4. success once required confs reached - Fix bug with missing persist call - Revert PENDING results to null when read from persistence as we dont want to show latest pending state. - Remove unused API_FAILURE --- core/src/main/java/bisq/core/trade/Trade.java | 9 +- .../xmr/DevTestXmrTxProofHttpClient.java | 199 ++++++++++++++++++ .../trade/txproof/xmr/XmrTxProofParser.java | 4 +- .../trade/txproof/xmr/XmrTxProofRequest.java | 10 +- .../xmr/test/DevTestXmrTxProofHttpClient.java | 102 --------- 5 files changed, 217 insertions(+), 107 deletions(-) create mode 100644 core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java delete mode 100644 core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index 468f87658c2..a00fe2dca89 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -597,7 +597,13 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv trade.setLockTime(proto.getLockTime()); trade.setLastRefreshRequestDate(proto.getLastRefreshRequestDate()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); - trade.setAssetTxProofResult(ProtoUtil.enumFromProto(AssetTxProofResult.class, proto.getAssetTxProofResult())); + + AssetTxProofResult persistedAssetTxProofResult = ProtoUtil.enumFromProto(AssetTxProofResult.class, proto.getAssetTxProofResult()); + // We do not want to show the user the last pending state when he starts up the app again, so we clear it. + if (persistedAssetTxProofResult == AssetTxProofResult.PENDING) { + persistedAssetTxProofResult = null; + } + trade.setAssetTxProofResult(persistedAssetTxProofResult); trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) @@ -884,6 +890,7 @@ public void setErrorMessage(String errorMessage) { public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResult) { this.assetTxProofResult = assetTxProofResult; assetTxProofResultUpdateProperty.set(assetTxProofResultUpdateProperty.get() + 1); + persist(); } diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java new file mode 100644 index 00000000000..cd9825187ce --- /dev/null +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java @@ -0,0 +1,199 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.trade.txproof.xmr; + +import bisq.core.trade.txproof.AssetTxProofHttpClient; + +import bisq.network.Socks5ProxyProvider; +import bisq.network.http.HttpClientImpl; + +import bisq.common.app.DevEnv; + +import javax.inject.Inject; + +import java.io.IOException; + +import java.util.Date; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static bisq.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; + +/** + * This should help to test error scenarios in dev testing the app. This is additional to unit test which test the + * correct data but do not test the context of the results and how it behaves in the UI. + * + * You have to change the binding in TradeModule to + * bind(AssetTxProofHttpClient.class).to(DevTestXmrTxProofHttpClient.class); to use that class. + * + * This class can be removed once done testing, but as multiple devs are testing its useful to share it for now. + */ +@Slf4j +public class DevTestXmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { + enum ApiInvalidDetails { + EMPTY_JSON, + MISSING_DATA, + MISSING_STATUS, + UNHANDLED_STATUS, + MISSING_ADDRESS, + MISSING_TX_ID, + MISSING_VIEW_KEY, + MISSING_TS, + MISSING_CONF, + EXCEPTION + } + + @Inject + public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { + super(socks5ProxyProvider); + } + + static int counter; + + @Override + public String requestWithGET(String param, + @Nullable String headerKey, + @Nullable String headerValue) throws IOException { + + XmrTxProofRequest.Result result = XmrTxProofRequest.Result.PENDING; + XmrTxProofRequest.Detail detail = XmrTxProofRequest.Detail.TX_NOT_FOUND; + ApiInvalidDetails apiInvalidDetails = ApiInvalidDetails.EXCEPTION; + + int delay = counter == 0 ? 2000 : 100; + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (counter >= 2) { + detail = XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(counter - 2); + } + counter++; + switch (result) { + case PENDING: + switch (detail) { + case TX_NOT_FOUND: + return validJson().replace("success", + "fail"); + case PENDING_CONFIRMATIONS: + return validJson().replace("201287", + String.valueOf(detail.getNumConfirmations())); + } + break; + case SUCCESS: + return validJson(); + case FAILED: + switch (detail) { + case TX_HASH_INVALID: + return validJson().replace("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802", + "-"); + case TX_KEY_INVALID: + return validJson().replace("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906", + "-"); + case ADDRESS_INVALID: + return validJson().replace("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802", + "-"); + case NO_MATCH_FOUND: + return validJson().replace("match\": true", + "match\": false"); + case AMOUNT_NOT_MATCHING: + return validJson().replace("8902597360000", + "18902597360000"); + case TRADE_DATE_NOT_MATCHING: + DevEnv.setDevMode(false); + long date = (new Date(1574922644 * 1000L).getTime() - (MAX_DATE_TOLERANCE * 1000L + 1)) / 1000; + String replace = validJson().replace("1574922644", + String.valueOf(date)); + return replace; + } + case ERROR: + switch (detail) { + /* case CONNECTION_FAILURE: + break;*/ + case API_INVALID: + switch (apiInvalidDetails) { + case EMPTY_JSON: + return null; + case MISSING_DATA: + return validJson().replace("data", + "missing"); + case MISSING_STATUS: + return validJson().replace("status", + "missing"); + case UNHANDLED_STATUS: + return validJson().replace("success", + "missing"); + case MISSING_ADDRESS: + return validJson().replace("address", + "missing"); + case MISSING_TX_ID: + return validJson().replace("tx_hash", + "missing"); + case MISSING_VIEW_KEY: + return validJson().replace("viewkey", + "missing"); + case MISSING_TS: + return validJson().replace("tx_timestamp", + "missing"); + case MISSING_CONF: + return validJson().replace("tx_confirmations", + "missing"); + case EXCEPTION: + return validJson().replace("} ", + ""); + } + break; + /* case NO_RESULTS_TIMEOUT: + break;*/ + } + } + return validJson(); + } + + private String validJson() { + return "{\n" + + " \"data\": {\n" + + " \"address\": \"590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571\",\n" + + " \"outputs\": [\n" + + " {\n" + + " \"amount\": 8902597360000,\n" + + " \"match\": true,\n" + + " \"output_idx\": 0,\n" + + " \"output_pubkey\": \"2b6d2296f2591c198cd1aa47de9a5d74270963412ed30bbcc63b8eff29f0d43e\"\n" + + " },\n" + + " {\n" + + " \"amount\": 0,\n" + + " \"match\": false,\n" + + " \"output_idx\": 1,\n" + + " \"output_pubkey\": \"f53271624847507d80b746e91e689e88bc41678d55246275f5ad3c0f7e8a9ced\"\n" + + " }\n" + + " ],\n" + + " \"tx_confirmations\": 201287,\n" + + " \"tx_hash\": \"5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802\",\n" + + " \"tx_prove\": true,\n" + + " \"tx_timestamp\": 1574922644,\n" + + " \"viewkey\": \"f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906\"\n" + + " },\n" + + " \"status\": \"success\"\n" + + "} "; + } + +} diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index ae223d22698..dda5f924c84 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -35,6 +35,8 @@ @Slf4j public class XmrTxProofParser implements AssetTxProofParser { + public static final long MAX_DATE_TOLERANCE = TimeUnit.HOURS.toSeconds(2); + XmrTxProofParser() { } @@ -110,7 +112,7 @@ public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { long tradeDateSeconds = model.getTradeDate().getTime() / 1000; long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync - if (difference > TimeUnit.HOURS.toSeconds(2) && !DevEnv.isDevMode()) { + if (difference > MAX_DATE_TOLERANCE && !DevEnv.isDevMode()) { log.warn("tx_timestamp {}, tradeDate: {}, difference {}", jsonTimestamp.getAsLong(), tradeDateSeconds, difference); return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java index f0b84d6060a..c853af2410c 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequest.java @@ -87,7 +87,6 @@ enum Detail { // Error states CONNECTION_FAILURE, - API_FAILURE, API_INVALID, // Failure states @@ -194,8 +193,13 @@ public void requestFromService(Consumer resultHandler, FaultHandler faul "&txprove=1"; log.info("Param {} for {}", param, this); String json = httpClient.requestWithGET(param, "User-Agent", "bisq/" + Version.VERSION); - String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); - log.info("Response json from {}\n{}", this, prettyJson); + try { + String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); + log.info("Response json from {}\n{}", this, prettyJson); + } catch (Throwable error) { + log.error("Pretty rint caused a {}}: raw josn={}", error, json); + } + Result result = parser.parse(model, json); log.info("Result from {}\n{}", this, result); return result; diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java deleted file mode 100644 index 48de7c27504..00000000000 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/test/DevTestXmrTxProofHttpClient.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.txproof.xmr.test; - -import bisq.core.trade.txproof.AssetTxProofHttpClient; - -import bisq.network.Socks5ProxyProvider; -import bisq.network.http.HttpClientImpl; - -import javax.inject.Inject; - -import java.io.IOException; - -import lombok.extern.slf4j.Slf4j; - -import javax.annotation.Nullable; - -/** - * This should help to test error scenarios in dev testing the app. This is additional to unit test which test the - * correct data but do not test the context of the results and how it behaves in the UI. - * - * You have to change the binding in TradeModule to - * bind(AssetTxProofHttpClient.class).to(DevTestXmrTxProofHttpClient.class); to use that class. - * - * This class can be removed once done testing, but as multiple devs are testing its useful to share it for now. - */ -@Slf4j -public class DevTestXmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { - enum TestCase { - SUCCESS, - INVALID_ADDRESS, - STATUS_FAIL - } - - @Inject - public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProvider) { - super(socks5ProxyProvider); - } - - @Override - public String requestWithGET(String param, - @Nullable String headerKey, - @Nullable String headerValue) throws IOException { - TestCase testCase = TestCase.SUCCESS; - switch (testCase) { - case SUCCESS: - return validJson(); - case INVALID_ADDRESS: - return validJson().replace("590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571", - "invalidAddress"); - case STATUS_FAIL: - return validJson().replace("success", - "fail"); - default: - return testCase.toString(); - } - } - - private String validJson() { - return "{\n" + - " \"data\": {\n" + - " \"address\": \"590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571\",\n" + - " \"outputs\": [\n" + - " {\n" + - " \"amount\": 8902597360000,\n" + - " \"match\": true,\n" + - " \"output_idx\": 0,\n" + - " \"output_pubkey\": \"2b6d2296f2591c198cd1aa47de9a5d74270963412ed30bbcc63b8eff29f0d43e\"\n" + - " },\n" + - " {\n" + - " \"amount\": 0,\n" + - " \"match\": false,\n" + - " \"output_idx\": 1,\n" + - " \"output_pubkey\": \"f53271624847507d80b746e91e689e88bc41678d55246275f5ad3c0f7e8a9ced\"\n" + - " }\n" + - " ],\n" + - " \"tx_confirmations\": 201287,\n" + - " \"tx_hash\": \"5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802\",\n" + - " \"tx_prove\": true,\n" + - " \"tx_timestamp\": 1574922644,\n" + - " \"viewkey\": \"f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906\"\n" + - " },\n" + - " \"status\": \"success\"\n" + - "} "; - } - -} From d3f12552aa0f5661578ecaad5c55ef882a4f4159 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 16:17:46 -0500 Subject: [PATCH 80/85] Update text --- core/src/main/resources/i18n/displayStrings.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties index 44ff1f17bec..cd947f25a17 100644 --- a/core/src/main/resources/i18n/displayStrings.properties +++ b/core/src/main/resources/i18n/displayStrings.properties @@ -575,7 +575,7 @@ portfolio.pending.autoConf=Auto-confirmed portfolio.pending.autoConf.state.xmr.txKeyReused=Transaction key re-used. Please open a dispute. portfolio.pending.autoConf.state.confirmations=Confirmations: {0}/{1} -portfolio.pending.autoConf.state.txNotFound=Transaction not confirmed yet +portfolio.pending.autoConf.state.txNotFound=Transaction not seen in mem-pool yet portfolio.pending.autoConf.state.txKeyOrTxIdInvalid=No valid transaction ID / transaction key portfolio.pending.autoConf.state.filterDisabledFeature=Disabled by developers. From 3492ae42f3342fc90eb8b5c012162d8382789652 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 16:40:08 -0500 Subject: [PATCH 81/85] Cleanup --- .../xmr/DevTestXmrTxProofHttpClient.java | 29 +++++++++++-------- .../txproof/xmr/XmrTxProofParserTest.java | 2 +- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java index cd9825187ce..235e1582fa4 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java @@ -26,8 +26,6 @@ import javax.inject.Inject; -import java.io.IOException; - import java.util.Date; import lombok.extern.slf4j.Slf4j; @@ -65,12 +63,12 @@ public DevTestXmrTxProofHttpClient(@Nullable Socks5ProxyProvider socks5ProxyProv super(socks5ProxyProvider); } - static int counter; + private static int counter; @Override public String requestWithGET(String param, @Nullable String headerKey, - @Nullable String headerValue) throws IOException { + @Nullable String headerValue) { XmrTxProofRequest.Result result = XmrTxProofRequest.Result.PENDING; XmrTxProofRequest.Detail detail = XmrTxProofRequest.Detail.TX_NOT_FOUND; @@ -109,7 +107,7 @@ public String requestWithGET(String param, return validJson().replace("f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906", "-"); case ADDRESS_INVALID: - return validJson().replace("5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802", + return validJson().replace("590f7263428051068bb45cdfcf93407c15b6e291d20c92d0251fcfbf53cc745cdf53319f7d6d7a8e21ea39041aabf31d220a32a875e3ca2087a777f1201c0571", "-"); case NO_MATCH_FOUND: return validJson().replace("match\": true", @@ -120,14 +118,16 @@ public String requestWithGET(String param, case TRADE_DATE_NOT_MATCHING: DevEnv.setDevMode(false); long date = (new Date(1574922644 * 1000L).getTime() - (MAX_DATE_TOLERANCE * 1000L + 1)) / 1000; - String replace = validJson().replace("1574922644", + return validJson().replace("1574922644", String.valueOf(date)); - return replace; + default: + return null; } case ERROR: switch (detail) { - /* case CONNECTION_FAILURE: - break;*/ + case CONNECTION_FAILURE: + // Not part of parser level testing + return null; case API_INVALID: switch (apiInvalidDetails) { case EMPTY_JSON: @@ -159,10 +159,15 @@ public String requestWithGET(String param, case EXCEPTION: return validJson().replace("} ", ""); + default: + return null; } - break; - /* case NO_RESULTS_TIMEOUT: - break;*/ + + case NO_RESULTS_TIMEOUT: + // Not part of parser level testing + return null; + default: + return null; } } return validJson(); diff --git a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java index 1e8ebbf41b4..af91a7d4d33 100644 --- a/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java +++ b/core/src/test/java/bisq/core/trade/txproof/xmr/XmrTxProofParserTest.java @@ -131,7 +131,7 @@ public void testJsonTxTimestamp() { "'tx_hash':'" + txHash + "', " + "'viewkey':'" + txKey + "'," + "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; - XmrTxProofRequest.Result parse = parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE); + parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE); assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE + 1); From 117b5bf87ced73ee69354df68db6654e4626d597 Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 16:50:11 -0500 Subject: [PATCH 82/85] Add default clauses --- .../core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java index 235e1582fa4..b415d354c44 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/DevTestXmrTxProofHttpClient.java @@ -94,8 +94,9 @@ public String requestWithGET(String param, case PENDING_CONFIRMATIONS: return validJson().replace("201287", String.valueOf(detail.getNumConfirmations())); + default: + return null; } - break; case SUCCESS: return validJson(); case FAILED: @@ -169,8 +170,9 @@ public String requestWithGET(String param, default: return null; } + default: + return null; } - return validJson(); } private String validJson() { From bf73ffcfa6dc098efbbf1edb99b782b148e0b4ff Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 18:47:55 -0500 Subject: [PATCH 83/85] Improve looging --- .../java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index dda5f924c84..495ef68a014 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -76,7 +76,8 @@ public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { } else { String expectedAddressHex = CryptoNoteUtils.convertToRawHex(model.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { - log.warn("address {}, expected: {}", jsonAddress.getAsString(), expectedAddressHex); + log.warn("Address from json result (convertToRawHex):\n{}\nExpected (convertToRawHex):\n{}\nRecipient address:\n{}", + jsonAddress.getAsString(), expectedAddressHex, model.getRecipientAddress()); return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.ADDRESS_INVALID); } } From 86821277d93bd1acdb75de985fd6c260d1df4ded Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 20:29:19 -0500 Subject: [PATCH 84/85] Add support for addresses with integrated payment ID --- .../main/java/bisq/asset/CryptoNoteUtils.java | 30 ++++++++++++++----- .../trade/txproof/xmr/XmrTxProofParser.java | 4 ++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java index 7540092465d..d5150353c6e 100644 --- a/assets/src/main/java/bisq/asset/CryptoNoteUtils.java +++ b/assets/src/main/java/bisq/asset/CryptoNoteUtils.java @@ -28,22 +28,38 @@ import java.util.Map; public class CryptoNoteUtils { - public static String convertToRawHex(String address) { + public static String getRawSpendKeyAndViewKey(String address) throws CryptoNoteUtils.CryptoNoteException { try { - byte[] decoded = MoneroBase58.decode(address); - // omit the type (1st byte) and checksum (last 4 byte) - byte[] slice = Arrays.copyOfRange(decoded, 1, decoded.length - 4); + // See https://monerodocs.org/public-address/standard-address/ + byte[] decoded = CryptoNoteUtils.MoneroBase58.decode(address); + // Standard addresses are of length 69 and addresses with integrated payment ID of length 77. + + if (decoded.length <= 65) { + throw new CryptoNoteUtils.CryptoNoteException("The address we received is too short. address=" + address); + } + + // If the length is not as expected but still can be truncated we log an error and continue. + if (decoded.length != 69 && decoded.length != 77) { + System.out.println("The address we received is not in the expected format. address=" + address); + } + + // We remove the network type byte, the checksum (4 bytes) and optionally the payment ID (8 bytes if present) + // So extract the 64 bytes after the first byte, which are the 32 byte public spend key + the 32 byte public view key + byte[] slice = Arrays.copyOfRange(decoded, 1, 65); return Utils.HEX.encode(slice); - } catch (CryptoNoteException e) { - e.printStackTrace(); + } catch (CryptoNoteUtils.CryptoNoteException e) { + throw new CryptoNoteUtils.CryptoNoteException(e); } - return null; } public static class CryptoNoteException extends Exception { CryptoNoteException(String msg) { super(msg); } + + public CryptoNoteException(CryptoNoteException exception) { + super(exception); + } } static class Keccak { diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java index 495ef68a014..cf7b7835cfc 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofParser.java @@ -74,7 +74,7 @@ public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { if (jsonAddress == null) { return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing address field")); } else { - String expectedAddressHex = CryptoNoteUtils.convertToRawHex(model.getRecipientAddress()); + String expectedAddressHex = CryptoNoteUtils.getRawSpendKeyAndViewKey(model.getRecipientAddress()); if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { log.warn("Address from json result (convertToRawHex):\n{}\nExpected (convertToRawHex):\n{}\nRecipient address:\n{}", jsonAddress.getAsString(), expectedAddressHex, model.getRecipientAddress()); @@ -167,6 +167,8 @@ public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { } catch (JsonParseException | NullPointerException e) { return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error(e.toString())); + } catch (CryptoNoteUtils.CryptoNoteException e) { + return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.ADDRESS_INVALID.error(e.toString())); } } } From b17ee93b6f72856637a3618732ebac382af3bd3b Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Wed, 2 Sep 2020 21:27:13 -0500 Subject: [PATCH 85/85] Add listeners for filter change for shutdown all requests and disable settings UI if auto-conf is disabled in filter. --- .../xmr/XmrTxProofRequestsPerTrade.java | 2 + .../trade/txproof/xmr/XmrTxProofService.java | 9 ++++ .../settings/preferences/PreferencesView.java | 43 +++++++++++-------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java index 1a0d346fc76..f49919232b5 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java @@ -35,6 +35,7 @@ import java.util.Set; import java.util.function.Consumer; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** @@ -42,6 +43,7 @@ */ @Slf4j class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { + @Getter private final Trade trade; private final AutoConfirmSettings autoConfirmSettings; private final AssetTxProofHttpClient httpClient; diff --git a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java index d426a0ae076..bd63e1df114 100644 --- a/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java +++ b/core/src/main/java/bisq/core/trade/txproof/xmr/XmrTxProofService.java @@ -87,6 +87,15 @@ public XmrTxProofService(FilterManager filterManager, this.p2PService = p2PService; this.walletsSetup = walletsSetup; this.httpClient = httpClient; + + filterManager.filterProperty().addListener((observable, oldValue, newValue) -> { + if (isAutoConfDisabledByFilter()) { + servicesByTradeId.values().stream().map(XmrTxProofRequestsPerTrade::getTrade).forEach(trade -> + trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED + .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature")))); + shutDown(); + } + }); } diff --git a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java index 17256e7467c..60ab77a3418 100644 --- a/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java +++ b/desktop/src/main/java/bisq/desktop/main/settings/preferences/PreferencesView.java @@ -35,6 +35,7 @@ import bisq.core.btc.wallet.Restrictions; import bisq.core.dao.DaoFacade; import bisq.core.dao.governance.asset.AssetService; +import bisq.core.filter.Filter; import bisq.core.filter.FilterManager; import bisq.core.locale.Country; import bisq.core.locale.CountryUtil; @@ -154,6 +155,8 @@ public class PreferencesView extends ActivatableViewAndModel transactionFeeChangeListener; private final boolean daoOptionsSet; private final boolean displayStandbyModeFeature; + private ChangeListener filterChangeListener; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialisation @@ -207,7 +210,6 @@ public void initialize() { initializeSeparator(); initializeAutoConfirmOptions(); initializeDisplayCurrencies(); - } @@ -651,20 +653,20 @@ private void initializeDaoOptions() { } private void initializeAutoConfirmOptions() { - GridPane subGrid = new GridPane(); - GridPane.setHgrow(subGrid, Priority.ALWAYS); - root.add(subGrid, 2, displayCurrenciesGridRowIndex, 2, 10); - addTitledGroupBg(subGrid, 0, 4, Res.get("setting.preferences.autoConfirmXMR"), 0); + GridPane autoConfirmGridPane = new GridPane(); + GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS); + root.add(autoConfirmGridPane, 2, displayCurrenciesGridRowIndex, 2, 10); + addTitledGroupBg(autoConfirmGridPane, 0, 4, Res.get("setting.preferences.autoConfirmXMR"), 0); int localRowIndex = 0; - autoConfirmXmrToggle = addSlideToggleButton(subGrid, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); + autoConfirmXmrToggle = addSlideToggleButton(autoConfirmGridPane, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE); - autoConfRequiredConfirmationsTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); + autoConfRequiredConfirmationsTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations")); autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(0, DevEnv.isDevMode() ? 100000000 : 1000)); - autoConfTradeLimitTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); + autoConfTradeLimitTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize")); autoConfTradeLimitTf.setValidator(new BtcValidator(formatter)); - autoConfServiceAddressTf = addInputTextField(subGrid, ++localRowIndex, Res.get("setting.preferences.autoConfirmServiceAddresses")); + autoConfServiceAddressTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmServiceAddresses")); autoConfServiceAddressTf.setValidator(GUIUtil.addressRegexValidator()); autoConfServiceAddressTf.setErrorMessage(Res.get("validation.invalidAddressList")); GridPane.setHgrow(autoConfServiceAddressTf, Priority.ALWAYS); @@ -721,8 +723,14 @@ private void initializeAutoConfirmOptions() { } } }; + + filterChangeListener = (observable, oldValue, newValue) -> { + autoConfirmGridPane.setDisable(newValue != null && newValue.isDisableAutoConf()); + }; + autoConfirmGridPane.setDisable(filterManager.getFilter() != null && filterManager.getFilter().isDisableAutoConf()); } + /////////////////////////////////////////////////////////////////////////////////////////// // Activate /////////////////////////////////////////////////////////////////////////////////////////// @@ -980,15 +988,14 @@ private void activateAutoConfirmPreferences() { autoConfRequiredConfirmationsTf.setText(String.valueOf(autoConfirmSettings.getRequiredConfirmations())); autoConfTradeLimitTf.setText(formatter.formatCoin(Coin.valueOf(autoConfirmSettings.getTradeLimit()))); autoConfServiceAddressTf.setText(String.join(", ", autoConfirmSettings.getServiceAddresses())); - autoConfRequiredConfirmationsTf.focusedProperty().addListener(autoConfRequiredConfirmationsFocusOutListener); autoConfTradeLimitTf.textProperty().addListener(autoConfTradeLimitListener); autoConfServiceAddressTf.textProperty().addListener(autoConfServiceAddressListener); autoConfServiceAddressTf.focusedProperty().addListener(autoConfServiceAddressFocusOutListener); - autoConfirmXmrToggle.setOnAction(e -> { preferences.setAutoConfEnabled(autoConfirmSettings.getCurrencyCode(), autoConfirmXmrToggle.isSelected()); }); + filterManager.filterProperty().addListener(filterChangeListener); }); } @@ -1062,11 +1069,13 @@ private void deactivateDaoPreferences() { } private void deactivateAutoConfirmPreferences() { - autoConfirmXmrToggle.setOnAction(null); - autoConfTradeLimitTf.textProperty().removeListener(autoConfTradeLimitListener); - autoConfServiceAddressTf.textProperty().removeListener(autoConfServiceAddressListener); - autoConfServiceAddressTf.focusedProperty().removeListener(autoConfServiceAddressFocusOutListener); - autoConfRequiredConfirmationsTf.focusedProperty().removeListener(autoConfRequiredConfirmationsFocusOutListener); - + preferences.findAutoConfirmSettings("XMR").ifPresent(autoConfirmSettings -> { + autoConfirmXmrToggle.setOnAction(null); + autoConfTradeLimitTf.textProperty().removeListener(autoConfTradeLimitListener); + autoConfServiceAddressTf.textProperty().removeListener(autoConfServiceAddressListener); + autoConfServiceAddressTf.focusedProperty().removeListener(autoConfServiceAddressFocusOutListener); + autoConfRequiredConfirmationsTf.focusedProperty().removeListener(autoConfRequiredConfirmationsFocusOutListener); + filterManager.filterProperty().removeListener(filterChangeListener); + }); } }