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

Add signature to dispute result and various other improvements #4543

Merged
merged 42 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4f1cbbd
Add check for refund agent if donation address is valid
chimp1984 Sep 11, 2020
2b04338
Dont allow opening refudn agent dispute if delayed payout tx is invalid.
chimp1984 Sep 11, 2020
c48abbf
Improve address validation code
chimp1984 Sep 11, 2020
d82631f
Fix some issues found during testing
chimp1984 Sep 12, 2020
677211b
Allow close dispute for refund agent without payout
chimp1984 Sep 12, 2020
08fb596
Call validatePayoutTx only after trades are initialized
chimp1984 Sep 12, 2020
05e1039
Call validatePayoutTx only after trades are initialized
chimp1984 Sep 12, 2020
7ac6e71
Dispute agent sign summary. Add tool for verification
chimp1984 Sep 12, 2020
559028e
Remove unused var
chimp1984 Sep 12, 2020
48066ae
Remove setting of pubKey as it is not needed
chimp1984 Sep 12, 2020
0c46e7d
Add more data to summary msg
chimp1984 Sep 12, 2020
de4fb17
Improve summary notes
chimp1984 Sep 13, 2020
966b22a
Fix line breaks
chimp1984 Sep 13, 2020
29f3a7c
Merge branch 'master_upstream' into allow-refund-agent-close-without-…
chimp1984 Sep 17, 2020
b0b4334
Merge branch 'master_upstream' into dispute-agents-sign-summary
chimp1984 Sep 17, 2020
1c0bef7
Merge branch 'master_upstream' into verify-donation-address-for-refun…
chimp1984 Sep 17, 2020
1c41db4
Fix wrong handling of mainnet RECIPIENT_BTC_ADDRESSes
chimp1984 Sep 17, 2020
3d4427c
Add result of filter match. Add more filter data (tx ids, json)
chimp1984 Sep 17, 2020
45cee2a
Add check for disputes with duplicated trade ID or payout tx ids
chimp1984 Sep 18, 2020
f46a991
Merge branch 'dispute-agents-sign-summary' into dispute-agent-branch
chimp1984 Sep 18, 2020
b2a9262
Merge branch 'verify-donation-address-for-refund-agent' into dispute-…
chimp1984 Sep 18, 2020
c1850cb
Merge branch 'master_upstream' into dispute-agent-branch
chimp1984 Sep 20, 2020
3293047
Set agentsUid to new uuid in case it is null from persisted data
chimp1984 Sep 20, 2020
d31deff
Remove dev log
chimp1984 Sep 20, 2020
25bc616
Add check for multiple deposit txs
chimp1984 Sep 20, 2020
4878a10
Optimize testIfDisputeTriesReplay methods to avoid that maps get crea…
chimp1984 Sep 20, 2020
c6778d6
Add copy to csv data button to report screen
chimp1984 Sep 21, 2020
72dca0b
Add cylce index
chimp1984 Sep 21, 2020
2943316
Remove agentsUid from protobuf, rename to uid
chimp1984 Sep 21, 2020
30e9add
Refactor: rename DelayedPayoutTxValidation to TradeDataValidation
chimp1984 Sep 21, 2020
3206c62
Refactor: Move RegexValidator from bisq.desktop.util.validation to bi…
chimp1984 Sep 21, 2020
987bf49
Add node address validation
chimp1984 Sep 21, 2020
baa915f
Add validateNodeAddress at onOpenNewDisputeMessage
chimp1984 Sep 21, 2020
d52199e
Merge branch 'refactor-regexvalidator' into dispute-agent-branch
chimp1984 Sep 21, 2020
c7a3f95
Rename filterString to filterTerm
chimp1984 Sep 21, 2020
76c8263
Ignore onion address validation for localhost
chimp1984 Sep 21, 2020
a9f1062
Move validation after adding dispute to list
chimp1984 Sep 21, 2020
81bea14
Show popup to peer who accepted mediators suggestion once locktime is…
chimp1984 Sep 21, 2020
f37446b
Change log level
chimp1984 Sep 25, 2020
8ac468d
Commit to trigger travis as it got stuck...
chimp1984 Sep 25, 2020
25c4b4d
Merge branch 'master_upstream' into dispute-agent-branch
chimp1984 Sep 25, 2020
423cc71
Add changes from merge conflict (class was renamed)
chimp1984 Sep 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion 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,9 +96,14 @@
import java.io.IOException;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -423,10 +429,18 @@ public int getFirstBlockOfPhaseForDisplay(int height, DaoPhase.Phase phase) {
case RESULT:
break;
}

return firstBlock;
}

public Map<Integer, Date> getBlockStartDateByCycleIndex() {
AtomicInteger index = new AtomicInteger();
Map<Integer, Date> map = new HashMap<>();
periodService.getCycles()
.forEach(cycle -> daoStateService.getBlockAtHeight(cycle.getHeightOfFirstBlock())
.ifPresent(block -> map.put(index.getAndIncrement(), new Date(block.getTime()))));
return map;
}

// Because last block in request and voting phases must not be used for making a tx as it will get confirmed in the
// next block which would be already the next phase we hide that last block to the user and add it to the break.
public int getLastBlockOfPhaseForDisplay(int height, DaoPhase.Phase phase) {
Expand Down Expand Up @@ -750,4 +764,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;
}
}
19 changes: 19 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 @@ -44,6 +44,7 @@
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

import lombok.EqualsAndHashCode;
Expand Down Expand Up @@ -103,6 +104,15 @@ public final class Dispute implements NetworkPayload {
@Nullable
private String delayedPayoutTxId;

// Added at v1.3.9
@Setter
@Nullable
private String donationAddressOfDelayedPayoutTx;
// We do not persist uid, it is only used by dispute agents to guarantee an uid.
@Setter
@Nullable
private transient String uid;


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
Expand Down Expand Up @@ -192,6 +202,7 @@ public Dispute(String tradeId,
this.supportType = supportType;

id = tradeId + "_" + traderId;
uid = UUID.randomUUID().toString();
}

@Override
Expand Down Expand Up @@ -228,6 +239,7 @@ 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));
return builder.build();
}

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

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

return dispute;
}

Expand Down Expand Up @@ -357,6 +374,7 @@ public String toString() {
return "Dispute{" +
"\n tradeId='" + tradeId + '\'' +
",\n id='" + id + '\'' +
",\n uid='" + uid + '\'' +
",\n traderId=" + traderId +
",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker +
Expand All @@ -382,6 +400,7 @@ public String toString() {
",\n supportType=" + supportType +
",\n mediatorsDisputeResult='" + mediatorsDisputeResult + '\'' +
",\n delayedPayoutTxId='" + delayedPayoutTxId + '\'' +
",\n donationAddressOfDelayedPayoutTx='" + donationAddressOfDelayedPayoutTx + '\'' +
"\n}";
}
}
89 changes: 80 additions & 9 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 @@ -36,6 +37,7 @@
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.Contract;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeDataValidation;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;

Expand All @@ -46,6 +48,8 @@

import bisq.common.UserThread;
import bisq.common.app.Version;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import bisq.common.handlers.FaultHandler;
import bisq.common.handlers.ResultHandler;
Expand All @@ -58,14 +62,18 @@

import javafx.beans.property.IntegerProperty;

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

import java.security.KeyPair;

import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

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

import javax.annotation.Nullable;
Expand All @@ -81,7 +89,15 @@ public abstract class DisputeManager<T extends DisputeList<? extends DisputeList
protected final OpenOfferManager openOfferManager;
protected final PubKeyRing pubKeyRing;
protected final DisputeListService<T> disputeListService;
private final Config config;
private final PriceFeedService priceFeedService;
protected final DaoFacade daoFacade;

@Getter
protected final ObservableList<TradeDataValidation.ValidationException> validationExceptions =
FXCollections.observableArrayList();
@Getter
private final KeyPair signatureKeyPair;


///////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -95,8 +111,10 @@ public DisputeManager(P2PService p2PService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
PubKeyRing pubKeyRing,
DaoFacade daoFacade,
KeyRing keyRing,
DisputeListService<T> disputeListService,
Config config,
PriceFeedService priceFeedService) {
super(p2PService, walletsSetup);

Expand All @@ -105,8 +123,11 @@ public DisputeManager(P2PService p2PService,
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.openOfferManager = openOfferManager;
this.pubKeyRing = pubKeyRing;
this.daoFacade = daoFacade;
this.pubKeyRing = keyRing.getPubKeyRing();
signatureKeyPair = keyRing.getSignatureKeyPair();
this.disputeListService = disputeListService;
this.config = config;
this.priceFeedService = priceFeedService;
}

Expand Down Expand Up @@ -178,7 +199,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 +230,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 +262,24 @@ public void onUpdatedDataReceived() {

tryApplyMessages();
cleanupDisputes();

ObservableList<Dispute> disputes = getDisputeList().getList();
disputes.forEach(dispute -> {
try {
TradeDataValidation.validateDonationAddress(dispute, dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
} catch (TradeDataValidation.AddressException | TradeDataValidation.NodeAddressException e) {
log.error(e.toString());
validationExceptions.add(e);
}
});

TradeDataValidation.testIfAnyDisputeTriedReplay(disputes,
disputeReplayException -> {
log.error(disputeReplayException.toString());
validationExceptions.add(disputeReplayException);
});
}

public boolean isTrader(Dispute dispute) {
Expand Down Expand Up @@ -308,9 +347,21 @@ protected void onOpenNewDisputeMessage(OpenNewDisputeMessage openNewDisputeMessa
}

addMediationResultMessage(dispute);

try {
TradeDataValidation.validateDonationAddress(dispute.getDonationAddressOfDelayedPayoutTx(), daoFacade);
TradeDataValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getBuyerNodeAddress(), config);
TradeDataValidation.validateNodeAddress(dispute, dispute.getContract().getSellerNodeAddress(), config);
} catch (TradeDataValidation.AddressException |
TradeDataValidation.DisputeReplayException |
TradeDataValidation.NodeAddressException e) {
log.error(e.toString());
validationExceptions.add(e);
}
}

// 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 +371,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 {
TradeDataValidation.validatePayoutTx(trade,
trade.getDelayedPayoutTx(),
dispute,
daoFacade,
btcWalletService);
} catch (TradeDataValidation.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.warn("Donation address is 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 +586,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 @@ -609,7 +680,7 @@ public void onFault(String errorMessage) {
}

// dispute agent send result to trader
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String text) {
public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute dispute, String summaryText) {
T disputeList = getDisputeList();
if (disputeList == null) {
log.warn("disputes is null");
Expand All @@ -621,7 +692,7 @@ public void sendDisputeResultMessage(DisputeResult disputeResult, Dispute disput
dispute.getTradeId(),
dispute.getTraderPubKeyRing().hashCode(),
false,
text,
summaryText,
p2PService.getAddress());

disputeResult.setChatMessage(chatMessage);
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 @@ -53,6 +54,8 @@
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.app.Version;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;

import org.bitcoinj.core.AddressFormatException;
Expand Down Expand Up @@ -89,11 +92,13 @@ public ArbitrationManager(P2PService p2PService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
PubKeyRing pubKeyRing,
DaoFacade daoFacade,
KeyRing keyRing,
ArbitrationDisputeListService arbitrationDisputeListService,
Config config,
PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
openOfferManager, pubKeyRing, arbitrationDisputeListService, priceFeedService);
openOfferManager, daoFacade, keyRing, arbitrationDisputeListService, config, priceFeedService);
}


Expand Down Expand Up @@ -135,7 +140,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
Loading