From 37e812deadbcacbf489254440d1edd04efd68357 Mon Sep 17 00:00:00 2001 From: woodser Date: Fri, 21 Apr 2023 09:55:34 -0400 Subject: [PATCH] remove XmrTxProofService --- .../main/java/haveno/core/api/CoreApi.java | 9 - .../haveno/core/api/CoreWalletsService.java | 60 -- .../haveno/core/app/DomainInitialisation.java | 5 - .../haveno/core/app/HavenoExecutable.java | 2 - .../java/haveno/core/app/HavenoSetup.java | 3 - .../arbitration/ArbitrationManager.java | 2 +- .../main/java/haveno/core/trade/Trade.java | 29 - .../trade/txproof/AssetTxProofHttpClient.java | 23 - .../core/trade/txproof/AssetTxProofModel.java | 21 - .../trade/txproof/AssetTxProofParser.java | 22 - .../trade/txproof/AssetTxProofRequest.java | 31 - .../txproof/AssetTxProofRequestsPerTrade.java | 28 - .../trade/txproof/AssetTxProofResult.java | 103 --- .../trade/txproof/AssetTxProofService.java | 24 - .../txproof/xmr/XmrTxProofHttpClient.java | 30 - .../trade/txproof/xmr/XmrTxProofModel.java | 97 --- .../trade/txproof/xmr/XmrTxProofParser.java | 172 ---- .../trade/txproof/xmr/XmrTxProofRequest.java | 289 ------ .../xmr/XmrTxProofRequestsPerTrade.java | 338 ------- .../trade/txproof/xmr/XmrTxProofService.java | 390 --------- .../core/xmr/wallet/BtcWalletService.java | 212 ----- .../core/xmr/wallet/TradeWalletService.java | 24 - .../haveno/core/xmr/wallet/TxBroadcaster.java | 146 ---- .../haveno/core/xmr/wallet/WalletService.java | 57 -- .../core/xmr/wallet/XmrWalletService.java | 2 +- .../http/MemPoolSpaceTxBroadcaster.java | 150 ---- .../wallet/http/TxBroadcastHttpClient.java | 30 - .../txproof/xmr/XmrTxProofParserTest.java | 176 ---- .../java/haveno/desktop/app/HavenoApp.java | 12 +- .../windows/BtcEmptyWalletWindow.java | 170 ---- .../windows/ManualPayoutTxWindow.java | 821 ------------------ .../overlays/windows/SetXmrTxKeyWindow.java | 118 --- .../overlays/windows/TradeDetailsWindow.java | 17 - .../steps/buyer/BuyerStep2View.java | 36 - .../steps/buyer/BuyerStep4View.java | 7 +- .../steps/seller/SellerStep3View.java | 52 -- .../java/haveno/desktop/util/GUIUtil.java | 31 - proto/src/main/proto/pb.proto | 3 +- 38 files changed, 5 insertions(+), 3737 deletions(-) delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofHttpClient.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofModel.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofParser.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequest.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequestsPerTrade.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofResult.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/AssetTxProofService.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofHttpClient.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofModel.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofParser.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequest.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java delete mode 100644 core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofService.java delete mode 100644 core/src/main/java/haveno/core/xmr/wallet/TxBroadcaster.java delete mode 100644 core/src/main/java/haveno/core/xmr/wallet/http/MemPoolSpaceTxBroadcaster.java delete mode 100644 core/src/main/java/haveno/core/xmr/wallet/http/TxBroadcastHttpClient.java delete mode 100644 core/src/test/java/haveno/core/trade/txproof/xmr/XmrTxProofParserTest.java delete mode 100644 desktop/src/main/java/haveno/desktop/main/overlays/windows/BtcEmptyWalletWindow.java delete mode 100644 desktop/src/main/java/haveno/desktop/main/overlays/windows/ManualPayoutTxWindow.java delete mode 100644 desktop/src/main/java/haveno/desktop/main/overlays/windows/SetXmrTxKeyWindow.java diff --git a/core/src/main/java/haveno/core/api/CoreApi.java b/core/src/main/java/haveno/core/api/CoreApi.java index 675f1a486b..052e54747c 100644 --- a/core/src/main/java/haveno/core/api/CoreApi.java +++ b/core/src/main/java/haveno/core/api/CoreApi.java @@ -17,7 +17,6 @@ package haveno.core.api; -import com.google.common.util.concurrent.FutureCallback; import haveno.common.app.Version; import haveno.common.config.Config; import haveno.common.crypto.IncorrectPasswordException; @@ -296,14 +295,6 @@ public class CoreApi { return walletsService.getFundingAddresses(); } - public void sendBtc(String address, - String amount, - String txFeeRate, - String memo, - FutureCallback callback) { - walletsService.sendBtc(address, amount, txFeeRate, memo, callback); - } - public Transaction getTransaction(String txId) { return walletsService.getTransaction(txId); } diff --git a/core/src/main/java/haveno/core/api/CoreWalletsService.java b/core/src/main/java/haveno/core/api/CoreWalletsService.java index 0464882996..65513de865 100644 --- a/core/src/main/java/haveno/core/api/CoreWalletsService.java +++ b/core/src/main/java/haveno/core/api/CoreWalletsService.java @@ -20,7 +20,6 @@ package haveno.core.api; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.util.concurrent.FutureCallback; import haveno.common.Timer; import haveno.common.UserThread; import haveno.core.api.model.AddressBalanceInfo; @@ -32,8 +31,6 @@ import haveno.core.user.Preferences; import haveno.core.util.FormattingUtils; import haveno.core.util.coin.CoinFormatter; import haveno.core.xmr.Balances; -import haveno.core.xmr.exceptions.AddressEntryException; -import haveno.core.xmr.exceptions.InsufficientFundsException; import haveno.core.xmr.model.AddressEntry; import haveno.core.xmr.setup.WalletsSetup; import haveno.core.xmr.wallet.BtcWalletService; @@ -44,7 +41,6 @@ import monero.wallet.model.MoneroDestination; import monero.wallet.model.MoneroTxWallet; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; @@ -57,7 +53,6 @@ import javax.inject.Named; import javax.inject.Singleton; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -229,61 +224,6 @@ class CoreWalletsService { .collect(Collectors.toList()); } - void sendBtc(String address, - String amount, - String txFeeRate, - String memo, - FutureCallback callback) { - verifyWalletsAreAvailable(); - verifyEncryptedWalletIsUnlocked(); - - try { - Set fromAddresses = btcWalletService.getAddressEntriesForAvailableBalanceStream() - .map(AddressEntry::getAddressString) - .collect(Collectors.toSet()); - Coin receiverAmount = getValidTransferAmount(amount, btcFormatter); - Coin txFeePerVbyte = getTxFeeRateFromParamOrPreferenceOrFeeService(txFeeRate); - - // TODO Support feeExcluded (or included), default is fee included. - // See WithdrawalView # onWithdraw (and refactor). - Transaction feeEstimationTransaction = - btcWalletService.getFeeEstimationTransactionForMultipleAddresses(fromAddresses, - receiverAmount, - txFeePerVbyte); - if (feeEstimationTransaction == null) - throw new IllegalStateException("could not estimate the transaction fee"); - - Coin dust = btcWalletService.getDust(feeEstimationTransaction); - Coin fee = feeEstimationTransaction.getFee().add(dust); - if (dust.isPositive()) { - fee = feeEstimationTransaction.getFee().add(dust); - log.info("Dust txo ({} sats) was detected, the dust amount has been added to the fee (was {}, now {})", - dust.value, - feeEstimationTransaction.getFee(), - fee.value); - } - log.info("Sending {} BTC to {} with tx fee of {} sats (fee rate {} sats/byte).", - amount, - address, - fee.value, - txFeePerVbyte.value); - btcWalletService.sendFundsForMultipleAddresses(fromAddresses, - address, - receiverAmount, - fee, - null, - tempAesKey, - memo.isEmpty() ? null : memo, - callback); - } catch (AddressEntryException ex) { - log.error("", ex); - throw new IllegalStateException("cannot send btc from any addresses in wallet", ex); - } catch (InsufficientFundsException | InsufficientMoneyException ex) { - log.error("", ex); - throw new IllegalStateException("cannot send btc due to insufficient funds", ex); - } - } - Transaction getTransaction(String txId) { if (txId.length() != 64) throw new IllegalArgumentException(format("%s is not a transaction id", txId)); diff --git a/core/src/main/java/haveno/core/app/DomainInitialisation.java b/core/src/main/java/haveno/core/app/DomainInitialisation.java index d3470229ae..4ee8c0ef17 100644 --- a/core/src/main/java/haveno/core/app/DomainInitialisation.java +++ b/core/src/main/java/haveno/core/app/DomainInitialisation.java @@ -47,7 +47,6 @@ import haveno.core.trade.ClosedTradableManager; import haveno.core.trade.TradeManager; import haveno.core.trade.failed.FailedTradesManager; import haveno.core.trade.statistics.TradeStatisticsManager; -import haveno.core.trade.txproof.xmr.XmrTxProofService; import haveno.core.user.User; import haveno.core.xmr.Balances; import haveno.network.p2p.P2PService; @@ -72,7 +71,6 @@ public class DomainInitialisation { private final TradeManager tradeManager; private final ClosedTradableManager closedTradableManager; private final FailedTradesManager failedTradesManager; - private final XmrTxProofService xmrTxProofService; private final OpenOfferManager openOfferManager; private final Balances balances; private final WalletAppSetup walletAppSetup; @@ -105,7 +103,6 @@ public class DomainInitialisation { TradeManager tradeManager, ClosedTradableManager closedTradableManager, FailedTradesManager failedTradesManager, - XmrTxProofService xmrTxProofService, OpenOfferManager openOfferManager, Balances balances, WalletAppSetup walletAppSetup, @@ -136,7 +133,6 @@ public class DomainInitialisation { this.tradeManager = tradeManager; this.closedTradableManager = closedTradableManager; this.failedTradesManager = failedTradesManager; - this.xmrTxProofService = xmrTxProofService; this.openOfferManager = openOfferManager; this.balances = balances; this.walletAppSetup = walletAppSetup; @@ -182,7 +178,6 @@ public class DomainInitialisation { closedTradableManager.onAllServicesInitialized(); failedTradesManager.onAllServicesInitialized(); - xmrTxProofService.onAllServicesInitialized(); openOfferManager.onAllServicesInitialized(); diff --git a/core/src/main/java/haveno/core/app/HavenoExecutable.java b/core/src/main/java/haveno/core/app/HavenoExecutable.java index 48d5406409..1a0a7ab02d 100644 --- a/core/src/main/java/haveno/core/app/HavenoExecutable.java +++ b/core/src/main/java/haveno/core/app/HavenoExecutable.java @@ -43,7 +43,6 @@ import haveno.core.setup.CoreSetup; import haveno.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import haveno.core.trade.HavenoUtils; import haveno.core.trade.statistics.TradeStatisticsManager; -import haveno.core.trade.txproof.xmr.XmrTxProofService; import haveno.core.xmr.setup.WalletsSetup; import haveno.core.xmr.wallet.BtcWalletService; import haveno.core.xmr.wallet.XmrWalletService; @@ -343,7 +342,6 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven injector.getInstance(PriceFeedService.class).shutDown(); injector.getInstance(ArbitratorManager.class).shutDown(); injector.getInstance(TradeStatisticsManager.class).shutDown(); - injector.getInstance(XmrTxProofService.class).shutDown(); injector.getInstance(AvoidStandbyModeService.class).shutDown(); // shut down open offer manager diff --git a/core/src/main/java/haveno/core/app/HavenoSetup.java b/core/src/main/java/haveno/core/app/HavenoSetup.java index 64ca865d7a..0cc92f0321 100644 --- a/core/src/main/java/haveno/core/app/HavenoSetup.java +++ b/core/src/main/java/haveno/core/app/HavenoSetup.java @@ -57,7 +57,6 @@ import haveno.core.xmr.setup.WalletsSetup; import haveno.core.xmr.wallet.BtcWalletService; import haveno.core.xmr.wallet.WalletsManager; import haveno.core.xmr.wallet.XmrWalletService; -import haveno.core.xmr.wallet.http.MemPoolSpaceTxBroadcaster; import haveno.network.Socks5ProxyProvider; import haveno.network.p2p.NodeAddress; import haveno.network.p2p.P2PService; @@ -248,8 +247,6 @@ public class HavenoSetup { this.arbitrationManager = arbitrationManager; HavenoUtils.havenoSetup = this; - - MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java index d18572a7c9..e618ef1c72 100644 --- a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java +++ b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java @@ -431,7 +431,7 @@ public final class ArbitrationManager extends DisputeManager XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + arbitratorSignedPayoutTx.getFee()); log.info("Payout tx fee {} is within tolerance, diff %={}", arbitratorSignedPayoutTx.getFee(), feeDiff); } diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java index 252f61e60c..d740902bc1 100644 --- a/core/src/main/java/haveno/core/trade/Trade.java +++ b/core/src/main/java/haveno/core/trade/Trade.java @@ -47,7 +47,6 @@ import haveno.core.trade.protocol.ProcessModelServiceProvider; import haveno.core.trade.protocol.TradeListener; import haveno.core.trade.protocol.TradePeer; import haveno.core.trade.protocol.TradeProtocol; -import haveno.core.trade.txproof.AssetTxProofResult; import haveno.core.util.VolumeUtil; import haveno.core.xmr.model.XmrAddressEntry; import haveno.core.xmr.wallet.XmrWalletService; @@ -55,13 +54,11 @@ import haveno.network.p2p.AckMessage; import haveno.network.p2p.NodeAddress; import haveno.network.p2p.P2PService; import javafx.beans.property.DoubleProperty; -import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -427,18 +424,6 @@ public abstract class Trade implements Tradable, Model { @Setter private String counterCurrencyExtraData; - // Added at v1.3.8 - // Generic tx proof result. We persist name if AssetTxProofResult enum. Other fields in the enum are not persisted - // as they are not very relevant as historical data (e.g. number of confirmations) - @Nullable - @Getter - private AssetTxProofResult assetTxProofResult; - // ObjectProperty with AssetTxProofResult does not notify changeListeners. Probably because AssetTxProofResult is - // an enum and enum does not support EqualsAndHashCode. Alternatively we could add a addListener and removeListener - // method and a listener interface, but the IntegerProperty seems to be less boilerplate. - @Getter - transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty(); - // Added in XMR integration private transient List tradeListeners; // notified on fully validated trade messages transient MoneroWalletListener depositTxListener; @@ -1342,11 +1327,6 @@ public abstract class Trade implements Tradable, Model { errorMessageProperty.set(appendedErrorMessage); } - public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResult) { - this.assetTxProofResult = assetTxProofResult; - assetTxProofResultUpdateProperty.set(assetTxProofResultUpdateProperty.get() + 1); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -1996,7 +1976,6 @@ public abstract class Trade implements Tradable, Model { Optional.ofNullable(payoutTxHex).ifPresent(e -> builder.setPayoutTxHex(payoutTxHex)); Optional.ofNullable(payoutTxKey).ifPresent(e -> builder.setPayoutTxKey(payoutTxKey)); Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData)); - Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name())); return builder.build(); } @@ -2020,13 +1999,6 @@ public abstract class Trade implements Tradable, Model { trade.setStartTime(proto.getStartTime()); trade.setCounterCurrencyExtraData(ProtoUtil.stringOrNullFromProto(proto.getCounterCurrencyExtraData())); - AssetTxProofResult persistedAssetTxProofResult = ProtoUtil.enumFromProto(AssetTxProofResult.class, proto.getAssetTxProofResult()); - // We do not want to show the user the last pending state when he starts up the app again, so we clear it. - if (persistedAssetTxProofResult == AssetTxProofResult.PENDING) { - persistedAssetTxProofResult = null; - } - trade.setAssetTxProofResult(persistedAssetTxProofResult); - trade.chatMessages.addAll(proto.getChatMessageList().stream() .map(ChatMessage::fromPayloadProto) .collect(Collectors.toList())); @@ -2055,7 +2027,6 @@ public abstract class Trade implements Tradable, Model { ",\n errorMessage='" + errorMessage + '\'' + ",\n counterCurrencyTxId='" + counterCurrencyTxId + '\'' + ",\n counterCurrencyExtraData='" + counterCurrencyExtraData + '\'' + - ",\n assetTxProofResult='" + assetTxProofResult + '\'' + ",\n chatMessages=" + chatMessages + ",\n totalTxFee=" + totalTxFee + ",\n takerFee=" + takerFee + diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofHttpClient.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofHttpClient.java deleted file mode 100644 index 814077f971..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofHttpClient.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -import haveno.network.http.HttpClient; - -public interface AssetTxProofHttpClient extends HttpClient { -} diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofModel.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofModel.java deleted file mode 100644 index af262c2557..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofModel.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -public interface AssetTxProofModel { -} diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofParser.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofParser.java deleted file mode 100644 index 78f95e2781..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofParser.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -public interface AssetTxProofParser { - R parse(T model, String jsonTxt); -} diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequest.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequest.java deleted file mode 100644 index 12e32937f0..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -import haveno.common.handlers.FaultHandler; - -import java.util.function.Consumer; - -public interface AssetTxProofRequest { - interface Result { - } - - void requestFromService(Consumer resultHandler, FaultHandler faultHandler); - - void terminate(); -} diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequestsPerTrade.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequestsPerTrade.java deleted file mode 100644 index 71a6b97304..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofRequestsPerTrade.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -import haveno.common.handlers.FaultHandler; - -import java.util.function.Consumer; - -public interface AssetTxProofRequestsPerTrade { - void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler); - - void terminate(); -} diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofResult.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofResult.java deleted file mode 100644 index e25c270a70..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofResult.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -import lombok.Getter; - -public enum AssetTxProofResult { - UNDEFINED, - - FEATURE_DISABLED, - TRADE_LIMIT_EXCEEDED, - INVALID_DATA, // Peer provided invalid data. Might be a scam attempt (e.g. txKey reused) - PAYOUT_TX_ALREADY_PUBLISHED, - DISPUTE_OPENED, - - REQUESTS_STARTED(false), - PENDING(false), - - // All services completed with a success state - COMPLETED, - - // Any service had an error (network, API service) - ERROR, - - // Any service failed. Might be that the tx is invalid. - FAILED; - - // If isTerminal is set it means that we stop the service - @Getter - private final boolean isTerminal; - @Getter - private String details = ""; - @Getter - private int numSuccessResults; - @Getter - private int numRequiredSuccessResults; - @Getter - private int numConfirmations; - @Getter - private int numRequiredConfirmations; - - - AssetTxProofResult() { - this(true); - } - - AssetTxProofResult(boolean isTerminal) { - this.isTerminal = isTerminal; - } - - - public AssetTxProofResult numSuccessResults(int numSuccessResults) { - this.numSuccessResults = numSuccessResults; - return this; - } - - public AssetTxProofResult numRequiredSuccessResults(int numRequiredSuccessResults) { - this.numRequiredSuccessResults = numRequiredSuccessResults; - return this; - } - - public AssetTxProofResult numConfirmations(int numConfirmations) { - this.numConfirmations = numConfirmations; - return this; - } - - public AssetTxProofResult numRequiredConfirmations(int numRequiredConfirmations) { - this.numRequiredConfirmations = numRequiredConfirmations; - return this; - } - - public AssetTxProofResult details(String details) { - this.details = details; - return this; - } - - @Override - public String toString() { - return "AssetTxProofResult{" + - "\n details='" + details + '\'' + - ",\n isTerminal=" + isTerminal + - ",\n numSuccessResults=" + numSuccessResults + - ",\n numRequiredSuccessResults=" + numRequiredSuccessResults + - ",\n numConfirmations=" + numConfirmations + - ",\n numRequiredConfirmations=" + numRequiredConfirmations + - "\n} " + super.toString(); - } -} diff --git a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofService.java b/core/src/main/java/haveno/core/trade/txproof/AssetTxProofService.java deleted file mode 100644 index da7996cc0e..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/AssetTxProofService.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof; - -public interface AssetTxProofService { - void onAllServicesInitialized(); - - void shutDown(); -} diff --git a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofHttpClient.java b/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofHttpClient.java deleted file mode 100644 index 3c17fe58b2..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofHttpClient.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof.xmr; - -import haveno.core.trade.txproof.AssetTxProofHttpClient; -import haveno.network.Socks5ProxyProvider; -import haveno.network.http.HttpClientImpl; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -class XmrTxProofHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { - XmrTxProofHttpClient(Socks5ProxyProvider socks5ProxyProvider) { - super(socks5ProxyProvider); - } -} diff --git a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofModel.java b/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofModel.java deleted file mode 100644 index dfcefe84b1..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofModel.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof.xmr; - -import com.google.common.annotations.VisibleForTesting; -import haveno.common.app.DevEnv; -import haveno.core.monetary.Volume; -import haveno.core.payment.payload.AssetAccountPayload; -import haveno.core.payment.payload.PaymentAccountPayload; -import haveno.core.trade.Trade; -import haveno.core.trade.txproof.AssetTxProofModel; -import haveno.core.user.AutoConfirmSettings; -import lombok.Value; -import lombok.extern.slf4j.Slf4j; - -import java.util.Date; - -import static com.google.common.base.Preconditions.checkNotNull; - -@SuppressWarnings("SpellCheckingInspection") -@Slf4j -@Value -public class XmrTxProofModel implements AssetTxProofModel { - // Those are values from a valid tx which are set automatically if DevEnv.isDevMode is enabled - public static final String DEV_ADDRESS = "85q13WDADXE26W6h7cStpPMkn8tWpvWgHbpGWWttFEafGXyjsBTXxxyQms4UErouTY5sdKpYHVjQm6SagiCqytseDkzfgub"; - public static final String DEV_TX_KEY = "f3ce66c9d395e5e460c8802b2c3c1fff04e508434f9738ee35558aac4678c906"; - public static final String DEV_TX_HASH = "5e665addf6d7c6300670e8a89564ed12b5c1a21c336408e2835668f9a6a0d802"; - public static final long DEV_AMOUNT = 8902597360000L; - - private final String serviceAddress; - private final AutoConfirmSettings autoConfirmSettings; - private final String tradeId; - private final String txHash; - private final String txKey; - private final String recipientAddress; - private final long amount; - private final Date tradeDate; - - XmrTxProofModel(Trade trade, String serviceAddress, AutoConfirmSettings autoConfirmSettings) { - this.serviceAddress = serviceAddress; - this.autoConfirmSettings = autoConfirmSettings; - - Volume volume = trade.getVolume(); - amount = DevEnv.isDevMode() ? - XmrTxProofModel.DEV_AMOUNT : // For dev testing we need to add the matching address to the dev tx key and dev view key - volume != null ? volume.getValue() * 10000L : 0L; // XMR satoshis have 12 decimal places vs. bitcoin's 8 - PaymentAccountPayload sellersPaymentAccountPayload = checkNotNull(trade.getSeller().getPaymentAccountPayload()); - recipientAddress = DevEnv.isDevMode() ? - XmrTxProofModel.DEV_ADDRESS : // For dev testing we need to add the matching address to the dev tx key and dev view key - ((AssetAccountPayload) sellersPaymentAccountPayload).getAddress(); - txHash = trade.getCounterCurrencyTxId(); - txKey = trade.getCounterCurrencyExtraData(); - tradeDate = trade.getDate(); - tradeId = trade.getId(); - } - - // NumRequiredConfirmations is read just in time. If user changes autoConfirmSettings during requests it will - // be reflected at next result parsing. - int getNumRequiredConfirmations() { - return autoConfirmSettings.getRequiredConfirmations(); - } - - // Used only for testing - // TODO Use mocking framework in testing to avoid that constructor... - @VisibleForTesting - XmrTxProofModel(String tradeId, - String txHash, - String txKey, - String recipientAddress, - long amount, - Date tradeDate, - AutoConfirmSettings autoConfirmSettings) { - this.tradeId = tradeId; - this.txHash = txHash; - this.txKey = txKey; - this.recipientAddress = recipientAddress; - this.amount = amount; - this.tradeDate = tradeDate; - this.autoConfirmSettings = autoConfirmSettings; - this.serviceAddress = autoConfirmSettings.getServiceAddresses().get(0); - } -} diff --git a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofParser.java b/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofParser.java deleted file mode 100644 index aca59e4e3b..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofParser.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof.xmr; - -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 haveno.asset.CryptoNoteUtils; -import haveno.common.app.DevEnv; -import haveno.core.trade.txproof.AssetTxProofParser; -import lombok.extern.slf4j.Slf4j; - -import java.util.concurrent.TimeUnit; - -@Slf4j -public class XmrTxProofParser implements AssetTxProofParser { - public static final long MAX_DATE_TOLERANCE = TimeUnit.HOURS.toSeconds(2); - - XmrTxProofParser() { - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - @SuppressWarnings("SpellCheckingInspection") - @Override - public XmrTxProofRequest.Result parse(XmrTxProofModel model, String jsonTxt) { - String txHash = model.getTxHash(); - try { - JsonObject json = new Gson().fromJson(jsonTxt, JsonObject.class); - if (json == null) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Empty json")); - } - // there should always be "data" and "status" at the top level - if (json.get("data") == null || !json.get("data").isJsonObject() || json.get("status") == null) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing data / status fields")); - } - JsonObject jsonData = json.get("data").getAsJsonObject(); - String jsonStatus = json.get("status").getAsString(); - if (jsonStatus.matches("fail")) { - // The API returns "fail" until the transaction has successfully reached the mempool or if request - // contained invalid data. - // We return TX_NOT_FOUND which will cause a retry later - return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.TX_NOT_FOUND); - } else if (!jsonStatus.matches("success")) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Unhandled status value")); - } - - // validate that the address matches - JsonElement jsonAddress = jsonData.get("address"); - if (jsonAddress == null) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing address field")); - } else { - String expectedAddressHex = CryptoNoteUtils.getRawSpendKeyAndViewKey(model.getRecipientAddress()); - if (!jsonAddress.getAsString().equalsIgnoreCase(expectedAddressHex)) { - log.warn("Address from json result (convertToRawHex):\n{}\nExpected (convertToRawHex):\n{}\nRecipient address:\n{}", - jsonAddress.getAsString(), expectedAddressHex, model.getRecipientAddress()); - return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.ADDRESS_INVALID); - } - } - - // validate that the txHash matches - JsonElement jsonTxHash = jsonData.get("tx_hash"); - if (jsonTxHash == null) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_hash field")); - } else { - if (!jsonTxHash.getAsString().equalsIgnoreCase(txHash)) { - log.warn("txHash {}, expected: {}", jsonTxHash.getAsString(), txHash); - return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TX_HASH_INVALID); - } - } - - // validate that the txKey matches - JsonElement jsonViewkey = jsonData.get("viewkey"); - if (jsonViewkey == null) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing viewkey field")); - } else { - if (!jsonViewkey.getAsString().equalsIgnoreCase(model.getTxKey())) { - log.warn("viewkey {}, expected: {}", jsonViewkey.getAsString(), model.getTxKey()); - return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TX_KEY_INVALID); - } - } - - // 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 XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_timestamp field")); - } else { - long tradeDateSeconds = model.getTradeDate().getTime() / 1000; - long difference = tradeDateSeconds - jsonTimestamp.getAsLong(); - // Accept up to 2 hours difference. Some tolerance is needed if users clock is out of sync - if (difference > MAX_DATE_TOLERANCE && !DevEnv.isDevMode()) { - log.warn("tx_timestamp {}, tradeDate: {}, difference {}", - jsonTimestamp.getAsLong(), tradeDateSeconds, difference); - return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); - } - } - - // calculate how many confirms are still needed - int confirmations; - JsonElement jsonConfirmations = jsonData.get("tx_confirmations"); - if (jsonConfirmations == null) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error("Missing tx_confirmations field")); - } else { - confirmations = jsonConfirmations.getAsInt(); - log.info("Confirmations: {}, xmr txHash: {}", confirmations, txHash); - } - - // 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; - 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(); - amountMatches = jsonAmount == model.getAmount(); - if (amountMatches) { - break; - } else { - log.warn("amount {}, expected: {}", jsonAmount, model.getAmount()); - } - } - } - - // None of the outputs had a match entry - if (!anyMatchFound) { - return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.NO_MATCH_FOUND); - } - - // None of the outputs had a match entry - if (!amountMatches) { - return XmrTxProofRequest.Result.FAILED.with(XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); - } - - int confirmsRequired = model.getNumRequiredConfirmations(); - if (confirmations < confirmsRequired) { - return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations)); - } else { - return XmrTxProofRequest.Result.SUCCESS.with(XmrTxProofRequest.Detail.SUCCESS.numConfirmations(confirmations)); - } - - } catch (JsonParseException | NullPointerException e) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.API_INVALID.error(e.toString())); - } catch (CryptoNoteUtils.CryptoNoteException e) { - return XmrTxProofRequest.Result.ERROR.with(XmrTxProofRequest.Detail.ADDRESS_INVALID.error(e.toString())); - } - } -} diff --git a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequest.java b/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequest.java deleted file mode 100644 index c24cb385dd..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequest.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof.xmr; - -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 com.google.common.util.concurrent.MoreExecutors; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParser; -import haveno.common.UserThread; -import haveno.common.app.Version; -import haveno.common.handlers.FaultHandler; -import haveno.common.util.Utilities; -import haveno.core.trade.txproof.AssetTxProofHttpClient; -import haveno.core.trade.txproof.AssetTxProofParser; -import haveno.core.trade.txproof.AssetTxProofRequest; -import haveno.network.Socks5ProxyProvider; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; - -import javax.annotation.Nullable; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -/** - * Requests for the XMR tx proof for a particular trade from a particular service. - * Repeats every 90 sec requests if tx is not confirmed or found yet until MAX_REQUEST_PERIOD of 12 hours is reached. - */ -@Slf4j -@EqualsAndHashCode -class XmrTxProofRequest implements AssetTxProofRequest { - - /////////////////////////////////////////////////////////////////////////////////////////// - // Enums - /////////////////////////////////////////////////////////////////////////////////////////// - - enum Result implements AssetTxProofRequest.Result { - PENDING, // Tx not visible in network yet, unconfirmed or not enough confirmations - SUCCESS, // Proof succeeded - FAILED, // Proof failed - ERROR; // Error from service, does not mean that proof failed - - @Nullable - @Getter - private Detail detail; - - Result with(Detail detail) { - this.detail = detail; - return this; - } - - @Override - public String toString() { - return "Result{" + - "\n detail=" + detail + - "\n} " + super.toString(); - } - } - - enum Detail { - // Pending - TX_NOT_FOUND, // Tx not visible in network yet. Could be also other error - PENDING_CONFIRMATIONS, - - SUCCESS, - - // Error states - CONNECTION_FAILURE, - API_INVALID, - - // Failure states - TX_HASH_INVALID, - TX_KEY_INVALID, - ADDRESS_INVALID, - NO_MATCH_FOUND, - AMOUNT_NOT_MATCHING, - TRADE_DATE_NOT_MATCHING, - NO_RESULTS_TIMEOUT; - - @Getter - private int numConfirmations; - @Nullable - @Getter - private String errorMsg; - - public Detail error(String errorMsg) { - this.errorMsg = errorMsg; - return this; - } - - public Detail numConfirmations(int numConfirmations) { - this.numConfirmations = numConfirmations; - return this; - } - - @Override - public String toString() { - return "Detail{" + - "\n numConfirmations=" + numConfirmations + - ",\n errorMsg='" + errorMsg + '\'' + - "\n} " + super.toString(); - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Static fields - /////////////////////////////////////////////////////////////////////////////////////////// - - private static final long REPEAT_REQUEST_PERIOD = TimeUnit.SECONDS.toMillis(90); - private static final long MAX_REQUEST_PERIOD = TimeUnit.HOURS.toMillis(12); - - /////////////////////////////////////////////////////////////////////////////////////////// - // Class fields - /////////////////////////////////////////////////////////////////////////////////////////// - - private final ListeningExecutorService executorService = Utilities.getListeningExecutorService( - "XmrTransferProofRequester", 3, 5, 10 * 60); - - private final AssetTxProofParser parser; - private final XmrTxProofModel model; - private final AssetTxProofHttpClient httpClient; - private final long firstRequest; - - private boolean terminated; - @Getter - @Nullable - private Result result; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - XmrTxProofRequest(Socks5ProxyProvider socks5ProxyProvider, - XmrTxProofModel model) { - this.parser = new XmrTxProofParser(); - this.model = model; - - httpClient = new XmrTxProofHttpClient(socks5ProxyProvider); - - // localhost, LAN address, or *.local FQDN starts with http://, don't use Tor - if (model.getServiceAddress().regionMatches(0, "http:", 0, 5)) { - httpClient.setBaseUrl(model.getServiceAddress()); - httpClient.setIgnoreSocks5Proxy(true); - // any non-onion FQDN starts with https://, use Tor - } else if (model.getServiceAddress().regionMatches(0, "https:", 0, 6)) { - httpClient.setBaseUrl(model.getServiceAddress()); - httpClient.setIgnoreSocks5Proxy(false); - // it's a raw onion so add http:// and use Tor proxy - } else { - httpClient.setBaseUrl("http://" + model.getServiceAddress()); - httpClient.setIgnoreSocks5Proxy(false); - } - - terminated = false; - firstRequest = System.currentTimeMillis(); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - @SuppressWarnings("SpellCheckingInspection") - @Override - public void requestFromService(Consumer resultHandler, FaultHandler faultHandler) { - if (terminated) { - // the XmrTransferProofService has asked us to terminate i.e. not make any further api calls - // this scenario may happen if a re-request is scheduled from the callback below - log.warn("Not starting {} as we have already terminated.", this); - return; - } - - if (httpClient.hasPendingRequest()) { - log.warn("We have a pending request open. We ignore that request. httpClient {}", httpClient); - return; - } - - // Timeout handing is delegated to the connection timeout handling in httpClient. - - ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("XmrTransferProofRequest-" + this.getShortId()); - String param = "/api/outputs?txhash=" + model.getTxHash() + - "&address=" + model.getRecipientAddress() + - "&viewkey=" + model.getTxKey() + - "&txprove=1"; - log.info("Param {} for {}", param, this); - String json = httpClient.get(param, "User-Agent", "haveno/" + Version.VERSION); - try { - String prettyJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(json)); - log.info("Response json from {}\n{}", this, prettyJson); - } catch (Throwable error) { - log.error("Pretty print caused a {}: raw json={}", error, json); - } - - Result result = parser.parse(model, json); - log.info("Result from {}\n{}", this, result); - return result; - }); - - Futures.addCallback(future, new FutureCallback<>() { - public void onSuccess(Result result) { - XmrTxProofRequest.this.result = result; - - if (terminated) { - log.warn("We received {} but {} was terminated already. We do not process result.", result, this); - return; - } - - switch (result) { - case PENDING: - if (isTimeOutReached()) { - log.warn("{} took too long without a success or failure/error result We give up. " + - "Might be that the transaction was never published.", this); - // If we reached out timeout we return with an error. - UserThread.execute(() -> resultHandler.accept(XmrTxProofRequest.Result.ERROR.with(Detail.NO_RESULTS_TIMEOUT))); - } else { - UserThread.runAfter(() -> requestFromService(resultHandler, faultHandler), REPEAT_REQUEST_PERIOD, TimeUnit.MILLISECONDS); - // We update our listeners - UserThread.execute(() -> resultHandler.accept(result)); - } - break; - case SUCCESS: - log.info("{} succeeded", result); - UserThread.execute(() -> resultHandler.accept(result)); - terminate(); - break; - case FAILED: - case ERROR: - UserThread.execute(() -> resultHandler.accept(result)); - terminate(); - break; - default: - log.warn("Unexpected result {}", result); - break; - } - } - - public void onFailure(@NotNull Throwable throwable) { - String errorMessage = this + " failed with error " + throwable.toString(); - faultHandler.handleFault(errorMessage, throwable); - UserThread.execute(() -> - resultHandler.accept(XmrTxProofRequest.Result.ERROR.with(Detail.CONNECTION_FAILURE.error(errorMessage)))); - } - }, MoreExecutors.directExecutor()); - } - - @Override - public void terminate() { - terminated = true; - } - - // Convenient for logging - @Override - public String toString() { - return "Request at: " + model.getServiceAddress() + " for trade: " + model.getTradeId(); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private String getShortId() { - return Utilities.getShortId(model.getTradeId()) + " @ " + model.getServiceAddress().substring(0, 6); - } - - private boolean isTimeOutReached() { - return System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD; - } -} diff --git a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java b/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java deleted file mode 100644 index 12f2658ba8..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofRequestsPerTrade.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof.xmr; - -import haveno.common.handlers.FaultHandler; -import haveno.core.filter.FilterManager; -import haveno.core.locale.Res; -import haveno.core.support.dispute.Dispute; -import haveno.core.support.dispute.mediation.MediationManager; -import haveno.core.support.dispute.refund.RefundManager; -import haveno.core.trade.HavenoUtils; -import haveno.core.trade.Trade; -import haveno.core.trade.txproof.AssetTxProofRequestsPerTrade; -import haveno.core.trade.txproof.AssetTxProofResult; -import haveno.core.user.AutoConfirmSettings; -import haveno.network.Socks5ProxyProvider; -import javafx.beans.value.ChangeListener; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.math.BigInteger; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; - -/** - * Handles the XMR tx proof requests for multiple services per trade. - */ -@Slf4j -class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade { - @Getter - private final Trade trade; - private final AutoConfirmSettings autoConfirmSettings; - private final MediationManager mediationManager; - private final FilterManager filterManager; - private final RefundManager refundManager; - private final Socks5ProxyProvider socks5ProxyProvider; - - private int numRequiredSuccessResults; - private final Set requests = new HashSet<>(); - - private int numSuccessResults; - private ChangeListener tradeStateListener; - private AutoConfirmSettings.Listener autoConfirmSettingsListener; - private ListChangeListener mediationListener, refundListener; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - XmrTxProofRequestsPerTrade(Socks5ProxyProvider socks5ProxyProvider, - Trade trade, - AutoConfirmSettings autoConfirmSettings, - MediationManager mediationManager, - FilterManager filterManager, - RefundManager refundManager) { - this.socks5ProxyProvider = socks5ProxyProvider; - this.trade = trade; - this.autoConfirmSettings = autoConfirmSettings; - this.mediationManager = mediationManager; - this.filterManager = filterManager; - this.refundManager = refundManager; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void requestFromAllServices(Consumer resultHandler, FaultHandler faultHandler) { - // isTradeAmountAboveLimit - if (isTradeAmountAboveLimit(trade)) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED); - return; - } - - // isPayoutPublished - if (trade.isPayoutPublished()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); - return; - } - - // IsEnabled() - // We will stop all our services if the user changes the enable state in the AutoConfirmSettings - if (!autoConfirmSettings.isEnabled()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); - return; - } - addSettingsListener(resultHandler); - - // TradeState - setupTradeStateListener(resultHandler); - // We checked initially for current trade state so no need to check again here - - // Check if mediation dispute and add listener - ObservableList mediationDisputes = mediationManager.getDisputesAsObservableList(); - if (isDisputed(mediationDisputes)) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); - return; - } - setupMediationListener(resultHandler, mediationDisputes); - - // Check if arbitration dispute and add listener - ObservableList refundDisputes = refundManager.getDisputesAsObservableList(); - if (isDisputed(refundDisputes)) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); - return; - } - setupArbitrationListener(resultHandler, refundDisputes); - - // All good so we start - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED); - - // We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started - // it will have no impact on serviceAddresses and numRequiredSuccessResults. - // Thought numRequiredConfirmations can be changed during request process and will be read from - // autoConfirmSettings at result parsing. - List serviceAddresses = autoConfirmSettings.getServiceAddresses(); - numRequiredSuccessResults = serviceAddresses.size(); - - for (String serviceAddress : serviceAddresses) { - if (filterManager.isAutoConfExplorerBanned(serviceAddress)) { - log.warn("Filtered out auto-confirmation address: {}", serviceAddress); - continue; // #4683: filter for auto-confirm explorers - } - XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings); - XmrTxProofRequest request = new XmrTxProofRequest(socks5ProxyProvider, model); - - log.info("{} created", request); - requests.add(request); - - request.requestFromService(result -> { - // If we ever received an error or failed result we terminate and do not process any - // future result anymore to avoid that we overwrite out state with success. - if (wasTerminated()) { - return; - } - - AssetTxProofResult assetTxProofResult; - if (trade.isPayoutPublished()) { - assetTxProofResult = AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED; - callResultHandlerAndMaybeTerminate(resultHandler, assetTxProofResult); - return; - } - - switch (result) { - case PENDING: - // We expect repeated PENDING results with different details - assetTxProofResult = getAssetTxProofResultForPending(result); - break; - case SUCCESS: - numSuccessResults++; - if (numSuccessResults < numRequiredSuccessResults) { - // Request is success but not all have completed yet. - int remaining = numRequiredSuccessResults - numSuccessResults; - log.info("{} succeeded. We have {} remaining request(s) open.", - request, remaining); - assetTxProofResult = getAssetTxProofResultForPending(result); - } else { - // All our services have returned a SUCCESS result so we - // have completed on the service level. - log.info("All {} tx proof requests for trade {} have been successful.", - numRequiredSuccessResults, trade.getShortId()); - XmrTxProofRequest.Detail detail = result.getDetail(); - assetTxProofResult = AssetTxProofResult.COMPLETED - .numSuccessResults(numSuccessResults) - .numRequiredSuccessResults(numRequiredSuccessResults) - .numConfirmations(detail != null ? detail.getNumConfirmations() : 0) - .numRequiredConfirmations(autoConfirmSettings.getRequiredConfirmations()); - } - break; - case FAILED: - log.warn("{} failed. " + - "This might not mean that the XMR transfer was invalid but you have to check yourself " + - "if the XMR transfer was correct. {}", - request, result); - - assetTxProofResult = AssetTxProofResult.FAILED; - break; - case ERROR: - default: - log.warn("{} resulted in an error. " + - "This might not mean that the XMR transfer was invalid but can be a network or " + - "service problem. {}", - request, result); - - assetTxProofResult = AssetTxProofResult.ERROR; - break; - } - - callResultHandlerAndMaybeTerminate(resultHandler, assetTxProofResult); - }, - faultHandler); - } - } - - private boolean wasTerminated() { - return requests.isEmpty(); - } - - private void addSettingsListener(Consumer resultHandler) { - autoConfirmSettingsListener = () -> { - if (!autoConfirmSettings.isEnabled()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED); - } - }; - autoConfirmSettings.addListener(autoConfirmSettingsListener); - } - - private void setupTradeStateListener(Consumer resultHandler) { - tradeStateListener = (observable, oldValue, newValue) -> { - if (trade.isPayoutPublished()) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED); - } - }; - trade.stateProperty().addListener(tradeStateListener); - } - - private void setupArbitrationListener(Consumer resultHandler, - ObservableList refundDisputes) { - refundListener = c -> { - c.next(); - if (c.wasAdded() && isDisputed(c.getAddedSubList())) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); - } - }; - refundDisputes.addListener(refundListener); - } - - private void setupMediationListener(Consumer resultHandler, - ObservableList mediationDisputes) { - mediationListener = c -> { - c.next(); - if (c.wasAdded() && isDisputed(c.getAddedSubList())) { - callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED); - } - }; - mediationDisputes.addListener(mediationListener); - } - - @Override - public void terminate() { - requests.forEach(XmrTxProofRequest::terminate); - requests.clear(); - - if (tradeStateListener != null) { - trade.stateProperty().removeListener(tradeStateListener); - } - - if (autoConfirmSettingsListener != null) { - autoConfirmSettings.removeListener(autoConfirmSettingsListener); - } - - if (mediationListener != null) { - mediationManager.getDisputesAsObservableList().removeListener(mediationListener); - } - - if (refundListener != null) { - refundManager.getDisputesAsObservableList().removeListener(refundListener); - } - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private void callResultHandlerAndMaybeTerminate(Consumer resultHandler, - AssetTxProofResult assetTxProofResult) { - resultHandler.accept(assetTxProofResult); - if (assetTxProofResult.isTerminal()) { - terminate(); - } - } - - private AssetTxProofResult getAssetTxProofResultForPending(XmrTxProofRequest.Result result) { - XmrTxProofRequest.Detail detail = result.getDetail(); - int numConfirmations = detail != null ? detail.getNumConfirmations() : 0; - log.info("{} returned with numConfirmations {}", - result, numConfirmations); - - String detailString = ""; - if (XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS == detail) { - detailString = Res.get("portfolio.pending.autoConf.state.confirmations", - numConfirmations, autoConfirmSettings.getRequiredConfirmations()); - - } else if (XmrTxProofRequest.Detail.TX_NOT_FOUND == detail) { - detailString = Res.get("portfolio.pending.autoConf.state.txNotFound"); - } - - return AssetTxProofResult.PENDING - .numSuccessResults(numSuccessResults) - .numRequiredSuccessResults(numRequiredSuccessResults) - .numConfirmations(detail != null ? detail.getNumConfirmations() : 0) - .numRequiredConfirmations(autoConfirmSettings.getRequiredConfirmations()) - .details(detailString); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Validation - /////////////////////////////////////////////////////////////////////////////////////////// - - private boolean isTradeAmountAboveLimit(Trade trade) { - BigInteger tradeAmount = trade.getAmount(); - BigInteger tradeLimit = BigInteger.valueOf(autoConfirmSettings.getTradeLimit()); - if (tradeAmount != null && tradeAmount.compareTo(tradeLimit) > 0) { - log.warn("Trade amount {} is higher than limit from auto-conf setting {}.", - HavenoUtils.formatXmr(tradeAmount, true), HavenoUtils.formatXmr(tradeLimit, true)); - return true; - } - return false; - } - - private boolean isDisputed(List disputes) { - return disputes.stream().anyMatch(e -> e.getTradeId().equals(trade.getId())); - } -} diff --git a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofService.java b/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofService.java deleted file mode 100644 index f1f511093a..0000000000 --- a/core/src/main/java/haveno/core/trade/txproof/xmr/XmrTxProofService.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.trade.txproof.xmr; - -import haveno.common.app.DevEnv; -import haveno.core.api.CoreMoneroConnectionsService; -import haveno.core.filter.FilterManager; -import haveno.core.locale.Res; -import haveno.core.support.dispute.mediation.MediationManager; -import haveno.core.support.dispute.refund.RefundManager; -import haveno.core.trade.ClosedTradableManager; -import haveno.core.trade.SellerTrade; -import haveno.core.trade.Trade; -import haveno.core.trade.TradeManager; -import haveno.core.trade.failed.FailedTradesManager; -import haveno.core.trade.protocol.SellerProtocol; -import haveno.core.trade.txproof.AssetTxProofResult; -import haveno.core.trade.txproof.AssetTxProofService; -import haveno.core.user.AutoConfirmSettings; -import haveno.core.user.Preferences; -import haveno.network.Socks5ProxyProvider; -import haveno.network.p2p.BootstrapListener; -import haveno.network.p2p.P2PService; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.value.ChangeListener; -import javafx.collections.ListChangeListener; -import javafx.collections.ObservableList; -import lombok.extern.slf4j.Slf4j; -import org.fxmisc.easybind.EasyBind; -import org.fxmisc.easybind.monadic.MonadicBinding; - -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 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 implements AssetTxProofService { - private final FilterManager filterManager; - private final Preferences preferences; - private final TradeManager tradeManager; - private final ClosedTradableManager closedTradableManager; - private final FailedTradesManager failedTradesManager; - private final MediationManager mediationManager; - private final RefundManager refundManager; - private final P2PService p2PService; - private final CoreMoneroConnectionsService connectionService; - private final Socks5ProxyProvider socks5ProxyProvider; - private final Map servicesByTradeId = new HashMap<>(); - private AutoConfirmSettings autoConfirmSettings; - private final Map> tradeStateListenerMap = new HashMap<>(); - private ChangeListener xmrPeersListener, xmrBlockListener; - private BootstrapListener bootstrapListener; - private MonadicBinding p2pNetworkAndWalletReady; - private ChangeListener p2pNetworkAndWalletReadyListener; - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Constructor - /////////////////////////////////////////////////////////////////////////////////////////// - - @SuppressWarnings("WeakerAccess") - @Inject - public XmrTxProofService(FilterManager filterManager, - Preferences preferences, - TradeManager tradeManager, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager, - MediationManager mediationManager, - RefundManager refundManager, - P2PService p2PService, - CoreMoneroConnectionsService connectionService, - Socks5ProxyProvider socks5ProxyProvider) { - this.filterManager = filterManager; - this.preferences = preferences; - this.tradeManager = tradeManager; - this.closedTradableManager = closedTradableManager; - this.failedTradesManager = failedTradesManager; - this.mediationManager = mediationManager; - this.refundManager = refundManager; - this.p2PService = p2PService; - this.connectionService = connectionService; - this.socks5ProxyProvider = socks5ProxyProvider; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // API - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - public void onAllServicesInitialized() { - // As we might trigger the payout tx we want to be sure that we are well connected to the Bitcoin network. - // onAllServicesInitialized is called once we have received the initial data but we want to have our - // hidden service published and upDatedDataResponse received before we start. - BooleanProperty isP2pBootstrapped = isP2pBootstrapped(); - BooleanProperty hasSufficientXmrPeers = hasSufficientXmrPeers(); - BooleanProperty isXmrBlockDownloadComplete = isXmrBlockDownloadComplete(); - if (isP2pBootstrapped.get() && hasSufficientXmrPeers.get() && isXmrBlockDownloadComplete.get()) { - onP2pNetworkAndWalletReady(); - } else { - p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped, hasSufficientXmrPeers, isXmrBlockDownloadComplete, - (bootstrapped, sufficientPeers, downloadComplete) -> - bootstrapped && sufficientPeers && downloadComplete); - - p2pNetworkAndWalletReadyListener = (observable, oldValue, newValue) -> { - if (newValue) { - onP2pNetworkAndWalletReady(); - } - }; - p2pNetworkAndWalletReady.subscribe(p2pNetworkAndWalletReadyListener); - } - } - - @Override - public void shutDown() { - servicesByTradeId.values().forEach(XmrTxProofRequestsPerTrade::terminate); - servicesByTradeId.clear(); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Private - /////////////////////////////////////////////////////////////////////////////////////////// - - private void onP2pNetworkAndWalletReady() { - if (p2pNetworkAndWalletReady != null) { - p2pNetworkAndWalletReady.removeListener(p2pNetworkAndWalletReadyListener); - p2pNetworkAndWalletReady = null; - p2pNetworkAndWalletReadyListener = null; - } - - if (!preferences.findAutoConfirmSettings("XMR").isPresent()) { - log.error("AutoConfirmSettings is not present"); - return; - } - autoConfirmSettings = preferences.findAutoConfirmSettings("XMR").get(); - - // We register a listener to stop running services. For new trades we check anyway in the trade validation - filterManager.filterProperty().addListener((observable, oldValue, newValue) -> { - if (isAutoConfDisabledByFilter()) { - servicesByTradeId.values().stream().map(XmrTxProofRequestsPerTrade::getTrade).forEach(trade -> - trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED - .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature")))); - tradeManager.requestPersistence(); - shutDown(); - } - }); - - // We listen on new trades - ObservableList tradableList = tradeManager.getObservableList(); - tradableList.addListener((ListChangeListener) c -> { - c.next(); - if (c.wasAdded()) { - processTrades(c.getAddedSubList()); - } - }); - - // Process existing trades - processTrades(tradableList); - } - - private void processTrades(List trades) { - trades.stream() - .filter(trade -> trade instanceof SellerTrade) - .map(trade -> (SellerTrade) trade) - .filter(this::isXmrTrade) - .filter(trade -> !trade.isPaymentReceived()) // Phase name is from the time when it was fiat only. Means counter currency (XMR) received. - .forEach(this::processTradeOrAddListener); - } - - // Basic requirements are fulfilled. - // We process further if we are in the expected state or register a listener - private void processTradeOrAddListener(SellerTrade trade) { - if (isExpectedTradeState(trade.getState())) { - startRequestsIfValid(trade); - } else { - // We are expecting SELLER_RECEIVED_PAYMENT_SENT_MSG in the future, so listen on changes - ChangeListener tradeStateListener = (observable, oldValue, newValue) -> { - if (isExpectedTradeState(newValue)) { - ChangeListener listener = tradeStateListenerMap.remove(trade.getId()); - if (listener != null) { - trade.stateProperty().removeListener(listener); - } - - startRequestsIfValid(trade); - } - }; - tradeStateListenerMap.put(trade.getId(), tradeStateListener); - trade.stateProperty().addListener(tradeStateListener); - } - } - - private void startRequestsIfValid(SellerTrade trade) { - String txId = trade.getCounterCurrencyTxId(); - String txHash = trade.getCounterCurrencyExtraData(); - if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) { - trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.txKeyOrTxIdInvalid"))); - tradeManager.requestPersistence(); - return; - } - - if (isAutoConfDisabledByFilter()) { - trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED - .details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature"))); - tradeManager.requestPersistence(); - return; - } - - if (wasTxKeyReUsed(trade, tradeManager.getObservableList())) { - trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA - .details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused"))); - tradeManager.requestPersistence(); - return; - } - - startRequests(trade); - } - - private void startRequests(SellerTrade trade) { - XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(socks5ProxyProvider, - trade, - autoConfirmSettings, - mediationManager, - filterManager, - refundManager); - servicesByTradeId.put(trade.getId(), service); - service.requestFromAllServices( - assetTxProofResult -> { - trade.setAssetTxProofResult(assetTxProofResult); - - if (assetTxProofResult == AssetTxProofResult.COMPLETED) { - log.info("###########################################################################################"); - log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", trade.getShortId()); - log.info("###########################################################################################"); - - ((SellerProtocol) tradeManager.getTradeProtocol(trade)).onPaymentReceived(() -> { - }, errorMessage -> { - }); - } - - if (assetTxProofResult.isTerminal()) { - servicesByTradeId.remove(trade.getId()); - } - - tradeManager.requestPersistence(); - }, - (errorMessage, throwable) -> { - log.error(errorMessage); - }); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Startup checks - /////////////////////////////////////////////////////////////////////////////////////////// - - private BooleanProperty isXmrBlockDownloadComplete() { - BooleanProperty result = new SimpleBooleanProperty(); - if (connectionService.isDownloadComplete()) { - result.set(true); - } else { - xmrBlockListener = (observable, oldValue, newValue) -> { - if (connectionService.isDownloadComplete()) { - connectionService.downloadPercentageProperty().removeListener(xmrBlockListener); - result.set(true); - } - }; - connectionService.downloadPercentageProperty().addListener(xmrBlockListener); - } - return result; - } - - private BooleanProperty hasSufficientXmrPeers() { - BooleanProperty result = new SimpleBooleanProperty(); - if (connectionService.hasSufficientPeersForBroadcast()) { - result.set(true); - } else { - xmrPeersListener = (observable, oldValue, newValue) -> { - if (connectionService.hasSufficientPeersForBroadcast()) { - connectionService.numPeersProperty().removeListener(xmrPeersListener); - result.set(true); - } - }; - connectionService.numPeersProperty().addListener(xmrPeersListener); - } - return result; - } - - private BooleanProperty isP2pBootstrapped() { - BooleanProperty result = new SimpleBooleanProperty(); - if (p2PService.isBootstrapped()) { - result.set(true); - } else { - bootstrapListener = new BootstrapListener() { - @Override - public void onUpdatedDataReceived() { - p2PService.removeP2PServiceListener(bootstrapListener); - result.set(true); - } - }; - p2PService.addP2PServiceListener(bootstrapListener); - } - return result; - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Validation - /////////////////////////////////////////////////////////////////////////////////////////// - - private boolean isXmrTrade(Trade trade) { - return (checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")); - } - - private boolean isExpectedTradeState(Trade.State newValue) { - return newValue == Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG; - } - - private boolean is32BitHexStringInValid(String hexString) { - if (hexString == null || hexString.isEmpty() || !hexString.matches("[a-fA-F0-9]{64}")) { - log.warn("Invalid hexString: {}", hexString); - return true; - } - - return false; - } - - private boolean isAutoConfDisabledByFilter() { - return filterManager.getFilter() != null && - filterManager.getFilter().isDisableAutoConf(); - } - - private boolean wasTxKeyReUsed(Trade trade, List activeTrades) { - // For dev testing we reuse test data so we ignore that check - 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.getObservableList().stream()); - Stream closedTrades = closedTradableManager.getObservableList().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) { - log.warn("Peer used the XMR tx key already at another trade with trade ID {}. " + - "This might be a scam attempt.", t.getId()); - } - return alreadyUsed; - }); - } -} diff --git a/core/src/main/java/haveno/core/xmr/wallet/BtcWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/BtcWalletService.java index 7d96e4484e..b7a884dae4 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/BtcWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/BtcWalletService.java @@ -18,10 +18,6 @@ package haveno.core.xmr.wallet; import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import haveno.common.handlers.ErrorMessageHandler; import haveno.common.util.Tuple2; import haveno.core.user.Preferences; import haveno.core.xmr.exceptions.AddressEntryException; @@ -31,7 +27,6 @@ import haveno.core.xmr.exceptions.WalletException; import haveno.core.xmr.model.AddressEntry; import haveno.core.xmr.model.AddressEntryList; import haveno.core.xmr.setup.WalletsSetup; -import haveno.core.xmr.wallet.http.MemPoolSpaceTxBroadcaster; import org.bitcoinj.core.Address; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Coin; @@ -39,16 +34,13 @@ import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.SegwitAddress; import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionInput; -import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.wallet.SendRequest; -import org.bitcoinj.wallet.Wallet; import org.bouncycastle.crypto.params.KeyParameter; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -403,162 +395,6 @@ public class BtcWalletService extends WalletService { } - /////////////////////////////////////////////////////////////////////////////////////////// - // Double spend unconfirmed transaction (unlock in case we got into a tx with a too low mining fee) - /////////////////////////////////////////////////////////////////////////////////////////// - - public void doubleSpendTransaction(String txId, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) - throws InsufficientFundsException { - AddressEntry addressEntry = getFreshAddressEntry(); - checkNotNull(addressEntry.getAddress(), "addressEntry.getAddress() must not be null"); - Optional transactionOptional = wallet.getTransactions(true).stream() - .filter(t -> t.getTxId().toString().equals(txId)) - .findAny(); - if (transactionOptional.isPresent()) { - Transaction txToDoubleSpend = transactionOptional.get(); - Address toAddress = addressEntry.getAddress(); - final TransactionConfidence.ConfidenceType confidenceType = txToDoubleSpend.getConfidence().getConfidenceType(); - if (confidenceType == TransactionConfidence.ConfidenceType.PENDING) { - log.debug("txToDoubleSpend no. of inputs " + txToDoubleSpend.getInputs().size()); - - Transaction newTransaction = new Transaction(params); - txToDoubleSpend.getInputs().stream().forEach(input -> { - final TransactionOutput connectedOutput = input.getConnectedOutput(); - if (connectedOutput != null && - connectedOutput.isMine(wallet) && - connectedOutput.getParentTransaction() != null && - connectedOutput.getParentTransaction().getConfidence() != null && - input.getValue() != null) { - //if (connectedOutput.getParentTransaction().getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) { - newTransaction.addInput(new TransactionInput(params, - newTransaction, - new byte[]{}, - new TransactionOutPoint(params, input.getOutpoint().getIndex(), - new Transaction(params, connectedOutput.getParentTransaction().bitcoinSerialize())), - Coin.valueOf(input.getValue().value))); - /* } else { - log.warn("Confidence of parent tx is not of type BUILDING: ConfidenceType=" + - connectedOutput.getParentTransaction().getConfidence().getConfidenceType()); - }*/ - } - } - ); - - log.info("newTransaction no. of inputs " + newTransaction.getInputs().size()); - log.info("newTransaction vsize in vkB " + newTransaction.getVsize() / 1024); - - if (!newTransaction.getInputs().isEmpty()) { - Coin amount = Coin.valueOf(newTransaction.getInputs().stream() - .mapToLong(input -> input.getValue() != null ? input.getValue().value : 0) - .sum()); - newTransaction.addOutput(amount, toAddress); - - try { - Coin fee; - int counter = 0; - int txVsize = 0; - Transaction tx; - SendRequest sendRequest; - Coin txFeeForWithdrawalPerVbyte = getTxFeeForWithdrawalPerVbyte(); - do { - counter++; - fee = txFeeForWithdrawalPerVbyte.multiply(txVsize); - newTransaction.clearOutputs(); - newTransaction.addOutput(amount.subtract(fee), toAddress); - - sendRequest = SendRequest.forTx(newTransaction); - sendRequest.fee = fee; - sendRequest.feePerKb = Coin.ZERO; - sendRequest.ensureMinRequiredFee = false; - sendRequest.aesKey = aesKey; - sendRequest.coinSelector = new BtcCoinSelector(toAddress, preferences.getIgnoreDustThreshold()); - sendRequest.changeAddress = toAddress; - wallet.completeTx(sendRequest); - tx = sendRequest.tx; - txVsize = tx.getVsize(); - printTx("FeeEstimationTransaction", tx); - sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString())); - } - while (feeEstimationNotSatisfied(counter, tx)); - - if (counter == 10) - log.error("Could not calculate the fee. Tx=" + tx); - - - Wallet.SendResult sendResult = null; - try { - sendRequest = SendRequest.forTx(newTransaction); - sendRequest.fee = fee; - sendRequest.feePerKb = Coin.ZERO; - sendRequest.ensureMinRequiredFee = false; - sendRequest.aesKey = aesKey; - sendRequest.coinSelector = new BtcCoinSelector(toAddress, preferences.getIgnoreDustThreshold()); - sendRequest.changeAddress = toAddress; - sendResult = wallet.sendCoins(sendRequest); - } catch (InsufficientMoneyException e) { - // in some cases getFee did not calculate correctly and we still get an InsufficientMoneyException - log.warn("We still have a missing fee " + (e.missing != null ? e.missing.toFriendlyString() : "")); - - amount = amount.subtract(e.missing); - newTransaction.clearOutputs(); - newTransaction.addOutput(amount, toAddress); - - sendRequest = SendRequest.forTx(newTransaction); - sendRequest.fee = fee; - sendRequest.feePerKb = Coin.ZERO; - sendRequest.ensureMinRequiredFee = false; - sendRequest.aesKey = aesKey; - sendRequest.coinSelector = new BtcCoinSelector(toAddress, - preferences.getIgnoreDustThreshold(), false); - sendRequest.changeAddress = toAddress; - - try { - sendResult = wallet.sendCoins(sendRequest); - printTx("FeeEstimationTransaction", newTransaction); - - // For better redundancy in case the broadcast via BitcoinJ fails we also - // publish the tx via mempool nodes. - MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx); - } catch (InsufficientMoneyException e2) { - errorMessageHandler.handleErrorMessage("We did not get the correct fee calculated. " + (e2.missing != null ? e2.missing.toFriendlyString() : "")); - } - } - if (sendResult != null) { - log.info("Broadcasting double spending transaction. " + sendResult.tx); - Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<>() { - @Override - public void onSuccess(Transaction result) { - log.info("Double spending transaction published. " + result); - resultHandler.run(); - } - - @Override - public void onFailure(@NotNull Throwable t) { - log.error("Broadcasting double spending transaction failed. " + t.getMessage()); - errorMessageHandler.handleErrorMessage(t.getMessage()); - } - }, MoreExecutors.directExecutor()); - } - - } catch (InsufficientMoneyException e) { - throw new InsufficientFundsException("The fees for that transaction exceed the available funds " + - "or the resulting output value is below the min. dust value:\n" + - "Missing " + (e.missing != null ? e.missing.toFriendlyString() : "null")); - } - } else { - String errorMessage = "We could not find inputs we control in the transaction we want to double spend."; - log.warn(errorMessage); - errorMessageHandler.handleErrorMessage(errorMessage); - } - } else if (confidenceType == TransactionConfidence.ConfidenceType.BUILDING) { - errorMessageHandler.handleErrorMessage("That transaction is already in the blockchain so we cannot double spend it."); - } else if (confidenceType == TransactionConfidence.ConfidenceType.DEAD) { - errorMessageHandler.handleErrorMessage("One of the inputs of that transaction has been already double spent."); - } - } - } - - /////////////////////////////////////////////////////////////////////////////////////////// // Withdrawal Fee calculation /////////////////////////////////////////////////////////////////////////////////////////// @@ -701,54 +537,6 @@ public class BtcWalletService extends WalletService { // Withdrawal Send /////////////////////////////////////////////////////////////////////////////////////////// - public String sendFunds(String fromAddress, - String toAddress, - Coin receiverAmount, - Coin fee, - @Nullable KeyParameter aesKey, - @SuppressWarnings("SameParameterValue") AddressEntry.Context context, - @Nullable String memo, - FutureCallback callback) throws AddressFormatException, - AddressEntryException, InsufficientMoneyException { - SendRequest sendRequest = getSendRequest(fromAddress, toAddress, receiverAmount, fee, aesKey, context); - Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); - Futures.addCallback(sendResult.broadcastComplete, callback, MoreExecutors.directExecutor()); - if (memo != null) { - sendResult.tx.setMemo(memo); - } - - // For better redundancy in case the broadcast via BitcoinJ fails we also - // publish the tx via mempool nodes. - MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx); - - return sendResult.tx.getTxId().toString(); - } - - public Transaction sendFundsForMultipleAddresses(Set fromAddresses, - String toAddress, - Coin receiverAmount, - Coin fee, - @Nullable String changeAddress, - @Nullable KeyParameter aesKey, - @Nullable String memo, - FutureCallback callback) throws AddressFormatException, - AddressEntryException, InsufficientMoneyException { - - SendRequest request = getSendRequestForMultipleAddresses(fromAddresses, toAddress, receiverAmount, fee, changeAddress, aesKey); - Wallet.SendResult sendResult = wallet.sendCoins(request); - Futures.addCallback(sendResult.broadcastComplete, callback, MoreExecutors.directExecutor()); - if (memo != null) { - sendResult.tx.setMemo(memo); - } - printTx("sendFunds", sendResult.tx); - - // For better redundancy in case the broadcast via BitcoinJ fails we also - // publish the tx via mempool nodes. - MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx); - - return sendResult.tx; - } - private SendRequest getSendRequest(String fromAddress, String toAddress, Coin amount, diff --git a/core/src/main/java/haveno/core/xmr/wallet/TradeWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/TradeWalletService.java index db0866a887..12dac1aa9f 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/TradeWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/TradeWalletService.java @@ -994,30 +994,6 @@ public class TradeWalletService { return new Tuple2<>(txId, signedTxHex); } - public void emergencyPublishPayoutTxFrom2of2MultiSig(String signedTxHex, TxBroadcaster.Callback callback) - throws AddressFormatException, TransactionVerificationException, WalletException { - Transaction payoutTx = new Transaction(params, Utils.HEX.decode(signedTxHex)); - WalletService.printTx("payoutTx", payoutTx); - WalletService.verifyTransaction(payoutTx); - WalletService.checkWalletConsistency(wallet); - broadcastTx(payoutTx, callback, 20); - } - - - /////////////////////////////////////////////////////////////////////////////////////////// - // Broadcast tx - /////////////////////////////////////////////////////////////////////////////////////////// - - public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback) { - checkNotNull(walletConfig); - TxBroadcaster.broadcastTx(wallet, walletConfig.peerGroup(), tx, callback); - } - - public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback, int timeoutInSec) { - checkNotNull(walletConfig); - TxBroadcaster.broadcastTx(wallet, walletConfig.peerGroup(), tx, callback, timeoutInSec); - } - /////////////////////////////////////////////////////////////////////////////////////////// // Misc diff --git a/core/src/main/java/haveno/core/xmr/wallet/TxBroadcaster.java b/core/src/main/java/haveno/core/xmr/wallet/TxBroadcaster.java deleted file mode 100644 index c20220652d..0000000000 --- a/core/src/main/java/haveno/core/xmr/wallet/TxBroadcaster.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.xmr.wallet; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import haveno.common.Timer; -import haveno.common.UserThread; -import haveno.core.xmr.exceptions.TxBroadcastException; -import haveno.core.xmr.exceptions.TxBroadcastTimeoutException; -import haveno.core.xmr.wallet.http.MemPoolSpaceTxBroadcaster; -import lombok.extern.slf4j.Slf4j; -import org.bitcoinj.core.PeerGroup; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.Utils; -import org.bitcoinj.wallet.Wallet; -import org.jetbrains.annotations.NotNull; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; - -@Slf4j -public class TxBroadcaster { - public interface Callback { - void onSuccess(Transaction transaction); - - default void onTimeout(TxBroadcastTimeoutException exception) { - Transaction tx = exception.getLocalTx(); - if (tx != null) { - // We optimistically assume that the tx broadcast succeeds later and call onSuccess on the callback handler. - // This behaviour carries less potential problems than if we would trigger a failure (e.g. which would cause - // a failed create offer attempt or failed take offer attempt). - // We have no guarantee how long it will take to get the information that sufficiently many BTC nodes have - // reported back to BitcoinJ that the tx is in their mempool. - // In normal situations that's very fast but in some cases it can take minutes (mostly related to Tor - // connection issues). So if we just go on in the application logic and treat it as successful and the - // tx will be broadcast successfully later all is fine. - // If it will fail to get broadcast, it will lead to a failure state, the same as if we would trigger a - // failure due the timeout. - // So we can assume that this behaviour will lead to less problems as otherwise. - // Long term we should implement better monitoring for Tor and the provided Bitcoin nodes to find out - // why those delays happen and add some rollback behaviour to the app state in case the tx will never - // get broadcast. - log.warn("TxBroadcaster.onTimeout called: {}", exception.toString()); - onSuccess(tx); - } else { - log.error("TxBroadcaster.onTimeout: Tx is null. exception={} ", exception.toString()); - onFailure(exception); - } - } - - void onFailure(TxBroadcastException exception); - } - - // Currently there is a bug in BitcoinJ causing the timeout at all BSQ transactions. - // It is because BitcoinJ does not handle confidence object correctly in case as tx got altered after the - // Wallet.complete() method is called which is the case for all BSQ txs. We will work on a fix for that but that - // will take more time. In the meantime we reduce the timeout to 5 seconds to avoid that the trade protocol runs - // into a timeout when using BSQ for trade fee. - // For trade fee txs we set only 1 sec timeout for now. - // FIXME - private static final int DEFAULT_BROADCAST_TIMEOUT = 5; - private static final Map broadcastTimerMap = new HashMap<>(); - - public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction localTx, Callback callback) { - broadcastTx(wallet, peerGroup, localTx, callback, DEFAULT_BROADCAST_TIMEOUT); - } - - public static void broadcastTx(Wallet wallet, PeerGroup peerGroup, Transaction tx, Callback callback, int timeOut) { - Timer timeoutTimer; - final String txId = tx.getTxId().toString(); - log.info("Txid: {} hex: {}", txId, Utils.HEX.encode(tx.bitcoinSerialize())); - if (!broadcastTimerMap.containsKey(txId)) { - timeoutTimer = UserThread.runAfter(() -> { - log.warn("Broadcast of tx {} not completed after {} sec.", txId, timeOut); - stopAndRemoveTimer(txId); - UserThread.execute(() -> callback.onTimeout(new TxBroadcastTimeoutException(tx, timeOut, wallet))); - }, timeOut); - - broadcastTimerMap.put(txId, timeoutTimer); - } else { - // Would be the wrong way how to use the API (calling 2 times a broadcast with same tx). - // An arbitrator reported that got the error after a manual payout, need to investigate why... - stopAndRemoveTimer(txId); - UserThread.execute(() -> callback.onFailure(new TxBroadcastException("We got broadcastTx called with a tx " + - "which has an open timeoutTimer. txId=" + txId, txId))); - } - - // We decided the least risky scenario is to commit the tx to the wallet and broadcast it later. - // If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have committed the tx to both bsq and btc - // wallets so the next line causes no effect. - // If it's a btc tx, the next line adds the tx to the wallet. - wallet.maybeCommitTx(tx); - - Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<>() { - @Override - public void onSuccess(@Nullable Transaction result) { - // We expect that there is still a timeout in our map, otherwise the timeout got triggered - if (broadcastTimerMap.containsKey(txId)) { - stopAndRemoveTimer(txId); - // At regtest we get called immediately back but we want to make sure that the handler is not called - // before the caller is finished. - UserThread.execute(() -> callback.onSuccess(tx)); - } else { - log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout. txId={}", txId); - } - } - - @Override - public void onFailure(@NotNull Throwable throwable) { - stopAndRemoveTimer(txId); - UserThread.execute(() -> callback.onFailure(new TxBroadcastException("We got an onFailure from " + - "the peerGroup.broadcastTransaction callback.", throwable))); - } - }, MoreExecutors.directExecutor()); - - // For better redundancy in case the broadcast via BitcoinJ fails we also - // publish the tx via mempool nodes. - MemPoolSpaceTxBroadcaster.broadcastTx(tx); - } - - private static void stopAndRemoveTimer(String txId) { - Timer timer = broadcastTimerMap.get(txId); - if (timer != null) - timer.stop(); - - broadcastTimerMap.remove(txId); - } -} diff --git a/core/src/main/java/haveno/core/xmr/wallet/WalletService.java b/core/src/main/java/haveno/core/xmr/wallet/WalletService.java index 995698bb61..ed4bc13f4f 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/WalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/WalletService.java @@ -21,12 +21,7 @@ import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.Multiset; import com.google.common.collect.SetMultimap; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; import haveno.common.config.Config; -import haveno.common.handlers.ErrorMessageHandler; -import haveno.common.handlers.ResultHandler; import haveno.core.user.Preferences; import haveno.core.xmr.exceptions.TransactionVerificationException; import haveno.core.xmr.exceptions.WalletException; @@ -34,7 +29,6 @@ import haveno.core.xmr.listeners.AddressConfidenceListener; import haveno.core.xmr.listeners.BalanceListener; import haveno.core.xmr.listeners.TxConfidenceListener; import haveno.core.xmr.setup.WalletsSetup; -import haveno.core.xmr.wallet.http.MemPoolSpaceTxBroadcaster; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import lombok.Getter; @@ -42,12 +36,10 @@ import lombok.extern.slf4j.Slf4j; import monero.wallet.MoneroWallet; import monero.wallet.model.MoneroTxWallet; import org.bitcoinj.core.Address; -import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.BlockChain; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Context; import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; @@ -73,7 +65,6 @@ import org.bitcoinj.wallet.DecryptingKeyBag; import org.bitcoinj.wallet.DeterministicSeed; import org.bitcoinj.wallet.KeyBag; import org.bitcoinj.wallet.RedeemData; -import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.Wallet; import org.bitcoinj.wallet.listeners.WalletChangeEventListener; import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener; @@ -364,19 +355,6 @@ public abstract class WalletService { } - /////////////////////////////////////////////////////////////////////////////////////////// - // Broadcast tx - /////////////////////////////////////////////////////////////////////////////////////////// - - public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback) { - TxBroadcaster.broadcastTx(wallet, walletsSetup.getPeerGroup(), tx, callback); - } - - public void broadcastTx(Transaction tx, TxBroadcaster.Callback callback, int timeOut) { - TxBroadcaster.broadcastTx(wallet, walletsSetup.getPeerGroup(), tx, callback, timeOut); - } - - /////////////////////////////////////////////////////////////////////////////////////////// // TransactionConfidence /////////////////////////////////////////////////////////////////////////////////////////// @@ -543,41 +521,6 @@ public abstract class WalletService { } - /////////////////////////////////////////////////////////////////////////////////////////// - // Empty complete Wallet - /////////////////////////////////////////////////////////////////////////////////////////// - - public void emptyBtcWallet(String toAddress, - KeyParameter aesKey, - ResultHandler resultHandler, - ErrorMessageHandler errorMessageHandler) - throws InsufficientMoneyException, AddressFormatException { - SendRequest sendRequest = SendRequest.emptyWallet(Address.fromString(params, toAddress)); - sendRequest.fee = Coin.ZERO; - sendRequest.aesKey = aesKey; - Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); - printTx("empty btc wallet", sendResult.tx); - - // For better redundancy in case the broadcast via BitcoinJ fails we also - // publish the tx via mempool nodes. - MemPoolSpaceTxBroadcaster.broadcastTx(sendResult.tx); - - Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<>() { - @Override - public void onSuccess(Transaction result) { - log.info("emptyBtcWallet onSuccess Transaction=" + result); - resultHandler.handleResult(); - } - - @Override - public void onFailure(@NotNull Throwable t) { - log.error("emptyBtcWallet onFailure " + t.toString()); - errorMessageHandler.handleErrorMessage(t.getMessage()); - } - }, MoreExecutors.directExecutor()); - } - - /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java index 256a088906..3243befb79 100644 --- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java +++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java @@ -449,7 +449,7 @@ public class XmrWalletService { // verify miner fee BigInteger feeEstimate = getFeeEstimate(tx.getWeight()); - double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal? + double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee()); log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff); diff --git a/core/src/main/java/haveno/core/xmr/wallet/http/MemPoolSpaceTxBroadcaster.java b/core/src/main/java/haveno/core/xmr/wallet/http/MemPoolSpaceTxBroadcaster.java deleted file mode 100644 index 9fd14c32a5..0000000000 --- a/core/src/main/java/haveno/core/xmr/wallet/http/MemPoolSpaceTxBroadcaster.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.xmr.wallet.http; - -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 com.google.common.util.concurrent.MoreExecutors; -import haveno.common.app.Version; -import haveno.common.config.Config; -import haveno.common.util.Utilities; -import haveno.core.user.Preferences; -import haveno.core.xmr.nodes.LocalBitcoinNode; -import haveno.network.Socks5ProxyProvider; -import haveno.network.http.HttpException; -import lombok.extern.slf4j.Slf4j; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.Utils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import static com.google.common.base.Preconditions.checkNotNull; - -@Slf4j -public class MemPoolSpaceTxBroadcaster { - private static Socks5ProxyProvider socks5ProxyProvider; - private static Preferences preferences; - private static LocalBitcoinNode localBitcoinNode; - private static final ListeningExecutorService executorService = Utilities.getListeningExecutorService( - "MemPoolSpaceTxBroadcaster", 3, 5, 10 * 60); - - public static void init(Socks5ProxyProvider socks5ProxyProvider, - Preferences preferences, - LocalBitcoinNode localBitcoinNode) { - MemPoolSpaceTxBroadcaster.socks5ProxyProvider = socks5ProxyProvider; - MemPoolSpaceTxBroadcaster.preferences = preferences; - MemPoolSpaceTxBroadcaster.localBitcoinNode = localBitcoinNode; - } - - public static void broadcastTx(Transaction tx) { - if (!Config.baseCurrencyNetwork().isMainnet()) { - log.info("MemPoolSpaceTxBroadcaster only supports mainnet"); - return; - } - - if (localBitcoinNode.shouldBeUsed()) { - log.info("A localBitcoinNode is detected and used. For privacy reasons we do not use the tx " + - "broadcast to mempool nodes in that case."); - return; - } - - if (socks5ProxyProvider == null) { - log.warn("We got broadcastTx called before init was called."); - return; - } - - String txIdToSend = tx.getTxId().toString(); - String rawTx = Utils.HEX.encode(tx.bitcoinSerialize(true)); - - List txBroadcastServices = new ArrayList<>(preferences.getDefaultTxBroadcastServices()); - // Broadcast to first service - String serviceAddress = broadcastTx(txIdToSend, rawTx, txBroadcastServices); - if (serviceAddress != null) { - // Broadcast to second service - txBroadcastServices.remove(serviceAddress); - broadcastTx(txIdToSend, rawTx, txBroadcastServices); - } - } - - @Nullable - private static String broadcastTx(String txIdToSend, String rawTx, List txBroadcastServices) { - String serviceAddress = getRandomServiceAddress(txBroadcastServices); - if (serviceAddress == null) { - log.warn("We don't have a serviceAddress available. txBroadcastServices={}", txBroadcastServices); - return null; - } - broadcastTx(serviceAddress, txIdToSend, rawTx); - return serviceAddress; - } - - private static void broadcastTx(String serviceAddress, String txIdToSend, String rawTx) { - TxBroadcastHttpClient httpClient = new TxBroadcastHttpClient(socks5ProxyProvider); - httpClient.setBaseUrl(serviceAddress); - httpClient.setIgnoreSocks5Proxy(false); - - log.info("We broadcast rawTx {} to {}", rawTx, serviceAddress); - ListenableFuture future = executorService.submit(() -> { - Thread.currentThread().setName("MemPoolSpaceTxBroadcaster @ " + serviceAddress); - return httpClient.post(rawTx, "User-Agent", "haveno/" + Version.VERSION); - }); - - Futures.addCallback(future, new FutureCallback<>() { - public void onSuccess(String txId) { - if (txId.equals(txIdToSend)) { - log.info("Broadcast of raw tx with txId {} to {} was successful. rawTx={}", - txId, serviceAddress, rawTx); - } else { - log.error("The txId we got returned from the service does not match " + - "out tx of the sending tx. txId={}; txIdToSend={}", - txId, txIdToSend); - } - } - - public void onFailure(@NotNull Throwable throwable) { - Throwable cause = throwable.getCause(); - if (cause instanceof HttpException) { - int responseCode = ((HttpException) cause).getResponseCode(); - String message = cause.getMessage(); - // See all error codes at: https://github.com/bitcoin/bitcoin/blob/master/src/rpc/protocol.h - if (responseCode == 400 && message.contains("code\":-27")) { - log.info("Broadcast of raw tx to {} failed as transaction {} is already confirmed", - serviceAddress, txIdToSend); - } else { - log.info("Broadcast of raw tx to {} failed for transaction {}. responseCode={}, error={}", - serviceAddress, txIdToSend, responseCode, message); - } - } else { - log.warn("Broadcast of raw tx with txId {} to {} failed. Error={}", - txIdToSend, serviceAddress, throwable.toString()); - } - } - }, MoreExecutors.directExecutor()); - } - - @Nullable - private static String getRandomServiceAddress(List txBroadcastServices) { - List list = checkNotNull(txBroadcastServices); - return !list.isEmpty() ? list.get(new Random().nextInt(list.size())) : null; - } -} diff --git a/core/src/main/java/haveno/core/xmr/wallet/http/TxBroadcastHttpClient.java b/core/src/main/java/haveno/core/xmr/wallet/http/TxBroadcastHttpClient.java deleted file mode 100644 index 32cc50eb07..0000000000 --- a/core/src/main/java/haveno/core/xmr/wallet/http/TxBroadcastHttpClient.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.core.xmr.wallet.http; - -import haveno.core.trade.txproof.AssetTxProofHttpClient; -import haveno.network.Socks5ProxyProvider; -import haveno.network.http.HttpClientImpl; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -class TxBroadcastHttpClient extends HttpClientImpl implements AssetTxProofHttpClient { - TxBroadcastHttpClient(Socks5ProxyProvider socks5ProxyProvider) { - super(socks5ProxyProvider); - } -} diff --git a/core/src/test/java/haveno/core/trade/txproof/xmr/XmrTxProofParserTest.java b/core/src/test/java/haveno/core/trade/txproof/xmr/XmrTxProofParserTest.java deleted file mode 100644 index 4b37085abd..0000000000 --- a/core/src/test/java/haveno/core/trade/txproof/xmr/XmrTxProofParserTest.java +++ /dev/null @@ -1,176 +0,0 @@ -package haveno.core.trade.txproof.xmr; - -import haveno.core.user.AutoConfirmSettings; -import org.junit.Before; -import org.junit.Test; - -import java.time.Instant; -import java.util.Collections; -import java.util.Date; - -import static haveno.core.trade.txproof.xmr.XmrTxProofParser.MAX_DATE_TOLERANCE; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; - -public class XmrTxProofParserTest { - private XmrTxProofModel xmrTxProofModel; - private String recipientAddressHex = "e957dac72bcec80d59b2fecacfa7522223b6a5df895b7e388e60297e85f3f867b42f43e8d9f086a99a997704ceb92bd9cd99d33952de90c9f5f93c82c62360ae"; - private String txHash = "488e48ab0c7e69028d19f787ec57fd496ff114caba9ab265bfd41a3ea0e4687d"; - private String txKey = "6c336e52ed537676968ee319af6983c80b869ca6a732b5962c02748b486f8f0f"; - private XmrTxProofParser parser; - private Date tradeDate; - - @Before - public void prepareMocksAndObjects() { - long amount = 100000000000L; - tradeDate = new Date(1574922644000L); - String serviceAddress = "127.0.0.1:8081"; - AutoConfirmSettings autoConfirmSettings = new AutoConfirmSettings(true, - 10, - 1, - Collections.singletonList(serviceAddress), - "XMR"); - - // TODO using the mocking framework would be better... - String recipientAddress = "4ATyxmFGU7h3EWu5kYR6gy6iCNFCftbsjATfbuBBjsRHJM4KTwEyeiyVNNUmsfpK1kdRxs8QoPLsZanGqe1Mby43LeyWNMF"; - xmrTxProofModel = new XmrTxProofModel( - "dummyTest", - txHash, - txKey, - recipientAddress, - amount, - tradeDate, - autoConfirmSettings); - - parser = new XmrTxProofParser(); - } - - @Test - public void testJsonRoot() { - // checking what happens when bad input is provided - assertSame(parser.parse(xmrTxProofModel, - "invalid json data").getDetail(), XmrTxProofRequest.Detail.API_INVALID); - assertSame(parser.parse(xmrTxProofModel, - "").getDetail(), XmrTxProofRequest.Detail.API_INVALID); - assertSame(parser.parse(xmrTxProofModel, - "[]").getDetail(), XmrTxProofRequest.Detail.API_INVALID); - assertSame(parser.parse(xmrTxProofModel, - "{}").getDetail(), XmrTxProofRequest.Detail.API_INVALID); - } - - @Test - public void testJsonTopLevel() { - // testing the top level fields: data and status - assertSame(parser.parse(xmrTxProofModel, - "{'data':{'title':''},'status':'fail'}") - .getDetail(), XmrTxProofRequest.Detail.TX_NOT_FOUND); - assertSame(parser.parse(xmrTxProofModel, - "{'data':{'title':''},'missingstatus':'success'}") - .getDetail(), XmrTxProofRequest.Detail.API_INVALID); - assertSame(parser.parse(xmrTxProofModel, - "{'missingdata':{'title':''},'status':'success'}") - .getDetail(), XmrTxProofRequest.Detail.API_INVALID); - } - - @Test - public void testJsonAddress() { - assertSame(parser.parse(xmrTxProofModel, - "{'data':{'missingaddress':'irrelevant'},'status':'success'}") - .getDetail(), XmrTxProofRequest.Detail.API_INVALID); - assertSame(parser.parse(xmrTxProofModel, - "{'data':{'address':'e957dac7'},'status':'success'}") - .getDetail(), XmrTxProofRequest.Detail.ADDRESS_INVALID); - } - - @Test - public void testJsonTxHash() { - String missing_tx_hash = "{'data':{'address':'" + recipientAddressHex + "'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, missing_tx_hash).getDetail(), XmrTxProofRequest.Detail.API_INVALID); - - String invalid_tx_hash = "{'data':{'address':'" + recipientAddressHex + "', 'tx_hash':'488e48'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, invalid_tx_hash).getDetail(), XmrTxProofRequest.Detail.TX_HASH_INVALID); - } - - @Test - public void testJsonTxKey() { - String missing_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, missing_tx_key).getDetail(), XmrTxProofRequest.Detail.API_INVALID); - - String invalid_tx_key = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "', " + - "'viewkey':'cdce04'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, invalid_tx_key).getDetail(), XmrTxProofRequest.Detail.TX_KEY_INVALID); - } - - @Test - public void testJsonTxTimestamp() { - String missing_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "'," + - "'viewkey':'" + txKey + "'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, missing_tx_timestamp).getDetail(), XmrTxProofRequest.Detail.API_INVALID); - - String invalid_tx_timestamp = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "', " + - "'viewkey':'" + txKey + "'," + - "'tx_timestamp':'12345'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, invalid_tx_timestamp).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); - - long tradeTimeSec = tradeDate.getTime() / 1000; - String ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE - 1); - String invalid_tx_timestamp_1ms_too_old = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "', " + - "'viewkey':'" + txKey + "'," + - "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; - assertSame(parser.parse(xmrTxProofModel, invalid_tx_timestamp_1ms_too_old).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); - - ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE); - String valid_tx_timestamp_exact_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "', " + - "'viewkey':'" + txKey + "'," + - "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; - parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE); - assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_exact_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.TRADE_DATE_NOT_MATCHING); - - ts = String.valueOf(tradeTimeSec - MAX_DATE_TOLERANCE + 1); - String valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE = "{'data':{'address':'" + recipientAddressHex + "', " + - "'tx_hash':'" + txHash + "', " + - "'viewkey':'" + txKey + "'," + - "'tx_timestamp':'" + ts + "'}, 'status':'success'}"; - assertNotSame(parser.parse(xmrTxProofModel, valid_tx_timestamp_less_than_MAX_DATE_TOLERANCE).getDetail(), XmrTxProofRequest.Detail.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':'" + epochDate + "'}" + - "}"; - assertSame(parser.parse(xmrTxProofModel, json), XmrTxProofRequest.Result.SUCCESS); - json = json.replaceFirst("777", "0"); - - assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS); - - json = json.replaceFirst("100000000000", "100000000001"); - assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.AMOUNT_NOT_MATCHING); - - // Revert change of amount - json = json.replaceFirst("100000000001", "100000000000"); - json = json.replaceFirst("'match':true", "'match':false"); - assertSame(parser.parse(xmrTxProofModel, json).getDetail(), XmrTxProofRequest.Detail.NO_MATCH_FOUND); - } - - @Test - public void testJsonFail() { - String failedJson = "{\"data\":null,\"message\":\"Cant parse tx hash: a\",\"status\":\"error\"}"; - assertSame(parser.parse(xmrTxProofModel, failedJson).getDetail(), XmrTxProofRequest.Detail.API_INVALID); - } -} diff --git a/desktop/src/main/java/haveno/desktop/app/HavenoApp.java b/desktop/src/main/java/haveno/desktop/app/HavenoApp.java index 71ad08692a..d8c03a320f 100644 --- a/desktop/src/main/java/haveno/desktop/app/HavenoApp.java +++ b/desktop/src/main/java/haveno/desktop/app/HavenoApp.java @@ -36,7 +36,6 @@ import haveno.core.user.Cookie; import haveno.core.user.CookieKey; import haveno.core.user.Preferences; import haveno.core.user.User; -import haveno.core.xmr.wallet.BtcWalletService; import haveno.core.xmr.wallet.WalletsManager; import haveno.desktop.common.view.CachingViewLoader; import haveno.desktop.common.view.View; @@ -44,9 +43,7 @@ import haveno.desktop.common.view.ViewLoader; import haveno.desktop.main.MainView; import haveno.desktop.main.debug.DebugView; import haveno.desktop.main.overlays.popups.Popup; -import haveno.desktop.main.overlays.windows.BtcEmptyWalletWindow; import haveno.desktop.main.overlays.windows.FilterWindow; -import haveno.desktop.main.overlays.windows.ManualPayoutTxWindow; import haveno.desktop.main.overlays.windows.SendAlertMessageWindow; import haveno.desktop.main.overlays.windows.ShowWalletDataWindow; import haveno.desktop.util.CssTheme; @@ -299,9 +296,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler { Utilities.isCtrlPressed(KeyCode.Q, keyEvent)) { shutDownByUser(); } else { - if (Utilities.isAltOrCtrlPressed(KeyCode.E, keyEvent)) { - injector.getInstance(BtcEmptyWalletWindow.class).show(); - } else if (Utilities.isAltOrCtrlPressed(KeyCode.M, keyEvent)) { + if (Utilities.isAltOrCtrlPressed(KeyCode.M, keyEvent)) { injector.getInstance(SendAlertMessageWindow.class).show(); } else if (Utilities.isAltOrCtrlPressed(KeyCode.F, keyEvent)) { injector.getInstance(FilterWindow.class).show(); @@ -323,11 +318,6 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler { new ShowWalletDataWindow(walletsManager).show(); else new Popup().warning(Res.get("popup.warning.walletNotInitialized")).show(); - } else if (Utilities.isAltOrCtrlPressed(KeyCode.G, keyEvent)) { - if (injector.getInstance(BtcWalletService.class).isWalletReady()) - injector.getInstance(ManualPayoutTxWindow.class).show(); - else - new Popup().warning(Res.get("popup.warning.walletNotInitialized")).show(); } else if (DevEnv.isDevMode()) { if (Utilities.isAltOrCtrlPressed(KeyCode.Z, keyEvent)) showDebugWindow(scene, injector); diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/BtcEmptyWalletWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/BtcEmptyWalletWindow.java deleted file mode 100644 index 9ad8f66d78..0000000000 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/BtcEmptyWalletWindow.java +++ /dev/null @@ -1,170 +0,0 @@ -package haveno.desktop.main.overlays.windows; - -import com.google.inject.Inject; -import haveno.common.UserThread; -import haveno.core.api.CoreMoneroConnectionsService; -import haveno.core.locale.Res; -import haveno.core.offer.OpenOfferManager; -import haveno.core.util.FormattingUtils; -import haveno.core.util.coin.CoinFormatter; -import haveno.core.xmr.wallet.BtcWalletService; -import haveno.core.xmr.wallet.Restrictions; -import haveno.desktop.components.AutoTooltipButton; -import haveno.desktop.components.InputTextField; -import haveno.desktop.main.overlays.Overlay; -import haveno.desktop.main.overlays.popups.Popup; -import haveno.desktop.util.GUIUtil; -import haveno.desktop.util.Transitions; -import haveno.network.p2p.P2PService; -import javafx.geometry.Insets; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.TextField; -import javafx.scene.input.KeyCode; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import org.bitcoinj.core.AddressFormatException; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.InsufficientMoneyException; -import org.bouncycastle.crypto.params.KeyParameter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Named; -import java.util.concurrent.TimeUnit; - -import static haveno.desktop.util.FormBuilder.addInputTextField; -import static haveno.desktop.util.FormBuilder.addMultilineLabel; -import static haveno.desktop.util.FormBuilder.addTopLabelTextField; - -public final class BtcEmptyWalletWindow extends Overlay { - protected static final Logger log = LoggerFactory.getLogger(BtcEmptyWalletWindow.class); - - private final WalletPasswordWindow walletPasswordWindow; - private final OpenOfferManager openOfferManager; - private final P2PService p2PService; - private final CoreMoneroConnectionsService connectionService; - private final BtcWalletService btcWalletService; - private final CoinFormatter btcFormatter; - - private Button emptyWalletButton; - private InputTextField addressInputTextField; - private TextField balanceTextField; - - @Inject - public BtcEmptyWalletWindow(WalletPasswordWindow walletPasswordWindow, - OpenOfferManager openOfferManager, - P2PService p2PService, - CoreMoneroConnectionsService connectionService, - BtcWalletService btcWalletService, - @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { - headLine(Res.get("emptyWalletWindow.headline", "BTC")); - width = 768; - type = Type.Instruction; - - this.p2PService = p2PService; - this.connectionService = connectionService; - this.btcWalletService = btcWalletService; - this.btcFormatter = btcFormatter; - this.walletPasswordWindow = walletPasswordWindow; - this.openOfferManager = openOfferManager; - } - - @Override - public void show() { - createGridPane(); - addHeadLine(); - addContent(); - applyStyles(); - display(); - } - - @Override - protected void setupKeyHandler(Scene scene) { - if (!hideCloseButton) { - scene.setOnKeyPressed(e -> { - if (e.getCode() == KeyCode.ESCAPE) { - e.consume(); - doClose(); - } - }); - } - } - - private void addContent() { - addMultilineLabel(gridPane, ++rowIndex, Res.get("emptyWalletWindow.info"), 0); - - Coin totalBalance = btcWalletService.getAvailableConfirmedBalance(); - balanceTextField = addTopLabelTextField(gridPane, ++rowIndex, Res.get("emptyWalletWindow.balance"), - btcFormatter.formatCoinWithCode(totalBalance), 10).second; - - addressInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("emptyWalletWindow.address")); - - closeButton = new AutoTooltipButton(Res.get("shared.cancel")); - closeButton.setOnAction(e -> { - hide(); - closeHandlerOptional.ifPresent(Runnable::run); - }); - - emptyWalletButton = new AutoTooltipButton(Res.get("emptyWalletWindow.button")); - boolean isBalanceSufficient = Restrictions.isAboveDust(totalBalance); - emptyWalletButton.setDefaultButton(isBalanceSufficient); - emptyWalletButton.setDisable(!isBalanceSufficient && addressInputTextField.getText().length() > 0); - emptyWalletButton.setOnAction(e -> { - if (addressInputTextField.getText().length() > 0 && isBalanceSufficient) { - log.warn(getClass().getSimpleName() + ".addContent() needs updated for XMR"); - } - }); - - closeButton.setDefaultButton(!isBalanceSufficient); - - HBox hBox = new HBox(); - hBox.setSpacing(10); - GridPane.setRowIndex(hBox, ++rowIndex); - hBox.getChildren().addAll(emptyWalletButton, closeButton); - gridPane.getChildren().add(hBox); - GridPane.setMargin(hBox, new Insets(10, 0, 0, 0)); - } - - private void doEmptyWallet(KeyParameter aesKey) { - if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) { - if (!openOfferManager.getObservableList().isEmpty()) { - UserThread.runAfter(() -> - new Popup().warning(Res.get("emptyWalletWindow.openOffers.warn")) - .actionButtonText(Res.get("emptyWalletWindow.openOffers.yes")) - .onAction(() -> doEmptyWallet2(aesKey)) - .show(), 300, TimeUnit.MILLISECONDS); - } else { - doEmptyWallet2(aesKey); - } - } - } - - private void doEmptyWallet2(KeyParameter aesKey) { - emptyWalletButton.setDisable(true); - openOfferManager.removeAllOpenOffers(() -> { - try { - btcWalletService.emptyBtcWallet(addressInputTextField.getText(), - aesKey, - () -> { - closeButton.updateText(Res.get("shared.close")); - balanceTextField.setText(btcFormatter.formatCoinWithCode(btcWalletService.getAvailableConfirmedBalance())); - emptyWalletButton.setDisable(true); - log.debug("wallet empty successful"); - onClose(() -> UserThread.runAfter(() -> new Popup() - .feedback(Res.get("emptyWalletWindow.sent.success")) - .show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS)); - doClose(); - }, - (errorMessage) -> { - emptyWalletButton.setDisable(false); - log.error("wallet empty failed {}", errorMessage); - }); - } catch (InsufficientMoneyException | AddressFormatException e1) { - e1.printStackTrace(); - log.error(e1.getMessage()); - emptyWalletButton.setDisable(false); - } - }); - } -} diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/ManualPayoutTxWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/ManualPayoutTxWindow.java deleted file mode 100644 index 577af816e8..0000000000 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/ManualPayoutTxWindow.java +++ /dev/null @@ -1,821 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.desktop.main.overlays.windows; - -import de.jensd.fx.fontawesome.AwesomeDude; -import de.jensd.fx.fontawesome.AwesomeIcon; -import haveno.common.UserThread; -import haveno.common.config.Config; -import haveno.common.util.Base64; -import haveno.common.util.Tuple2; -import haveno.common.util.Utilities; -import haveno.core.api.CoreMoneroConnectionsService; -import haveno.core.locale.Res; -import haveno.core.payment.validation.LengthValidator; -import haveno.core.payment.validation.PercentageNumberValidator; -import haveno.core.support.dispute.Dispute; -import haveno.core.support.dispute.mediation.MediationManager; -import haveno.core.user.BlockChainExplorer; -import haveno.core.user.Preferences; -import haveno.core.xmr.exceptions.TransactionVerificationException; -import haveno.core.xmr.exceptions.TxBroadcastException; -import haveno.core.xmr.exceptions.WalletException; -import haveno.core.xmr.wallet.TradeWalletService; -import haveno.core.xmr.wallet.TxBroadcaster; -import haveno.core.xmr.wallet.WalletsManager; -import haveno.desktop.components.AutoTooltipButton; -import haveno.desktop.components.HavenoTextArea; -import haveno.desktop.components.InputTextField; -import haveno.desktop.main.overlays.Overlay; -import haveno.desktop.main.overlays.popups.Popup; -import haveno.desktop.util.GUIUtil; -import haveno.network.p2p.P2PService; -import javafx.beans.value.ChangeListener; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.geometry.Insets; -import javafx.geometry.Orientation; -import javafx.geometry.Pos; -import javafx.scene.Scene; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.Separator; -import javafx.scene.control.TextArea; -import javafx.scene.control.Tooltip; -import javafx.scene.input.KeyCode; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import org.bitcoinj.core.Address; -import org.bitcoinj.core.AddressFormatException; -import org.bitcoinj.core.Coin; -import org.bitcoinj.core.ECKey; -import org.bitcoinj.core.SignatureDecodeException; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.Utils; -import org.bitcoinj.core.VerificationException; -import org.bitcoinj.script.Script; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import javax.inject.Inject; -import java.nio.charset.Charset; -import java.security.SignatureException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -import static haveno.desktop.util.FormBuilder.addCheckBox; -import static haveno.desktop.util.FormBuilder.addInputTextField; -import static haveno.desktop.util.FormBuilder.addTopLabelComboBox; - -// We don't translate here as it is for dev only purpose -public class ManualPayoutTxWindow extends Overlay { - private static final int HEX_HASH_LENGTH = 32 * 2; - private static final int HEX_PUBKEY_LENGTH = 33 * 2; - private static final Logger log = LoggerFactory.getLogger(ManualPayoutTxWindow.class); - private final TradeWalletService tradeWalletService; - private final P2PService p2PService; - private final MediationManager mediationManager; - private final Preferences preferences; - private final CoreMoneroConnectionsService connectionService; - private final WalletsManager walletsManager; - GridPane inputsGridPane; - GridPane importTxGridPane; - GridPane exportTxGridPane; - GridPane signTxGridPane; - GridPane buildTxGridPane; - GridPane signVerifyMsgGridPane; - CheckBox depositTxLegacy, recentTickets; - ComboBox mediationDropDown; - ObservableList disputeObservableList; - InputTextField depositTxHex; - InputTextField amountInMultisig; - InputTextField buyerPayoutAmount; - InputTextField sellerPayoutAmount; - InputTextField txFee; - InputTextField txFeePct; - InputTextField buyerAddressString; - InputTextField sellerAddressString; - InputTextField buyerPubKeyAsHex; - InputTextField sellerPubKeyAsHex; - InputTextField buyerSignatureAsHex; - InputTextField sellerSignatureAsHex; - InputTextField privateKeyHex; - InputTextField signatureHex; - TextArea importHex; - TextArea exportHex; - TextArea finalSignedTxHex; - private ChangeListener txFeeListener, amountInMultisigListener, buyerPayoutAmountListener, sellerPayoutAmountListener; - - /////////////////////////////////////////////////////////////////////////////////////////// - // Public API - /////////////////////////////////////////////////////////////////////////////////////////// - - @Inject - public ManualPayoutTxWindow(TradeWalletService tradeWalletService, - P2PService p2PService, - MediationManager mediationManager, - Preferences preferences, - CoreMoneroConnectionsService connectionService, - WalletsManager walletsManager) { - this.tradeWalletService = tradeWalletService; - this.p2PService = p2PService; - this.mediationManager = mediationManager; - this.preferences = preferences; - this.connectionService = connectionService; - this.walletsManager = walletsManager; - type = Type.Attention; - } - - @Override - public void show() { - if (headLine == null) - headLine = "Emergency MultiSig payout tool"; // We dont translate here as it is for dev only purpose - - width = 1068; - createGridPane(); - addHeadLine(); - addContent(); - addButtons(); - applyStyles(); - txFeeListener = (observable, oldValue, newValue) -> { - calculateTxFee(); - }; - buyerPayoutAmountListener = (observable, oldValue, newValue) -> { - calculateTxFee(); - }; - sellerPayoutAmountListener = (observable, oldValue, newValue) -> { - calculateTxFee(); - }; - amountInMultisigListener = (observable, oldValue, newValue) -> { - calculateTxFee(); - }; - txFee.focusedProperty().addListener(txFeeListener); - buyerPayoutAmount.focusedProperty().addListener(buyerPayoutAmountListener); - sellerPayoutAmount.focusedProperty().addListener(sellerPayoutAmountListener); - amountInMultisig.focusedProperty().addListener(amountInMultisigListener); - display(); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Protected - /////////////////////////////////////////////////////////////////////////////////////////// - - @Override - protected void setupKeyHandler(Scene scene) { - if (!hideCloseButton) { - scene.setOnKeyPressed(e -> { - if (e.getCode() == KeyCode.ESCAPE) { - e.consume(); - doClose(); - } - }); - } - } - - @Override - protected void createGridPane() { - gridPane = new GridPane(); - gridPane.setHgap(15); - gridPane.setVgap(15); - gridPane.setPadding(new Insets(64, 64, 64, 64)); - gridPane.setPrefWidth(width); - ColumnConstraints columnConstraints1 = new ColumnConstraints(); - ColumnConstraints columnConstraints2 = new ColumnConstraints(); - columnConstraints1.setPercentWidth(25); - columnConstraints2.setPercentWidth(75); - gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2); - } - - @Override - protected void cleanup() { - txFee.focusedProperty().removeListener(txFeeListener); - buyerPayoutAmount.focusedProperty().removeListener(buyerPayoutAmountListener); - sellerPayoutAmount.focusedProperty().removeListener(sellerPayoutAmountListener); - amountInMultisig.focusedProperty().removeListener(amountInMultisigListener); - super.cleanup(); - } - - private void addContent() { - rowIndex = 1; - this.disableActionButton = true; - addLeftPanelButtons(); - addInputsPane(); - addImportPane(); - addExportPane(); - addSignPane(); - addBuildPane(); - signVerifyMsgGridPane = addSignVerifyMsgPane(new GridPane()); - hideAllPanes(); - inputsGridPane.setVisible(true); - - // Notes: - // Open with alt+g - // Priv key is only visible if pw protection is removed (wallet details data (alt+j)) - // Take missing buyerPubKeyAsHex and sellerPubKeyAsHex from contract data! - // Lookup sellerPrivateKeyAsHex associated with sellerPubKeyAsHex (or buyers) in wallet details data - // sellerPubKeys/buyerPubKeys are auto generated if used the fields below - } - - private void addLeftPanelButtons() { - Button buttonInputs = new AutoTooltipButton("Inputs"); - Button buttonImport = new AutoTooltipButton("Import"); - Button buttonExport = new AutoTooltipButton("Export"); - Button buttonSign = new AutoTooltipButton("Sign"); - Button buttonBuild = new AutoTooltipButton("Build"); - Button buttonSignVerifyMsg = new AutoTooltipButton("Sign/Verify Msg"); - VBox vBox = new VBox(12, buttonInputs, buttonImport, buttonExport, buttonSign, buttonBuild, buttonSignVerifyMsg); - vBox.getChildren().forEach(button -> ((Button) button).setPrefWidth(500)); - gridPane.add(vBox, 0, rowIndex); - buttonInputs.getStyleClass().add("action-button"); - buttonInputs.setOnAction(e -> { // just show the inputs pane - hideAllPanes(); - vBox.getChildren().forEach(button -> button.getStyleClass().remove("action-button")); - buttonInputs.getStyleClass().add("action-button"); - inputsGridPane.setVisible(true); - }); - buttonImport.setOnAction(e -> { // just show the import pane - hideAllPanes(); - vBox.getChildren().forEach(button -> button.getStyleClass().remove("action-button")); - buttonImport.getStyleClass().add("action-button"); - importTxGridPane.setVisible(true); - importHex.setText(""); - }); - buttonExport.setOnAction(e -> { // show export pane and fill in the data - hideAllPanes(); - vBox.getChildren().forEach(button -> button.getStyleClass().remove("action-button")); - buttonExport.getStyleClass().add("action-button"); - exportTxGridPane.setVisible(true); - exportHex.setText(generateExportText()); - }); - buttonSign.setOnAction(e -> { // just show the sign pane - hideAllPanes(); - vBox.getChildren().forEach(button -> button.getStyleClass().remove("action-button")); - buttonSign.getStyleClass().add("action-button"); - signTxGridPane.setVisible(true); - privateKeyHex.setText(""); - signatureHex.setText(""); - }); - buttonBuild.setOnAction(e -> { // just show the build pane - hideAllPanes(); - vBox.getChildren().forEach(button -> button.getStyleClass().remove("action-button")); - buttonBuild.getStyleClass().add("action-button"); - buildTxGridPane.setVisible(true); - finalSignedTxHex.setText(""); - }); - buttonSignVerifyMsg.setOnAction(e -> { // just show the sign msg pane - hideAllPanes(); - vBox.getChildren().forEach(button -> button.getStyleClass().remove("action-button")); - buttonSignVerifyMsg.getStyleClass().add("action-button"); - signVerifyMsgGridPane.setVisible(true); - }); - } - - private void addInputsPane() { - inputsGridPane = new GridPane(); - gridPane.add(inputsGridPane, 1, rowIndex); - int rowIndexA = 0; - - depositTxLegacy = addCheckBox(inputsGridPane, rowIndexA, "depositTxLegacy"); - - Tooltip tooltip = new Tooltip(Res.get("txIdTextField.blockExplorerIcon.tooltip")); - Label blockExplorerIcon = new Label(); - blockExplorerIcon.getStyleClass().addAll("icon", "highlight"); - blockExplorerIcon.setTooltip(tooltip); - AwesomeDude.setIcon(blockExplorerIcon, AwesomeIcon.EXTERNAL_LINK); - blockExplorerIcon.setMinWidth(20); - blockExplorerIcon.setOnMouseClicked(mouseEvent -> openBlockExplorer(depositTxHex.getText())); - depositTxHex = addInputTextField(inputsGridPane, rowIndexA, "depositTxId"); - HBox hBoxTx = new HBox(12, depositTxHex, blockExplorerIcon); - hBoxTx.setAlignment(Pos.BASELINE_LEFT); - hBoxTx.setPrefWidth(800); - inputsGridPane.add(new Label(""), 0, ++rowIndexA); // spacer - inputsGridPane.add(hBoxTx, 0, ++rowIndexA); - - amountInMultisig = addInputTextField(inputsGridPane, ++rowIndexA, "amountInMultisig"); - inputsGridPane.add(new Label(""), 0, ++rowIndexA); // spacer - buyerPayoutAmount = addInputTextField(inputsGridPane, rowIndexA, "buyerPayoutAmount"); - sellerPayoutAmount = addInputTextField(inputsGridPane, rowIndexA, "sellerPayoutAmount"); - txFee = addInputTextField(inputsGridPane, rowIndexA, "Tx fee"); - txFee.setEditable(false); - txFeePct = addInputTextField(inputsGridPane, rowIndexA, "Tx fee %"); - txFeePct.setEditable(false); - PercentageNumberValidator validator = new PercentageNumberValidator(); - validator.setMaxValue(10D); - txFeePct.setValidator(validator); - - HBox hBox = new HBox(12, buyerPayoutAmount, sellerPayoutAmount, txFee, txFeePct); - hBox.setAlignment(Pos.BASELINE_LEFT); - hBox.setPrefWidth(800); - inputsGridPane.add(hBox, 0, ++rowIndexA); - buyerAddressString = addInputTextField(inputsGridPane, ++rowIndexA, "buyerPayoutAddress"); - sellerAddressString = addInputTextField(inputsGridPane, ++rowIndexA, "sellerPayoutAddress"); - buyerPubKeyAsHex = addInputTextField(inputsGridPane, ++rowIndexA, "buyerPubKeyAsHex"); - sellerPubKeyAsHex = addInputTextField(inputsGridPane, ++rowIndexA, "sellerPubKeyAsHex"); - depositTxHex.setPrefWidth(800); - depositTxLegacy.setAllowIndeterminate(false); - depositTxLegacy.setSelected(false); - depositTxHex.setValidator(new LengthValidator(HEX_HASH_LENGTH, HEX_HASH_LENGTH)); - buyerAddressString.setValidator(new LengthValidator(20, 80)); - sellerAddressString.setValidator(new LengthValidator(20, 80)); - buyerPubKeyAsHex.setValidator(new LengthValidator(HEX_PUBKEY_LENGTH, HEX_PUBKEY_LENGTH)); - sellerPubKeyAsHex.setValidator(new LengthValidator(HEX_PUBKEY_LENGTH, HEX_PUBKEY_LENGTH)); - } - - private void addImportPane() { - int rowIndexB = 0; - importTxGridPane = new GridPane(); - gridPane.add(importTxGridPane, 1, rowIndex); - importHex = new HavenoTextArea(); - importHex.setEditable(true); - importHex.setWrapText(true); - importHex.setPrefSize(800, 150); - importTxGridPane.add(importHex, 0, ++rowIndexB); - importTxGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - Button buttonImport = new AutoTooltipButton("Import From String"); - buttonImport.setOnAction(e -> { - // here we need to populate the "inputs" fields from the data contained in the TextArea - if (doImport(importHex.getText())) { - // switch back to the inputs pane - hideAllPanes(); - inputsGridPane.setVisible(true); - } - }); - HBox hBox = new HBox(12, buttonImport); - hBox.setAlignment(Pos.BASELINE_CENTER); - hBox.setPrefWidth(800); - importTxGridPane.add(hBox, 0, ++rowIndexB); - importTxGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - - final Separator separator = new Separator(Orientation.HORIZONTAL); - separator.setPadding(new Insets(10, 10, 10, 10)); - importTxGridPane.add(separator, 0, ++rowIndexB); - - importTxGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - final Tuple2> xTuple = addTopLabelComboBox(importTxGridPane, rowIndexB, "Mediation Ticket", "", 0); - mediationDropDown = xTuple.second; - recentTickets = addCheckBox(importTxGridPane, rowIndexB, "Recent Tickets"); - recentTickets.setSelected(true); - HBox hBox2 = new HBox(12, mediationDropDown, recentTickets); - hBox2.setAlignment(Pos.BASELINE_CENTER); - hBox2.setPrefWidth(800); - importTxGridPane.add(hBox2, 0, ++rowIndexB); - populateMediationTicketCombo(recentTickets.isSelected()); - recentTickets.setOnAction(e -> { - populateMediationTicketCombo(recentTickets.isSelected()); - }); - importTxGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - Button buttonImportTicket = new AutoTooltipButton("Import From Mediation Ticket"); - buttonImportTicket.setOnAction(e -> { - // here we need to populate the "inputs" fields from the chosen mediator ticket - importFromMediationTicket(mediationDropDown.getValue()); - }); - HBox hBox3 = new HBox(12, buttonImportTicket); - hBox3.setAlignment(Pos.BASELINE_CENTER); - hBox3.setPrefWidth(800); - importTxGridPane.add(hBox3, 0, ++rowIndexB); - } - - private void addExportPane() { - exportTxGridPane = new GridPane(); - gridPane.add(exportTxGridPane, 1, rowIndex); - exportHex = new HavenoTextArea(); - exportHex.setEditable(false); - exportHex.setWrapText(true); - exportHex.setPrefSize(800, 250); - exportTxGridPane.add(exportHex, 0, 1); - } - - private void addSignPane() { - int rowIndexB = 0; - signTxGridPane = new GridPane(); - gridPane.add(signTxGridPane, 1, rowIndex); - privateKeyHex = addInputTextField(inputsGridPane, ++rowIndexB, "privateKeyHex"); - signTxGridPane.add(privateKeyHex, 0, ++rowIndexB); - signatureHex = addInputTextField(signTxGridPane, ++rowIndexB, "signatureHex"); - signatureHex.setPrefWidth(800); - signatureHex.setEditable(false); - Label copyIcon = new Label(); - copyIcon.setTooltip(new Tooltip(Res.get("txIdTextField.copyIcon.tooltip"))); - AwesomeDude.setIcon(copyIcon, AwesomeIcon.COPY); - copyIcon.getStyleClass().addAll("icon", "highlight"); - copyIcon.setMinWidth(20); - copyIcon.setOnMouseClicked(mouseEvent -> Utilities.copyToClipboard(signatureHex.getText())); - HBox hBoxSig = new HBox(12, signatureHex, copyIcon); - hBoxSig.setAlignment(Pos.BASELINE_LEFT); - hBoxSig.setPrefWidth(800); - signTxGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - signTxGridPane.add(hBoxSig, 0, ++rowIndexB); - signTxGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - Button buttonLocate = new AutoTooltipButton("Locate key in wallet"); - Button buttonSign = new AutoTooltipButton("Generate Signature"); - HBox hBox = new HBox(12, buttonLocate, buttonSign); - hBox.setAlignment(Pos.BASELINE_CENTER); - hBox.setPrefWidth(800); - signTxGridPane.add(hBox, 0, ++rowIndexB); - buttonLocate.setOnAction(e -> { - if (!validateInputFields()) { - signatureHex.setText("You need to fill in the inputs tab first"); - return; - } - String walletInfo = walletsManager.getWalletsAsString(true); - String privateKeyText = findPrivForPubOrAddress(walletInfo, buyerPubKeyAsHex.getText()); - if (privateKeyText == null) { - privateKeyText = findPrivForPubOrAddress(walletInfo, sellerPubKeyAsHex.getText()); - } - if (privateKeyText == null) { - privateKeyText = "Not found in wallet"; - } - privateKeyHex.setText(privateKeyText); - }); - buttonSign.setOnAction(e -> { - signatureHex.setText(generateSignature()); - }); - } - - private void addBuildPane() { - buildTxGridPane = new GridPane(); - gridPane.add(buildTxGridPane, 1, rowIndex); - int rowIndexA = 0; - buyerSignatureAsHex = addInputTextField(buildTxGridPane, ++rowIndexA, "buyerSignatureAsHex"); - sellerSignatureAsHex = addInputTextField(buildTxGridPane, ++rowIndexA, "sellerSignatureAsHex"); - buildTxGridPane.add(new Label(""), 0, ++rowIndexA); // spacer - finalSignedTxHex = new HavenoTextArea(); - finalSignedTxHex.setEditable(false); - finalSignedTxHex.setWrapText(true); - finalSignedTxHex.setPrefSize(800, 250); - buildTxGridPane.add(finalSignedTxHex, 0, ++rowIndexA); - buildTxGridPane.add(new Label(""), 0, ++rowIndexA); // spacer - Button buttonBuild = new AutoTooltipButton("Build"); - Button buttonBroadcast = new AutoTooltipButton("Broadcast"); - HBox hBox = new HBox(12, buttonBuild, buttonBroadcast); - hBox.setAlignment(Pos.BASELINE_CENTER); - hBox.setPrefWidth(800); - buildTxGridPane.add(hBox, 0, ++rowIndexA); - buttonBuild.setOnAction(e -> { - finalSignedTxHex.setText(buildFinalTx(false)); - }); - buttonBroadcast.setOnAction(e -> { - finalSignedTxHex.setText(buildFinalTx(true)); - }); - } - - private GridPane addSignVerifyMsgPane(GridPane myGridPane) { - int rowIndexB = 0; - gridPane.add(myGridPane, 1, rowIndex); - TextArea messageText = new HavenoTextArea(); - messageText.setPromptText("Message"); - messageText.setEditable(true); - messageText.setWrapText(true); - messageText.setPrefSize(800, 150); - myGridPane.add(messageText, 0, ++rowIndexB); - myGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - InputTextField address = addInputTextField(myGridPane, ++rowIndexB, "Address"); - myGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - TextArea messageSig = new HavenoTextArea(); - messageSig.setPromptText("Signature"); - messageSig.setEditable(true); - messageSig.setWrapText(true); - messageSig.setPrefSize(800, 65); - myGridPane.add(messageSig, 0, ++rowIndexB); - myGridPane.add(new Label(""), 0, ++rowIndexB); // spacer - Button buttonSign = new AutoTooltipButton("Sign"); - Button buttonVerify = new AutoTooltipButton("Verify"); - HBox buttonBox = new HBox(12, buttonSign, buttonVerify); - buttonBox.setAlignment(Pos.BASELINE_CENTER); - buttonBox.setPrefWidth(800); - myGridPane.add(buttonBox, 0, ++rowIndexB); - - buttonSign.setOnAction(e -> { - String walletInfo = walletsManager.getWalletsAsString(true); - String privKeyHex = findPrivForPubOrAddress(walletInfo, address.getText()); - if (privKeyHex == null) { - messageSig.setText(""); - new Popup().information("Key not found in wallet").show(); - } else { - ECKey myPrivateKey = ECKey.fromPrivate(Utils.HEX.decode(privKeyHex)); - String signatureBase64 = myPrivateKey.signMessage(messageText.getText()); - messageSig.setText(signatureBase64); - } - }); - buttonVerify.setOnAction(e -> { - try { - ECKey key = ECKey.signedMessageToKey(messageText.getText(), messageSig.getText()); - Address address1 = Address.fromKey(Config.baseCurrencyNetworkParameters(), key, Script.ScriptType.P2PKH); - Address address2 = Address.fromKey(Config.baseCurrencyNetworkParameters(), key, Script.ScriptType.P2WPKH); - if (address.getText().equalsIgnoreCase(address1.toString()) || - address.getText().equalsIgnoreCase(address2.toString())) { - new Popup().information("Signature verified").show(); - } else { - new Popup().warning("Wrong signature").show(); - } - } catch (SignatureException ex) { - log.warn(ex.toString()); - new Popup().warning("Wrong signature").show(); - } - }); - return myGridPane; - } - - private void hideAllPanes() { - inputsGridPane.setVisible(false); - importTxGridPane.setVisible(false); - exportTxGridPane.setVisible(false); - signTxGridPane.setVisible(false); - buildTxGridPane.setVisible(false); - signVerifyMsgGridPane.setVisible(false); - } - - private void populateMediationTicketCombo(boolean recentTicketsOnly) { - Instant twoWeeksAgo = Instant.ofEpochSecond(Instant.now().getEpochSecond() - TimeUnit.DAYS.toSeconds(14)); - disputeObservableList = mediationManager.getDisputesAsObservableList(); - ObservableList disputeIds = FXCollections.observableArrayList(); - for (Dispute dispute :disputeObservableList) { - if (dispute.getDisputePayoutTxId() != null) // only show disputes not paid out - continue; - if (recentTicketsOnly && dispute.getOpeningDate().toInstant().isBefore(twoWeeksAgo)) - continue; - if (!disputeIds.contains(dispute.getTradeId())) - disputeIds.add(dispute.getTradeId()); - } - disputeIds.sort((a, b) -> a.compareTo(b)); - mediationDropDown.setItems(disputeIds); - } - - private void clearInputFields() { - depositTxHex.setText(""); - amountInMultisig.setText(""); - buyerPayoutAmount.setText(""); - sellerPayoutAmount.setText(""); - buyerAddressString.setText(""); - sellerAddressString.setText(""); - buyerPubKeyAsHex.setText(""); - sellerPubKeyAsHex.setText(""); - } - - private boolean validateInputFields() { - return (depositTxHex.getText().length() == HEX_HASH_LENGTH && - amountInMultisig.getText().length() > 0 && - buyerPayoutAmount.getText().length() > 0 && - sellerPayoutAmount.getText().length() > 0 && - txFee.getText().length() > 0 && - buyerAddressString.getText().length() > 0 && - sellerAddressString.getText().length() > 0 && - buyerPubKeyAsHex.getText().length() == HEX_PUBKEY_LENGTH && - sellerPubKeyAsHex.getText().length() == HEX_PUBKEY_LENGTH && - txFeePct.getValidator().validate(txFeePct.getText()).isValid); - } - - private boolean validateInputFieldsAndSignatures() { - return (validateInputFields() && - buyerSignatureAsHex.getText().length() > 0 && - sellerSignatureAsHex.getText().length() > 0); - } - - private Coin getInputFieldAsCoin(InputTextField inputTextField) { - try { - return Coin.parseCoin(inputTextField.getText().trim()); - } catch (RuntimeException ignore) { - } - return Coin.ZERO; - } - - private void calculateTxFee() { - if (buyerPayoutAmount.getText().length() > 0 && - sellerPayoutAmount.getText().length() > 0 && - amountInMultisig.getText().length() > 0) { - Coin txFeeValue = getInputFieldAsCoin(amountInMultisig) - .subtract(getInputFieldAsCoin(buyerPayoutAmount)) - .subtract(getInputFieldAsCoin(sellerPayoutAmount)); - txFee.setText(txFeeValue.toPlainString()); - double feePercent = (double) txFeeValue.value / getInputFieldAsCoin(amountInMultisig).value; - txFeePct.setText(String.format("%.2f", feePercent * 100)); - } - } - - private void openBlockExplorer(String txId) { - if (txId.length() != HEX_HASH_LENGTH) - return; - if (preferences != null) { - BlockChainExplorer blockChainExplorer = preferences.getBlockChainExplorer(); - GUIUtil.openWebPage(blockChainExplorer.txUrl + txId, false); - } - } - - private String findPrivForPubOrAddress(String walletInfo, String searchKey) { - // split the walletInfo into lines, strip whitespace - // look for lines beginning " addr:" followed by "DeterministicKey{pub HEX=" .... ", priv HEX=" - int lineIndex = 0; - while (lineIndex < walletInfo.length() && lineIndex != -1) { - lineIndex = walletInfo.indexOf(" addr:", lineIndex); - if (lineIndex == -1) { - return null; - } - int toIndex = walletInfo.indexOf("}", lineIndex); - if (toIndex == -1) { - return null; - } - String candidate1 = walletInfo.substring(lineIndex, toIndex); - lineIndex = toIndex; - // do we have the search key? - if (candidate1.indexOf(searchKey, 0) > -1) { - int startOfPriv = candidate1.indexOf("priv HEX=", 0); - if (startOfPriv > -1) { - return candidate1.substring(startOfPriv + 9, startOfPriv + 9 + HEX_HASH_LENGTH); - } - } - } - return null; - } - - private String generateExportText() { - // check that all input fields have been entered, except signatures - ArrayList fieldList = new ArrayList<>(); - fieldList.add(depositTxLegacy.isSelected() ? "legacy" : "segwit"); - fieldList.add(depositTxHex.getText()); - fieldList.add(amountInMultisig.getText()); - fieldList.add(buyerPayoutAmount.getText()); - fieldList.add(sellerPayoutAmount.getText()); - fieldList.add(buyerAddressString.getText()); - fieldList.add(sellerAddressString.getText()); - fieldList.add(buyerPubKeyAsHex.getText()); - fieldList.add(sellerPubKeyAsHex.getText()); - for (String item : fieldList) { - if (item.length() < 1) { - return "You need to fill in the inputs first"; - } - } - String listString = String.join(":", fieldList); - String base64encoded = Base64.encode(listString.getBytes()); - return base64encoded; - } - - private boolean doImport(String importedText) { - try { - clearInputFields(); - String decoded = new String(Base64.decode(importedText.replaceAll("\\s+", "")), Charset.forName("UTF-8")); - String splitArray[] = decoded.split(":"); - if (splitArray.length < 9) { - importHex.setText("Import failed - data format incorrect"); - return false; - } - int fieldIndex = 0; - depositTxLegacy.setSelected(splitArray[fieldIndex++].equalsIgnoreCase("legacy")); - depositTxHex.setText(splitArray[fieldIndex++]); - amountInMultisig.setText(splitArray[fieldIndex++]); - buyerPayoutAmount.setText(splitArray[fieldIndex++]); - sellerPayoutAmount.setText(splitArray[fieldIndex++]); - buyerAddressString.setText(splitArray[fieldIndex++]); - sellerAddressString.setText(splitArray[fieldIndex++]); - buyerPubKeyAsHex.setText(splitArray[fieldIndex++]); - sellerPubKeyAsHex.setText(splitArray[fieldIndex++]); - calculateTxFee(); - } catch (IllegalArgumentException e) { - importHex.setText("Import failed - base64 string incorrect"); - return false; - } - return true; - } - - private void importFromMediationTicket(String tradeId) { - throw new RuntimeException("ManualPayoutTxWindow.importFromMediationTicket() not adapted to XMR"); -// clearInputFields(); -// Optional optionalDispute = mediationManager.findDispute(tradeId); -// if (optionalDispute.isPresent()) { -// Dispute dispute = optionalDispute.get(); -// depositTxHex.setText(dispute.getDepositTxId()); -// if (dispute.disputeResultProperty().get() != null) { -// buyerPayoutAmount.setText(dispute.disputeResultProperty().get().getBuyerPayoutAmount().toPlainString()); -// sellerPayoutAmount.setText(dispute.disputeResultProperty().get().getSellerPayoutAmount().toPlainString()); -// } -// buyerAddressString.setText(dispute.getContract().getBuyerPayoutAddressString()); -// sellerAddressString.setText(dispute.getContract().getSellerPayoutAddressString()); -// buyerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getBuyerMultiSigPubKey())); -// sellerPubKeyAsHex.setText(Utils.HEX.encode(dispute.getContract().getSellerMultiSigPubKey())); -// // switch back to the inputs pane -// hideAllPanes(); -// inputsGridPane.setVisible(true); -// UserThread.execute(() -> new Popup().warning("Ticket imported. You still need to enter the multisig amount and specify if it is a legacy Tx").show()); -// } - } - - private String generateSignature() { - calculateTxFee(); - // check that all input fields have been entered, except signatures - if (!validateInputFields() || privateKeyHex.getText().length() < 1) { - return "You need to fill in the inputs first"; - } - - String retVal = ""; - try { - Tuple2 combined = tradeWalletService.emergencyBuildPayoutTxFrom2of2MultiSig(depositTxHex.getText(), - getInputFieldAsCoin(buyerPayoutAmount), - getInputFieldAsCoin(sellerPayoutAmount), - getInputFieldAsCoin(txFee), - buyerAddressString.getText(), - sellerAddressString.getText(), - buyerPubKeyAsHex.getText(), - sellerPubKeyAsHex.getText(), - depositTxLegacy.isSelected()); - String redeemScriptHex = combined.first; - String unsignedTxHex = combined.second; - retVal = tradeWalletService.emergencyGenerateSignature( - unsignedTxHex, - redeemScriptHex, - getInputFieldAsCoin(amountInMultisig), - privateKeyHex.getText()); - } catch (IllegalArgumentException ee) { - log.error(ee.toString()); - ee.printStackTrace(); - UserThread.execute(() -> new Popup().warning(ee.toString()).show()); - } - return retVal; - } - - private String buildFinalTx(boolean broadcastIt) { - String retVal = ""; - calculateTxFee(); - // check that all input fields have been entered, including signatures - if (!validateInputFieldsAndSignatures()) { - retVal = "You need to fill in the inputs first"; - } else { - try { - // grab data from the inputs pane, build an unsigned tx and write it to the TextArea - Tuple2 combined = tradeWalletService.emergencyBuildPayoutTxFrom2of2MultiSig(depositTxHex.getText(), - getInputFieldAsCoin(buyerPayoutAmount), - getInputFieldAsCoin(sellerPayoutAmount), - getInputFieldAsCoin(txFee), - buyerAddressString.getText(), - sellerAddressString.getText(), - buyerPubKeyAsHex.getText(), - sellerPubKeyAsHex.getText(), - depositTxLegacy.isSelected()); - String redeemScriptHex = combined.first; - String unsignedTxHex = combined.second; - Tuple2 txIdAndHex = tradeWalletService.emergencyApplySignatureToPayoutTxFrom2of2MultiSig( - unsignedTxHex, - redeemScriptHex, - buyerSignatureAsHex.getText(), - sellerSignatureAsHex.getText(), - depositTxLegacy.isSelected()); - retVal = "txId:{" + txIdAndHex.first + "}\r\ntxHex:{" + txIdAndHex.second + "}"; - - if (broadcastIt) { - TxBroadcaster.Callback callback = new TxBroadcaster.Callback() { - @Override - public void onSuccess(@Nullable Transaction result) { - log.info("onSuccess"); - UserThread.execute(() -> { - String txId = result != null ? result.getTxId().toString() : "null"; - new Popup().information("Transaction successfully published. Transaction ID: " + txId).show(); - }); - } - @Override - public void onFailure(TxBroadcastException exception) { - log.error(exception.toString()); - UserThread.execute(() -> new Popup().warning(exception.toString()).show()); - } - }; - - if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) { - try { - tradeWalletService.emergencyPublishPayoutTxFrom2of2MultiSig( - txIdAndHex.second, - callback); - } catch (AddressFormatException | WalletException | TransactionVerificationException ee) { - log.error(ee.toString()); - ee.printStackTrace(); - UserThread.execute(() -> new Popup().warning(ee.toString()).show()); - } - } - } - } catch (IllegalArgumentException | SignatureDecodeException | VerificationException ee) { - log.error(ee.toString()); - ee.printStackTrace(); - retVal = ee.toString(); - } - } - return retVal; - } - -} diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/SetXmrTxKeyWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/SetXmrTxKeyWindow.java deleted file mode 100644 index a461fc4c2e..0000000000 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/SetXmrTxKeyWindow.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * This file is part of Haveno. - * - * Haveno 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. - * - * Haveno 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 Haveno. If not, see . - */ - -package haveno.desktop.main.overlays.windows; - -import haveno.core.locale.Res; -import haveno.core.trade.txproof.xmr.XmrTxProofModel; -import haveno.core.util.validation.RegexValidator; -import haveno.desktop.components.InputTextField; -import haveno.desktop.main.overlays.Overlay; -import javafx.geometry.HPos; -import javafx.geometry.Insets; -import javafx.scene.layout.ColumnConstraints; -import javafx.scene.layout.GridPane; -import javafx.scene.layout.Priority; -import lombok.Getter; - -import javax.annotation.Nullable; - -import static haveno.common.app.DevEnv.isDevMode; -import static haveno.desktop.util.FormBuilder.addInputTextField; -import static haveno.desktop.util.FormBuilder.addMultilineLabel; -import static javafx.beans.binding.Bindings.createBooleanBinding; - -public class SetXmrTxKeyWindow extends Overlay { - - private InputTextField txHashInputTextField, txKeyInputTextField; - @Getter - private RegexValidator regexValidator; - - public SetXmrTxKeyWindow() { - type = Type.Attention; - } - - public void show() { - if (headLine == null) - headLine = Res.get("setXMRTxKeyWindow.headline"); - - width = 868; - createGridPane(); - addHeadLine(); - addContent(); - addButtons(); - - regexValidator = new RegexValidator(); - regexValidator.setPattern("[a-fA-F0-9]{64}|^$"); - regexValidator.setErrorMessage(Res.get("portfolio.pending.step2_buyer.confirmStart.proof.invalidInput")); - txHashInputTextField.setValidator(regexValidator); - txKeyInputTextField.setValidator(regexValidator); - if (isDevMode()) { - // pre-populate the fields with test data when in dev mode - txHashInputTextField.setText(XmrTxProofModel.DEV_TX_HASH); - txKeyInputTextField.setText(XmrTxProofModel.DEV_TX_KEY); - } - - actionButton.disableProperty().bind(createBooleanBinding(() -> { - String txHash = txHashInputTextField.getText(); - String txKey = txKeyInputTextField.getText(); - - // If a field is empty we allow to continue. We do not enforce that users send the data. - if (txHash.isEmpty() || txKey.isEmpty()) { - return false; - } - - // Otherwise we require that input is valid - return !txHashInputTextField.getValidator().validate(txHash).isValid || - !txKeyInputTextField.getValidator().validate(txKey).isValid; - }, - txHashInputTextField.textProperty(), txKeyInputTextField.textProperty())); - - applyStyles(); - display(); - } - - @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() { - addMultilineLabel(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.note"), 0); - txHashInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txHash"), 10); - txKeyInputTextField = addInputTextField(gridPane, ++rowIndex, Res.get("setXMRTxKeyWindow.txKey")); - } -} diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/TradeDetailsWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/TradeDetailsWindow.java index dc79810b36..463e07c83b 100644 --- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/TradeDetailsWindow.java +++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/TradeDetailsWindow.java @@ -29,7 +29,6 @@ import haveno.core.trade.Contract; import haveno.core.trade.HavenoUtils; import haveno.core.trade.Trade; import haveno.core.trade.TradeManager; -import haveno.core.trade.txproof.AssetTxProofResult; import haveno.core.util.FormattingUtils; import haveno.core.util.VolumeUtil; import haveno.core.util.coin.CoinFormatter; @@ -38,7 +37,6 @@ import haveno.desktop.components.HavenoTextArea; import haveno.desktop.main.MainView; import haveno.desktop.main.overlays.Overlay; import haveno.desktop.util.DisplayUtils; -import haveno.desktop.util.GUIUtil; import haveno.desktop.util.Layout; import haveno.network.p2p.NodeAddress; import javafx.beans.property.IntegerProperty; @@ -64,7 +62,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; -import static com.google.common.base.Preconditions.checkNotNull; import static haveno.desktop.util.DisplayUtils.getAccountWitnessDescription; import static haveno.desktop.util.FormBuilder.add2ButtonsWithBox; import static haveno.desktop.util.FormBuilder.addConfirmationLabelTextArea; @@ -188,10 +185,6 @@ public class TradeDetailsWindow extends Overlay { rows++; } - boolean showXmrProofResult = checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR") && - trade.getAssetTxProofResult() != null && - trade.getAssetTxProofResult() != AssetTxProofResult.UNDEFINED; - if (trade.getPayoutTxId() != null) rows++; boolean showDisputedTx = arbitrationManager.findOwnDispute(trade.getId()).isPresent() && @@ -202,8 +195,6 @@ public class TradeDetailsWindow extends Overlay { rows += 2; if (trade.getTradePeerNodeAddress() != null) rows++; - if (showXmrProofResult) - rows++; addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE); addConfirmationLabelTextField(gridPane, rowIndex, Res.get("shared.tradeId"), @@ -230,14 +221,6 @@ public class TradeDetailsWindow extends Overlay { addConfirmationLabelTextField(gridPane, ++rowIndex, Res.get("tradeDetailsWindow.tradePeersOnion"), trade.getTradePeerNodeAddress().getFullAddress()); - if (showXmrProofResult) { - // As the window is already overloaded we replace the tradePeersPubKeyHash field with the auto-conf state - // if XMR is the currency - addConfirmationLabelTextField(gridPane, ++rowIndex, - Res.get("portfolio.pending.step3_seller.autoConf.status.label"), - GUIUtil.getProofResultAsString(trade.getAssetTxProofResult())); - } - if (contract != null) { buyersAccountAge = getAccountWitnessDescription(accountAgeWitnessService, offer.getPaymentMethod(), buyerPaymentAccountPayload, contract.getBuyerPubKeyRing()); sellersAccountAge = getAccountWitnessDescription(accountAgeWitnessService, offer.getPaymentMethod(), sellerPaymentAccountPayload, contract.getSellerPubKeyRing()); diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index b92a4dc1ad..fa3ea6e5af 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -96,11 +96,9 @@ import haveno.desktop.components.paymentmethods.VerseForm; import haveno.desktop.components.paymentmethods.WeChatPayForm; import haveno.desktop.components.paymentmethods.WesternUnionForm; import haveno.desktop.main.overlays.popups.Popup; -import haveno.desktop.main.overlays.windows.SetXmrTxKeyWindow; import haveno.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import haveno.desktop.main.portfolio.pendingtrades.steps.TradeStepView; import haveno.desktop.util.Layout; -import haveno.desktop.util.Transitions; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.GridPane; @@ -110,7 +108,6 @@ import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; import java.util.List; -import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; import static haveno.desktop.util.FormBuilder.addButtonBusyAnimationLabel; @@ -543,44 +540,11 @@ public class BuyerStep2View extends TradeStepView { } else { showConfirmPaymentSentPopup(); } - } else if (sellersPaymentAccountPayload instanceof AssetAccountPayload && isXmrTrade()) { - 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; - } - - trade.setCounterCurrencyExtraData(txKey); - trade.setCounterCurrencyTxId(txHash); - - model.dataModel.getTradeManager().requestPersistence(); - showConfirmPaymentSentPopup(); - }) - .closeButtonText(Res.get("shared.cancel")) - .onClose(setXmrTxKeyWindow::hide) - .show(); } else { showConfirmPaymentSentPopup(); } } - private void showProofWarningPopup() { - Popup popup = new Popup(); - 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::showConfirmPaymentSentPopup) - .closeButtonText(Res.get("shared.cancel")) - .onClose(popup::hide) - .show(); - } - private void showConfirmPaymentSentPopup() { String key = "confirmPaymentSent"; if (!DevEnv.isDevMode() && DontShowAgainLookup.showAgain(key)) { diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java index 5763d7ebb8..8ba61e0b9d 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep4View.java @@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXBadge; import haveno.common.UserThread; import haveno.common.app.DevEnv; import haveno.core.locale.Res; -import haveno.core.trade.txproof.AssetTxProofResult; import haveno.core.user.DontShowAgainLookup; import haveno.core.xmr.model.XmrAddressEntry; import haveno.desktop.components.AutoTooltipButton; @@ -88,15 +87,11 @@ public class BuyerStep4View extends TradeStepView { } else { completedTradeLabel.setText(Res.get("portfolio.pending.step5_buyer.groupTitle")); } - JFXBadge autoConfBadge = new JFXBadge(new Label(""), Pos.BASELINE_RIGHT); - autoConfBadge.setText(Res.get("portfolio.pending.autoConf")); - autoConfBadge.getStyleClass().add("auto-conf"); - HBox hBox2 = new HBox(1, completedTradeLabel, autoConfBadge); + HBox hBox2 = new HBox(1, completedTradeLabel); GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10)); gridPane.getChildren().add(hBox2); GridPane.setRowSpan(hBox2, 5); - autoConfBadge.setVisible(AssetTxProofResult.COMPLETED == trade.getAssetTxProofResult()); if (trade.getDisputeState().isNotDisputed()) { addCompactTopLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.TWICE_FIRST_ROW_DISTANCE); diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 19d29ec311..3e1d8fcb95 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -40,7 +40,6 @@ import haveno.core.payment.payload.USPostalMoneyOrderAccountPayload; import haveno.core.payment.payload.WesternUnionAccountPayload; import haveno.core.trade.Contract; import haveno.core.trade.Trade; -import haveno.core.trade.txproof.AssetTxProofResult; import haveno.core.user.DontShowAgainLookup; import haveno.core.util.VolumeUtil; import haveno.desktop.components.BusyAnimation; @@ -50,7 +49,6 @@ import haveno.desktop.components.indicator.TxConfidenceIndicator; import haveno.desktop.main.overlays.popups.Popup; import haveno.desktop.main.portfolio.pendingtrades.PendingTradesViewModel; import haveno.desktop.main.portfolio.pendingtrades.steps.TradeStepView; -import haveno.desktop.util.GUIUtil; import haveno.desktop.util.Layout; import javafx.beans.value.ChangeListener; import javafx.geometry.Insets; @@ -67,7 +65,6 @@ import org.fxmisc.easybind.Subscription; import javax.annotation.Nullable; import java.util.Optional; -import static com.google.common.base.Preconditions.checkNotNull; import static haveno.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup; import static haveno.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon; import static haveno.desktop.util.FormBuilder.addTitledGroupBg; @@ -154,15 +151,6 @@ public class SellerStep3View extends TradeStepView { } } }); - - if (isXmrTrade()) { - proofResultListener = (observable, oldValue, newValue) -> { - applyAssetTxProofResult(trade.getAssetTxProofResult()); - }; - trade.getAssetTxProofResultUpdateProperty().addListener(proofResultListener); - - applyAssetTxProofResult(trade.getAssetTxProofResult()); - } } @Override @@ -179,10 +167,6 @@ public class SellerStep3View extends TradeStepView { if (timeoutTimer != null) { timeoutTimer.stop(); } - - if (isXmrTrade()) { - trade.getAssetTxProofResultUpdateProperty().removeListener(proofResultListener); - } } @@ -488,42 +472,6 @@ public class SellerStep3View extends TradeStepView { } } - private void applyAssetTxProofResult(@Nullable AssetTxProofResult result) { - checkNotNull(assetTxProofResultField); - checkNotNull(assetTxConfidenceIndicator); - - String txt = GUIUtil.getProofResultAsString(result); - assetTxProofResultField.setText(txt); - - if (result == null) { - assetTxConfidenceIndicator.setProgress(0); - return; - } - - switch (result) { - case PENDING: - case COMPLETED: - if (result.getNumRequiredConfirmations() > 0) { - int numRequiredConfirmations = result.getNumRequiredConfirmations(); - int numConfirmations = result.getNumConfirmations(); - if (numConfirmations == 0) { - assetTxConfidenceIndicator.setProgress(-1); - } else { - double progress = Math.min(1, (double) numConfirmations / (double) numRequiredConfirmations); - assetTxConfidenceIndicator.setProgress(progress); - assetTxConfidenceIndicator.getTooltip().setText( - Res.get("portfolio.pending.autoConf.blocks", - numConfirmations, numRequiredConfirmations)); - } - } - break; - default: - // Set invisible by default - assetTxConfidenceIndicator.setProgress(0); - break; - } - } - private Label createPopoverLabel(String text) { Label label = new Label(text); label.setPrefWidth(600); diff --git a/desktop/src/main/java/haveno/desktop/util/GUIUtil.java b/desktop/src/main/java/haveno/desktop/util/GUIUtil.java index d68a193ad1..a2aba8bd8d 100644 --- a/desktop/src/main/java/haveno/desktop/util/GUIUtil.java +++ b/desktop/src/main/java/haveno/desktop/util/GUIUtil.java @@ -49,7 +49,6 @@ import haveno.core.payment.PaymentAccount; import haveno.core.payment.PaymentAccountList; import haveno.core.payment.payload.PaymentMethod; import haveno.core.trade.HavenoUtils; -import haveno.core.trade.txproof.AssetTxProofResult; import haveno.core.user.DontShowAgainLookup; import haveno.core.user.Preferences; import haveno.core.user.User; @@ -104,7 +103,6 @@ import org.bitcoinj.core.Coin; import org.bitcoinj.uri.BitcoinURI; import org.jetbrains.annotations.NotNull; -import javax.annotation.Nullable; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -1018,35 +1016,6 @@ public class GUIUtil { MaterialDesignIcon.APPROVAL : MaterialDesignIcon.ALERT_CIRCLE_OUTLINE; } - public static String getProofResultAsString(@Nullable AssetTxProofResult result) { - if (result == null) { - return ""; - } - String key = "portfolio.pending.autoConf.state." + result.name(); - switch (result) { - case UNDEFINED: - return ""; - case FEATURE_DISABLED: - return Res.get(key, result.getDetails()); - case TRADE_LIMIT_EXCEEDED: - return Res.get(key); - case INVALID_DATA: - return Res.get(key, result.getDetails()); - case PAYOUT_TX_ALREADY_PUBLISHED: - case DISPUTE_OPENED: - case REQUESTS_STARTED: - return Res.get(key); - case PENDING: - return Res.get(key, result.getNumSuccessResults(), result.getNumRequiredSuccessResults(), result.getDetails()); - case COMPLETED: - case ERROR: - case FAILED: - return Res.get(key); - default: - return result.name(); - } - } - public static ScrollPane createScrollPane() { ScrollPane scrollPane = new ScrollPane(); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 51f27024ad..f5ac28286f 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1497,8 +1497,7 @@ message Trade { NodeAddress refund_agent_node_address = 27; RefundResultState refund_result_state = 28; string counter_currency_extra_data = 29; - string asset_tx_proof_result = 30; // name of AssetTxProofResult enum - string uid = 31; + string uid = 30; } message BuyerAsMakerTrade {