Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add check for refund agent if donation address is valid #4516

30 changes: 30 additions & 0 deletions core/src/main/java/bisq/core/dao/DaoFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@

import bisq.asset.Asset;

import bisq.common.config.Config;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ExceptionHandler;
import bisq.common.handlers.ResultHandler;
Expand All @@ -95,6 +96,7 @@
import java.io.IOException;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -750,4 +752,32 @@ public long getRequiredBond(BondedRoleType bondedRoleType) {
long baseFactor = daoStateService.getParamValueAsCoin(Param.BONDED_ROLE_FACTOR, height).value;
return requiredBondUnit * baseFactor;
}

public Set<String> getAllPastParamValues(Param param) {
Set<String> set = new HashSet<>();
periodService.getCycles().forEach(cycle -> {
set.add(getParamValue(param, cycle.getHeightOfFirstBlock()));
});
return set;
}

public Set<String> getAllDonationAddresses() {
// We support any of the past addresses as well as in case the peer has not enabled the DAO or is out of sync we
// do not want to break validation.
Set<String> allPastParamValues = getAllPastParamValues(Param.RECIPIENT_BTC_ADDRESS);

// If Dao is deactivated we need to add the default address as getAllPastParamValues will not return us any.
if (allPastParamValues.isEmpty()) {
allPastParamValues.add(Param.RECIPIENT_BTC_ADDRESS.getDefaultValue());
}

if (Config.baseCurrencyNetwork().isMainnet()) {
// If Dao is deactivated we need to add the past addresses used as well.
// This list need to be updated once a new address gets defined.
allPastParamValues.add("3EtUWqsGThPtjwUczw27YCo6EWvQdaPUyp"); // burning man 2019
allPastParamValues.add("3A8Zc1XioE2HRzYfbb5P8iemCS72M6vRJV"); // burningman2
}

return allPastParamValues;
}
}
21 changes: 21 additions & 0 deletions core/src/main/java/bisq/core/support/dispute/Dispute.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ public final class Dispute implements NetworkPayload {
@Nullable
private String delayedPayoutTxId;

// Added at v1.3.9
@Setter
@Nullable
private String donationAddressOfDelayedPayoutTx;
@Setter
@Nullable
private String agentsUid;


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
Expand Down Expand Up @@ -228,6 +236,8 @@ public protobuf.Dispute toProtoMessage() {
Optional.ofNullable(supportType).ifPresent(result -> builder.setSupportType(SupportType.toProtoMessage(supportType)));
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId));
Optional.ofNullable(donationAddressOfDelayedPayoutTx).ifPresent(result -> builder.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx));
Optional.ofNullable(agentsUid).ifPresent(result -> builder.setAgentsUid(agentsUid));
return builder.build();
}

Expand Down Expand Up @@ -271,6 +281,16 @@ public static Dispute fromProto(protobuf.Dispute proto, CoreProtoResolver corePr
dispute.setDelayedPayoutTxId(delayedPayoutTxId);
}

String donationAddressOfDelayedPayoutTx = proto.getDonationAddressOfDelayedPayoutTx();
if (!donationAddressOfDelayedPayoutTx.isEmpty()) {
dispute.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx);
}

String agentsUid = proto.getAgentsUid();
if (!agentsUid.isEmpty()) {
dispute.setAgentsUid(agentsUid);
}

return dispute;
}

Expand Down Expand Up @@ -382,6 +402,7 @@ public String toString() {
",\n supportType=" + supportType +
",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' +
",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' +
",\n donationAddressOfDelayedPayoutTx='" + donationAddressOfDelayedPayoutTx + '\'' +
"\n}";
}
}
69 changes: 64 additions & 5 deletions core/src/main/java/bisq/core/support/dispute/DisputeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.monetary.Altcoin;
Expand All @@ -35,6 +36,7 @@
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.Contract;
import bisq.core.trade.DelayedPayoutTxValidation;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
Expand All @@ -58,6 +60,7 @@

import javafx.beans.property.IntegerProperty;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.util.List;
Expand All @@ -66,6 +69,7 @@
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Nullable;
Expand All @@ -82,6 +86,11 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
protected final PubKeyRing pubKeyRing;
protected final DisputeListService<T> disputeListService;
private final PriceFeedService priceFeedService;
protected final DaoFacade daoFacade;

@Getter
protected final ObservableList<DelayedPayoutTxValidation.ValidationException> validationExceptions =
FXCollections.observableArrayList();


///////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -95,6 +104,7 @@ public DisputeManager(P2PService p2PService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
DaoFacade daoFacade,
PubKeyRing pubKeyRing,
DisputeListService<T> disputeListService,
PriceFeedService priceFeedService) {
Expand All @@ -105,6 +115,7 @@ public DisputeManager(P2PService p2PService,
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.openOfferManager = openOfferManager;
this.daoFacade = daoFacade;
this.pubKeyRing = pubKeyRing;
this.disputeListService = disputeListService;
this.priceFeedService = priceFeedService;
Expand Down Expand Up @@ -178,7 +189,7 @@ public void addAndPersistChatMessage(ChatMessage message) {
@Nullable
public abstract NodeAddress getAgentNodeAddress(Dispute dispute);

protected abstract Trade.DisputeState getDisputeState_StartedByPeer();
protected abstract Trade.DisputeState getDisputeStateStartedByPeer();

public abstract void cleanupDisputes();

Expand Down Expand Up @@ -209,7 +220,7 @@ public String getNrOfDisputes(boolean isBuyer, Contract contract) {
return disputeListService.getNrOfDisputes(isBuyer, contract);
}

private T getDisputeList() {
protected T getDisputeList() {
return disputeListService.getDisputeList();
}

Expand Down Expand Up @@ -241,6 +252,20 @@ public void onUpdatedDataReceived() {

tryApplyMessages();
cleanupDisputes();

getDisputeList().getList().forEach(dispute -> {
if (dispute.getAgentsUid() == null) {
dispute.setAgentsUid(UUID.randomUUID().toString());
}

try {
DelayedPayoutTxValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, getDisputeList().getList());
} catch (DelayedPayoutTxValidation.AddressException | DelayedPayoutTxValidation.DisputeReplayException e) {
log.error(e.toString());
validationExceptions.add(e);
}
});
}

public boolean isTrader(Dispute dispute) {
Expand All @@ -257,6 +282,7 @@ public Optional<Dispute> findOwnDispute(String tradeId) {
return disputeList.stream().filter(e -> e.getTradeId().equals(tradeId)).findAny();
}


///////////////////////////////////////////////////////////////////////////////////////////
// Message handler
///////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -271,13 +297,23 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa

String errorMessage = null;
Dispute dispute = openNewDisputeMessage.getDispute();
// Dispute agent sets uid to be sure to identify disputes uniquely to protect against replaying old disputes
dispute.setAgentsUid(UUID.randomUUID().toString());
dispute.setStorage(disputeListService.getStorage());
// Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before
dispute.setSupportType(openNewDisputeMessage.getSupportType());

Contract contract = dispute.getContract();
addPriceInfoMessage(dispute, 0);

try {
DelayedPayoutTxValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
DelayedPayoutTxValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
} catch (DelayedPayoutTxValidation.AddressException | DelayedPayoutTxValidation.DisputeReplayException e) {
log.error(e.toString());
validationExceptions.add(e);
}

PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
if (isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
Expand Down Expand Up @@ -310,7 +346,7 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
addMediationResultMessage(dispute);
}

// not dispute requester receives that from dispute agent
// Not-dispute-requester receives that msg from dispute agent
protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDisputeMessage) {
T disputeList = getDisputeList();
if (disputeList == null) {
Expand All @@ -320,14 +356,33 @@ protected void onPeerOpenedDisputeMessage(PeerOpenedDisputeMessage peerOpenedDis

String errorMessage = null;
Dispute dispute = peerOpenedDisputeMessage.getDispute();

Optional<Trade> optionalTrade = tradeManager.getTradeById(dispute.getTradeId());
if (!optionalTrade.isPresent()) {
return;
}

Trade trade = optionalTrade.get();
try {
DelayedPayoutTxValidation.validatePayoutTx(trade,
trade.getDelayedPayoutTx(),
dispute,
daoFacade,
btcWalletService);
} catch (DelayedPayoutTxValidation.ValidationException e) {
// The peer sent us an invalid donation address. We do not return here as we don't want to break
// mediation/arbitration and log only the issue. The dispute agent will run validation as well and will get
// a popup displayed to react.
log.error("Donation address invalid. {}", e.toString());
}

if (!isAgent(dispute)) {
if (!disputeList.contains(dispute)) {
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
dispute.setStorage(disputeListService.getStorage());
disputeList.add(dispute);
Optional<Trade> tradeOptional = tradeManager.getTradeById(dispute.getTradeId());
tradeOptional.ifPresent(trade -> trade.setDisputeState(getDisputeState_StartedByPeer()));
trade.setDisputeState(getDisputeStateStartedByPeer());
errorMessage = null;
} else {
// valid case if both have opened a dispute and agent was not online.
Expand Down Expand Up @@ -516,6 +571,7 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener,
disputeFromOpener.isSupportTicket(),
disputeFromOpener.getSupportType());
dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
dispute.setDonationAddressOfDelayedPayoutTx(disputeFromOpener.getDonationAddressOfDelayedPayoutTx());

Optional<Dispute> storedDisputeOptional = findDispute(dispute);

Expand Down Expand Up @@ -543,6 +599,9 @@ private void doSendPeerOpenedDisputeMessage(Dispute disputeFromOpener,

addPriceInfoMessage(dispute, 0);

// Dispute agent sets uid to be sure to identify disputes uniquely to protect against replaying old disputes
dispute.setAgentsUid(UUID.randomUUID().toString());

disputeList.add(dispute);

// We mirrored dispute already!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
Expand Down Expand Up @@ -88,11 +89,12 @@ public ArbitrationManager(P2PService p2PService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
DaoFacade daoFacade,
PubKeyRing pubKeyRing,
ArbitrationDisputeListService arbitrationDisputeListService,
PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
openOfferManager, pubKeyRing, arbitrationDisputeListService, priceFeedService);
openOfferManager, daoFacade, pubKeyRing, arbitrationDisputeListService, priceFeedService);
}


Expand Down Expand Up @@ -134,7 +136,7 @@ public NodeAddress getAgentNodeAddress(Dispute dispute) {
}

@Override
protected Trade.DisputeState getDisputeState_StartedByPeer() {
protected Trade.DisputeState getDisputeStateStartedByPeer() {
return Trade.DisputeState.DISPUTE_STARTED_BY_PEER;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
Expand Down Expand Up @@ -80,13 +81,15 @@ public MediationManager(P2PService p2PService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
DaoFacade daoFacade,
PubKeyRing pubKeyRing,
MediationDisputeListService mediationDisputeListService,
PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
openOfferManager, pubKeyRing, mediationDisputeListService, priceFeedService);
openOfferManager, daoFacade, pubKeyRing, mediationDisputeListService, priceFeedService);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Implement template methods
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -117,7 +120,7 @@ public void dispatchMessage(SupportMessage message) {
}

@Override
protected Trade.DisputeState getDisputeState_StartedByPeer() {
protected Trade.DisputeState getDisputeStateStartedByPeer() {
return Trade.DisputeState.MEDIATION_STARTED_BY_PEER;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.dao.DaoFacade;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
Expand Down Expand Up @@ -74,13 +75,15 @@ public RefundManager(P2PService p2PService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
DaoFacade daoFacade,
PubKeyRing pubKeyRing,
RefundDisputeListService refundDisputeListService,
PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
openOfferManager, pubKeyRing, refundDisputeListService, priceFeedService);
openOfferManager, daoFacade, pubKeyRing, refundDisputeListService, priceFeedService);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Implement template methods
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -111,7 +114,7 @@ public void dispatchMessage(SupportMessage message) {
}

@Override
protected Trade.DisputeState getDisputeState_StartedByPeer() {
protected Trade.DisputeState getDisputeStateStartedByPeer() {
return Trade.DisputeState.REFUND_REQUEST_STARTED_BY_PEER;
}

Expand Down