mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-13 17:05:40 -04:00
remove XmrTxProofService
This commit is contained in:
parent
33147e1c7c
commit
37e812dead
38 changed files with 5 additions and 3737 deletions
|
@ -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<TradeListener> 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 +
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.txproof;
|
||||
|
||||
import haveno.network.http.HttpClient;
|
||||
|
||||
public interface AssetTxProofHttpClient extends HttpClient {
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.txproof;
|
||||
|
||||
public interface AssetTxProofModel {
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.txproof;
|
||||
|
||||
public interface AssetTxProofParser<R extends AssetTxProofRequest.Result, T extends AssetTxProofModel> {
|
||||
R parse(T model, String jsonTxt);
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.txproof;
|
||||
|
||||
import haveno.common.handlers.FaultHandler;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface AssetTxProofRequest<R extends AssetTxProofRequest.Result> {
|
||||
interface Result {
|
||||
}
|
||||
|
||||
void requestFromService(Consumer<R> resultHandler, FaultHandler faultHandler);
|
||||
|
||||
void terminate();
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.txproof;
|
||||
|
||||
import haveno.common.handlers.FaultHandler;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface AssetTxProofRequestsPerTrade {
|
||||
void requestFromAllServices(Consumer<AssetTxProofResult> resultHandler, FaultHandler faultHandler);
|
||||
|
||||
void terminate();
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.txproof;
|
||||
|
||||
public interface AssetTxProofService {
|
||||
void onAllServicesInitialized();
|
||||
|
||||
void shutDown();
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<XmrTxProofRequest.Result, XmrTxProofModel> {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<XmrTxProofRequest.Result> {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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<XmrTxProofRequest.Result, XmrTxProofModel> 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<Result> 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<Result> 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;
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<XmrTxProofRequest> requests = new HashSet<>();
|
||||
|
||||
private int numSuccessResults;
|
||||
private ChangeListener<Trade.State> tradeStateListener;
|
||||
private AutoConfirmSettings.Listener autoConfirmSettingsListener;
|
||||
private ListChangeListener<Dispute> 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<AssetTxProofResult> 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<Dispute> mediationDisputes = mediationManager.getDisputesAsObservableList();
|
||||
if (isDisputed(mediationDisputes)) {
|
||||
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED);
|
||||
return;
|
||||
}
|
||||
setupMediationListener(resultHandler, mediationDisputes);
|
||||
|
||||
// Check if arbitration dispute and add listener
|
||||
ObservableList<Dispute> 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<String> 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<AssetTxProofResult> resultHandler) {
|
||||
autoConfirmSettingsListener = () -> {
|
||||
if (!autoConfirmSettings.isEnabled()) {
|
||||
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED);
|
||||
}
|
||||
};
|
||||
autoConfirmSettings.addListener(autoConfirmSettingsListener);
|
||||
}
|
||||
|
||||
private void setupTradeStateListener(Consumer<AssetTxProofResult> resultHandler) {
|
||||
tradeStateListener = (observable, oldValue, newValue) -> {
|
||||
if (trade.isPayoutPublished()) {
|
||||
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED);
|
||||
}
|
||||
};
|
||||
trade.stateProperty().addListener(tradeStateListener);
|
||||
}
|
||||
|
||||
private void setupArbitrationListener(Consumer<AssetTxProofResult> resultHandler,
|
||||
ObservableList<Dispute> refundDisputes) {
|
||||
refundListener = c -> {
|
||||
c.next();
|
||||
if (c.wasAdded() && isDisputed(c.getAddedSubList())) {
|
||||
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED);
|
||||
}
|
||||
};
|
||||
refundDisputes.addListener(refundListener);
|
||||
}
|
||||
|
||||
private void setupMediationListener(Consumer<AssetTxProofResult> resultHandler,
|
||||
ObservableList<Dispute> 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<AssetTxProofResult> 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<? extends Dispute> disputes) {
|
||||
return disputes.stream().anyMatch(e -> e.getTradeId().equals(trade.getId()));
|
||||
}
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>();
|
||||
private AutoConfirmSettings autoConfirmSettings;
|
||||
private final Map<String, ChangeListener<Trade.State>> tradeStateListenerMap = new HashMap<>();
|
||||
private ChangeListener<Number> xmrPeersListener, xmrBlockListener;
|
||||
private BootstrapListener bootstrapListener;
|
||||
private MonadicBinding<Boolean> p2pNetworkAndWalletReady;
|
||||
private ChangeListener<Boolean> 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<Trade> tradableList = tradeManager.getObservableList();
|
||||
tradableList.addListener((ListChangeListener<Trade>) c -> {
|
||||
c.next();
|
||||
if (c.wasAdded()) {
|
||||
processTrades(c.getAddedSubList());
|
||||
}
|
||||
});
|
||||
|
||||
// Process existing trades
|
||||
processTrades(tradableList);
|
||||
}
|
||||
|
||||
private void processTrades(List<? extends Trade> 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<Trade.State> tradeStateListener = (observable, oldValue, newValue) -> {
|
||||
if (isExpectedTradeState(newValue)) {
|
||||
ChangeListener<Trade.State> 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<Trade> 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<Trade> failedAndOpenTrades = Stream.concat(activeTrades.stream(), failedTradesManager.getObservableList().stream());
|
||||
Stream<Trade> closedTrades = closedTradableManager.getObservableList().stream()
|
||||
.filter(tradable -> tradable instanceof Trade)
|
||||
.map(tradable -> (Trade) tradable);
|
||||
Stream<Trade> 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;
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue