Skip to content

Commit

Permalink
Merge pull request #6404 from HenrikJannsen/improve-dispute-validation
Browse files Browse the repository at this point in the history
Improve dispute validation
  • Loading branch information
alejandrogarcia83 committed Nov 25, 2022
2 parents a6293a6 + b8637d0 commit 80a927a
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 19 deletions.
16 changes: 16 additions & 0 deletions core/src/main/java/bisq/core/provider/MempoolHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@

import java.io.IOException;

import java.util.concurrent.CompletableFuture;

import javax.annotation.Nullable;

@Singleton
Expand All @@ -42,4 +44,18 @@ public String getTxDetails(String txId) throws IOException {
String api = "/" + txId;
return get(api, "User-Agent", "bisq/" + Version.VERSION);
}


public CompletableFuture<String> requestTxAsHex(String txId) {
super.shutDown(); // close any prior incomplete request

return CompletableFuture.supplyAsync(() -> {
String api = "/" + txId + "/hex";
try {
return get(api, "User-Agent", "bisq/" + Version.VERSION);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;

import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -66,12 +67,19 @@ public void onSuccess(String mempoolData) {
log.info("Received mempoolData of [{}] from provider", mempoolData);
mempoolServiceCallback.set(mempoolData);
}

public void onFailure(@NotNull Throwable throwable) {
mempoolServiceCallback.setException(throwable);
}
}, MoreExecutors.directExecutor());
}


public CompletableFuture<String> requestTxAsHex(String txId) {
mempoolHttpClient.setBaseUrl(getRandomServiceAddress(txBroadcastServices));
return mempoolHttpClient.requestTxAsHex(txId);
}

public boolean switchToAnotherProvider() {
txBroadcastServices.remove(mempoolHttpClient.getBaseUrl());
return txBroadcastServices.size() > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import lombok.Getter;
Expand Down Expand Up @@ -129,7 +130,12 @@ public void checkTxIsConfirmed(String txId, Consumer<TxValidator> resultHandler)
mempoolRequest.getTxStatus(future, txId);
}

// ///////////////////////////
public CompletableFuture<String> requestTxAsHex(String txId) {
outstandingRequests++;
return new MempoolRequest(preferences, socks5ProxyProvider)
.requestTxAsHex(txId)
.whenComplete((result, throwable) -> outstandingRequests--);
}

private void validateOfferMakerTx(MempoolRequest mempoolRequest,
TxValidator txValidator,
Expand Down
18 changes: 17 additions & 1 deletion core/src/main/java/bisq/core/support/dispute/Dispute.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package bisq.core.support.dispute;

import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.Res;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.support.SupportType;
Expand All @@ -39,6 +40,8 @@

import com.google.protobuf.ByteString;

import org.bitcoinj.core.Transaction;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
Expand Down Expand Up @@ -100,7 +103,7 @@ public static protobuf.Dispute.State toProtoMessage(Dispute.State state) {
private final PubKeyRing traderPubKeyRing;
private final long tradeDate;
private final long tradePeriodEnd;
private Contract contract;
private final Contract contract;
@Nullable
private final byte[] contractHash;
@Nullable
Expand Down Expand Up @@ -163,6 +166,7 @@ public static protobuf.Dispute.State toProtoMessage(Dispute.State state) {
private transient final IntegerProperty badgeCountProperty = new SimpleIntegerProperty();

private transient FileTransferReceiver fileTransferSession = null;
private transient Optional<Transaction> cachedDepositTx = Optional.empty();

public FileTransferReceiver createOrGetFileTransferReceiver(NetworkNode networkNode,
NodeAddress peerNodeAddress,
Expand All @@ -181,6 +185,7 @@ public FileTransferSender createFileTransferSender(NetworkNode networkNode,
return new FileTransferSender(networkNode, peerNodeAddress, this.tradeId, this.traderId, this.getRoleStringForLogFile(), callback);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -501,6 +506,16 @@ public String getRoleStringForLogFile() {
+ (disputeOpenerIsMaker ? "MAKER" : "TAKER");
}

public Optional<Transaction> findDepositTx(BtcWalletService btcWalletService) {
if (cachedDepositTx.isPresent() || depositTxSerialized == null) {
return cachedDepositTx;
}

Transaction tx = new Transaction(btcWalletService.getParams(), depositTxSerialized);
cachedDepositTx = Optional.of(tx);
return cachedDepositTx;
}

@Override
public String toString() {
return "Dispute{" +
Expand Down Expand Up @@ -534,6 +549,7 @@ public String toString() {
",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' +
",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' +
",\n donationAddressOfDelayedPayoutTx='" + donationAddressOfDelayedPayoutTx + '\'' +
",\n cachedDepositTx='" + cachedDepositTx + '\'' +
"\n}";
}
}
11 changes: 7 additions & 4 deletions core/src/main/java/bisq/core/support/dispute/DisputeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -368,12 +368,12 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
addMediationResultMessage(dispute);

try {
DisputeValidation.validateDisputeData(dispute, btcWalletService);
DisputeValidation.validateNodeAddresses(dispute, config);
DisputeValidation.validateSenderNodeAddress(dispute, openNewDisputeMessage.getSenderNodeAddress());
DisputeValidation.validateDonationAddressMatchesAnyPastParamValues(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
DisputeValidation.validateNodeAddresses(dispute, config);
} catch (DisputeValidation.AddressException |
DisputeValidation.DisputeReplayException |
DisputeValidation.NodeAddressException e) {
} catch (DisputeValidation.ValidationException e) {
log.error(e.toString());
validationExceptions.add(e);
}
Expand All @@ -398,6 +398,9 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis

Trade trade = optionalTrade.get();
try {
DisputeValidation.validateDisputeData(dispute, btcWalletService);
DisputeValidation.validateNodeAddresses(dispute, config);
DisputeValidation.validateTradeAndDispute(dispute, trade);
DisputeValidation.validateDonationAddress(dispute,
Objects.requireNonNull(trade.getDelayedPayoutTx()),
btcWalletService.getParams(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,34 @@

package bisq.core.support.dispute;

import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.support.SupportType;
import bisq.core.trade.model.bisq_v1.Contract;
import bisq.core.trade.model.bisq_v1.Trade;
import bisq.core.util.JsonUtil;
import bisq.core.util.validation.RegexValidatorFactory;

import bisq.network.p2p.NodeAddress;

import bisq.common.config.Config;
import bisq.common.crypto.CryptoException;
import bisq.common.crypto.Hash;
import bisq.common.crypto.Sig;
import bisq.common.util.Tuple3;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

Expand All @@ -46,6 +56,72 @@

@Slf4j
public class DisputeValidation {
public static void validateDisputeData(Dispute dispute,
BtcWalletService btcWalletService) throws ValidationException {
try {
Contract contract = dispute.getContract();
checkArgument(contract.getOfferPayload().getId().equals(dispute.getTradeId()), "Invalid tradeId");
checkArgument(dispute.getContractAsJson().equals(JsonUtil.objectToJson(contract)), "Invalid contractAsJson");
checkArgument(Arrays.equals(Objects.requireNonNull(dispute.getContractHash()), Hash.getSha256Hash(checkNotNull(dispute.getContractAsJson()))),
"Invalid contractHash");

Optional<Transaction> depositTx = dispute.findDepositTx(btcWalletService);
if (depositTx.isPresent()) {
checkArgument(depositTx.get().getTxId().toString().equals(dispute.getDepositTxId()), "Invalid depositTxId");
checkArgument(depositTx.get().getInputs().size() >= 2, "DepositTx must have at least 2 inputs");
}

try {
// Only the dispute opener has set the signature
String makerContractSignature = dispute.getMakerContractSignature();
if (makerContractSignature != null) {
Sig.verify(contract.getMakerPubKeyRing().getSignaturePubKey(),
dispute.getContractAsJson(),
makerContractSignature);
}
String takerContractSignature = dispute.getTakerContractSignature();
if (takerContractSignature != null) {
Sig.verify(contract.getTakerPubKeyRing().getSignaturePubKey(),
dispute.getContractAsJson(),
takerContractSignature);
}
} catch (CryptoException e) {
throw new ValidationException(dispute, e.getMessage());
}
} catch (Throwable t) {
throw new ValidationException(dispute, t.getMessage());
}
}

public static void validateTradeAndDispute(Dispute dispute, Trade trade)
throws ValidationException {
try {
checkArgument(dispute.getContract().equals(trade.getContract()),
"contract must match contract from trade");

checkNotNull(trade.getDelayedPayoutTx(), "trade.getDelayedPayoutTx() must not be null");
checkNotNull(dispute.getDelayedPayoutTxId(), "delayedPayoutTxId must not be null");
checkArgument(dispute.getDelayedPayoutTxId().equals(trade.getDelayedPayoutTx().getTxId().toString()),
"delayedPayoutTxId must match delayedPayoutTxId from trade");

checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null");
checkNotNull(dispute.getDepositTxId(), "depositTxId must not be null");
checkArgument(dispute.getDepositTxId().equals(trade.getDepositTx().getTxId().toString()),
"depositTx must match depositTx from trade");

checkNotNull(dispute.getDepositTxSerialized(), "depositTxSerialized must not be null");
} catch (Throwable t) {
throw new ValidationException(dispute, t.getMessage());
}
}

public static void validateSenderNodeAddress(Dispute dispute,
NodeAddress senderNodeAddress) throws NodeAddressException {
if (!senderNodeAddress.equals(dispute.getContract().getBuyerNodeAddress())
&& !senderNodeAddress.equals(dispute.getContract().getSellerNodeAddress())) {
throw new NodeAddressException(dispute, "senderNodeAddress not matching any of the traders node addresses");
}
}

public static void validateNodeAddresses(Dispute dispute, Config config)
throws NodeAddressException {
Expand Down Expand Up @@ -81,8 +157,7 @@ public static void validateDonationAddressMatchesAnyPastParamValues(Dispute disp
public static void validateDonationAddress(Dispute dispute,
Transaction delayedPayoutTx,
NetworkParameters params,
DaoFacade daoFacade
)
DaoFacade daoFacade)
throws AddressException {
TransactionOutput output = delayedPayoutTx.getOutput(0);
Address address = output.getScriptPubKey().getToAddress(params);
Expand Down Expand Up @@ -225,26 +300,26 @@ public static class ValidationException extends Exception {
@Getter
private final Dispute dispute;

public ValidationException(Dispute dispute, String msg) {
ValidationException(Dispute dispute, String msg) {
super(msg);
this.dispute = dispute;
}
}

public static class NodeAddressException extends ValidationException {
public NodeAddressException(Dispute dispute, String msg) {
NodeAddressException(Dispute dispute, String msg) {
super(dispute, msg);
}
}

public static class AddressException extends ValidationException {
public AddressException(Dispute dispute, String msg) {
AddressException(Dispute dispute, String msg) {
super(dispute, msg);
}
}

public static class DisputeReplayException extends ValidationException {
public DisputeReplayException(Dispute dispute, String msg) {
DisputeReplayException(Dispute dispute, String msg) {
super(dispute, msg);
}
}
Expand Down
Loading

0 comments on commit 80a927a

Please sign in to comment.