From 43e4809d81929143c5d5b1dc53aed911d681605f Mon Sep 17 00:00:00 2001 From: chimp1984 Date: Sun, 26 Jul 2020 18:55:07 -0500 Subject: [PATCH 01/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] - 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/44] 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/44] - 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/44] - 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/44] - 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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/44] 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; }