support re-opening dispute if payout fails

This commit is contained in:
woodser 2024-07-31 19:36:15 -04:00
parent 79cd9f3e82
commit 3b0080dbba
9 changed files with 173 additions and 85 deletions

View File

@ -112,7 +112,7 @@ public class CoreDisputesService {
// Sends the openNewDisputeMessage to arbitrator, who will then create 2 disputes
// one for the opener, the other for the peer, see sendPeerOpenedDisputeMessage.
disputeManager.sendDisputeOpenedMessage(dispute, false, resultHandler, faultHandler);
disputeManager.sendDisputeOpenedMessage(dispute, resultHandler, faultHandler);
tradeManager.requestPersistence();
}, trade.getId());
}

View File

@ -186,8 +186,7 @@ public abstract class SupportManager {
private void onAckMessage(AckMessage ackMessage) {
if (ackMessage.getSourceType() == getAckMessageSourceType()) {
if (ackMessage.isSuccess()) {
log.info("Received AckMessage for {} with tradeId {} and uid {}",
ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getSourceUid());
log.info("Received AckMessage for {} with tradeId {} and uid {}", ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getSourceUid());
// ack message on chat message received when dispute is opened and closed
if (ackMessage.getSourceMsgClassName().equals(ChatMessage.class.getSimpleName())) {
@ -195,15 +194,35 @@ public abstract class SupportManager {
for (Dispute dispute : trade.getDisputes()) {
for (ChatMessage chatMessage : dispute.getChatMessages()) {
if (chatMessage.getUid().equals(ackMessage.getSourceUid())) {
if (dispute.isClosed()) trade.pollWalletNormallyForMs(30000); // sync to check for payout
else trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
if (trade.getDisputeState() == Trade.DisputeState.DISPUTE_REQUESTED) {
if (dispute.isClosed()) dispute.reOpen();
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
} else if (dispute.isClosed()) {
trade.pollWalletNormallyForMs(30000); // sync to check for payout
}
}
}
}
}
} else {
log.warn("Received AckMessage with error state for {} with tradeId {} and errorMessage={}",
ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getErrorMessage());
log.warn("Received AckMessage with error state for {} with tradeId={}, sender={}, errorMessage={}",
ackMessage.getSourceMsgClassName(), ackMessage.getSourceId(), ackMessage.getSenderNodeAddress(), ackMessage.getErrorMessage());
// nack message on chat message received when dispute closed message is nacked
if (ackMessage.getSourceMsgClassName().equals(ChatMessage.class.getSimpleName())) {
Trade trade = tradeManager.getTrade(ackMessage.getSourceId());
for (Dispute dispute : trade.getDisputes()) {
for (ChatMessage chatMessage : dispute.getChatMessages()) {
if (chatMessage.getUid().equals(ackMessage.getSourceUid())) {
if (trade.getDisputeState().isCloseRequested()) {
log.warn("DisputeCloseMessage was nacked. We close the dispute now. tradeId={}, nack sender={}", trade.getId(), ackMessage.getSenderNodeAddress());
dispute.setIsClosed();
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
}
}
}
}
}
}
getAllChatMessages(ackMessage.getSourceId()).stream()

View File

@ -77,6 +77,10 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
REOPENED,
CLOSED;
public boolean isOpen() {
return this == NEW || this == OPEN || this == REOPENED;
}
public static Dispute.State fromProto(protobuf.Dispute.State state) {
return ProtoUtil.enumFromProto(Dispute.State.class, state.name());
}

View File

@ -326,7 +326,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// trader sends message to arbitrator to open dispute
public void sendDisputeOpenedMessage(Dispute dispute,
boolean reOpen,
ResultHandler resultHandler,
FaultHandler faultHandler) {
@ -356,7 +355,16 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
boolean reOpen = storedDisputeOptional.isPresent() && storedDisputeOptional.get().isClosed();
if (!storedDisputeOptional.isPresent() || reOpen) {
// add or re-open dispute
if (reOpen) {
dispute = storedDisputeOptional.get();
} else {
disputeList.add(dispute);
}
String disputeInfo = getDisputeInfo(dispute);
String sysMsg = dispute.isSupportTicket() ?
Res.get("support.youOpenedTicket", disputeInfo, Version.VERSION) :
@ -371,9 +379,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
p2PService.getAddress());
chatMessage.setSystemMessage(true);
dispute.addAndPersistChatMessage(chatMessage);
if (!reOpen) {
disputeList.add(dispute);
}
// create dispute opened message
trade.exportMultisigHex();
@ -392,6 +397,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
recordPendingMessage(disputeOpenedMessage.getClass().getSimpleName());
// send dispute opened message
trade.setDisputeState(Trade.DisputeState.DISPUTE_REQUESTED);
mailboxMessageService.sendEncryptedMailboxMessage(agentNodeAddress,
dispute.getAgentPubKeyRing(),
disputeOpenedMessage,
@ -425,7 +431,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// We use the chatMessage wrapped inside the openNewDisputeMessage for
// the state, as that is displayed to the user and we only persist that msg
chatMessage.setStoredInMailbox(true);
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_REQUESTED);
requestPersistence();
resultHandler.handleResult();
}
@ -442,6 +447,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// We use the chatMessage wrapped inside the openNewDisputeMessage for
// the state, as that is displayed to the user and we only persist that msg
chatMessage.setSendMessageError(errorMessage);
trade.setDisputeState(Trade.DisputeState.NO_DISPUTE);
requestPersistence();
faultHandler.handleFault("Sending dispute message failed: " +
errorMessage, new DisputeMessageDeliveryFailedException());
@ -460,16 +466,30 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// arbitrator receives dispute opened message from opener, opener's peer receives from arbitrator
protected void handleDisputeOpenedMessage(DisputeOpenedMessage message) {
Dispute dispute = message.getDispute();
log.info("Processing {} with trade {}, dispute {}", message.getClass().getSimpleName(), dispute.getTradeId(), dispute.getId());
Dispute msgDispute = message.getDispute();
log.info("Processing {} with trade {}, dispute {}", message.getClass().getSimpleName(), msgDispute.getTradeId(), msgDispute.getId());
// get trade
Trade trade = tradeManager.getTrade(dispute.getTradeId());
Trade trade = tradeManager.getTrade(msgDispute.getTradeId());
if (trade == null) {
log.warn("Dispute trade {} does not exist", dispute.getTradeId());
log.warn("Dispute trade {} does not exist", msgDispute.getTradeId());
return;
}
if (trade.isPayoutPublished()) {
log.warn("Dispute trade {} payout already published", msgDispute.getTradeId());
return;
}
// find existing dispute
Optional<Dispute> storedDisputeOptional = findDispute(msgDispute);
// determine if re-opening dispute
boolean reOpen = storedDisputeOptional.isPresent() && storedDisputeOptional.get().isClosed();
// use existing dispute or create new
Dispute dispute = reOpen ? storedDisputeOptional.get() : msgDispute;
// process on trade thread
ThreadUtils.execute(() -> {
synchronized (trade) {
String errorMessage = null;
@ -508,14 +528,20 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
}
// get sender
senderPubKeyRing = trade.isArbitrator() ? (dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing()) : trade.getArbitrator().getPubKeyRing();
TradePeer sender = trade.getTradePeer(senderPubKeyRing);
TradePeer sender;
if (reOpen) { // re-open can come from either peer
sender = trade.isArbitrator() ? trade.getTradePeer(message.getSenderNodeAddress()) : trade.getArbitrator();
senderPubKeyRing = sender.getPubKeyRing();
} else {
senderPubKeyRing = trade.isArbitrator() ? (dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing()) : trade.getArbitrator().getPubKeyRing();
sender = trade.getTradePeer(senderPubKeyRing);
}
if (sender == null) throw new RuntimeException("Pub key ring is not from arbitrator, buyer, or seller");
// update sender node address
sender.setNodeAddress(message.getSenderNodeAddress());
// message to trader is expected from arbitrator
// verify message to trader is expected from arbitrator
if (!trade.isArbitrator() && sender != trade.getArbitrator()) {
throw new RuntimeException(message.getClass().getSimpleName() + " to trader is expected only from arbitrator");
}
@ -533,16 +559,29 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// add chat message with price info
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
// add dispute
// add or re-open dispute
synchronized (disputeList) {
if (!disputeList.contains(dispute)) {
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
if (!storedDisputeOptional.isPresent()) {
disputeList.add(dispute);
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
if (!disputeList.contains(msgDispute)) {
if (!storedDisputeOptional.isPresent() || reOpen) {
// send dispute opened message to peer if arbitrator
if (trade.isArbitrator()) sendDisputeOpenedMessageToPeer(dispute, contract, dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(), trade.getSelf().getUpdatedMultisigHex());
// update trade state
if (reOpen) {
trade.setDisputeState(Trade.DisputeState.DISPUTE_OPENED);
} else {
disputeList.add(dispute);
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
}
// reset buyer and seller unsigned payout tx hex
trade.getBuyer().setUnsignedPayoutTxHex(null);
trade.getSeller().setUnsignedPayoutTxHex(null);
// send dispute opened message to other peer if arbitrator
if (trade.isArbitrator()) {
TradePeer senderPeer = sender == trade.getMaker() ? trade.getTaker() : trade.getMaker();
if (senderPeer != trade.getMaker() && senderPeer != trade.getTaker()) throw new RuntimeException("Sender peer is not maker or taker, address=" + senderPeer.getNodeAddress());
sendDisputeOpenedMessageToPeer(dispute, contract, senderPeer.getPubKeyRing(), trade.getSelf().getUpdatedMultisigHex());
}
tradeManager.requestPersistence();
errorMessage = null;
} else {
@ -553,7 +592,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// add chat message with mediation info if applicable
addMediationResultMessage(dispute);
} else {
throw new RuntimeException("We got a dispute msg that we have already stored. TradeId = " + dispute.getTradeId());
throw new RuntimeException("We got a dispute msg that we have already stored. TradeId = " + msgDispute.getTradeId());
}
}
} catch (Exception e) {
@ -566,7 +605,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
// use chat message instead of open dispute message for the ack
ObservableList<ChatMessage> messages = message.getDispute().getChatMessages();
if (!messages.isEmpty()) {
ChatMessage msg = messages.get(0);
ChatMessage msg = messages.get(messages.size() - 1); // send ack to sender of last chat message
sendAckMessage(msg, senderPubKeyRing, errorMessage == null, errorMessage);
}
@ -580,7 +619,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
Contract contractFromOpener,
PubKeyRing pubKeyRing,
String updatedMultisigHex) {
log.info("{}.sendPeerOpenedDisputeMessage() with trade {}, dispute {}", getClass().getSimpleName(), disputeFromOpener.getTradeId(), disputeFromOpener.getId());
log.info("{} sendPeerOpenedDisputeMessage() with trade {}, dispute {}", getClass().getSimpleName(), disputeFromOpener.getTradeId(), disputeFromOpener.getId());
// We delay a bit for sending the message to the peer to allow that a openDispute message from the peer is
// being used as the valid msg. If dispute agent was offline and both peer requested we want to see the correct
// message and not skip the system message of the peer as it would be the case if we have created the system msg
@ -602,6 +641,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
return;
}
// create mirrored dispute
Dispute dispute = new Dispute(new Date().getTime(),
disputeFromOpener.getTradeId(),
pubKeyRing.hashCode(),
@ -627,10 +667,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
dispute.setDonationAddressOfDelayedPayoutTx(disputeFromOpener.getDonationAddressOfDelayedPayoutTx());
// skip if dispute already open
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
// Valid case if both have opened a dispute and agent was not online.
if (storedDisputeOptional.isPresent()) {
if (storedDisputeOptional.isPresent() && !storedDisputeOptional.get().isClosed()) {
log.info("We got a dispute already open for that trade and trading peer. TradeId = {}", dispute.getTradeId());
return;
}
@ -652,8 +691,15 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
addPriceInfoMessage(dispute, 0);
synchronized (disputeList) {
disputeList.add(dispute);
// add or re-open dispute
boolean reOpen = storedDisputeOptional.isPresent() && storedDisputeOptional.get().isClosed();
if (reOpen) {
dispute = storedDisputeOptional.get();
dispute.reOpen();
} else {
synchronized (disputeList) {
disputeList.add(dispute);
}
}
// get trade
@ -663,10 +709,10 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
return;
}
// We mirrored dispute already!
Contract contract = dispute.getContract();
PubKeyRing peersPubKeyRing = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing();
NodeAddress peersNodeAddress = dispute.isDisputeOpenerIsBuyer() ? contract.getSellerNodeAddress() : contract.getBuyerNodeAddress();
// create dispute opened message with peer dispute
TradePeer peer = trade.getTradePeer(pubKeyRing);
PubKeyRing peersPubKeyRing = peer.getPubKeyRing();
NodeAddress peersNodeAddress = peer.getNodeAddress();
DisputeOpenedMessage peerOpenedDisputeMessage = new DisputeOpenedMessage(dispute,
p2PService.getAddress(),
UUID.randomUUID().toString(),
@ -754,7 +800,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute.addAndPersistChatMessage(chatMessage);
}
// create dispute payout tx once per trader if we have their updated multisig hex
// create dispute payout tx
TradePeer receiver = trade.getTradePeer(dispute.getTraderPubKeyRing());
if (!trade.isPayoutPublished() && receiver.getUpdatedMultisigHex() != null && receiver.getUnsignedPayoutTxHex() == null) {
createDisputePayoutTx(trade, dispute.getContract(), disputeResult, true);
@ -906,8 +952,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
if (updateState) {
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
trade.updatePayout(payoutTx);
if (trade.getBuyer().getUpdatedMultisigHex() != null && trade.getBuyer().getUnsignedPayoutTxHex() == null) trade.getBuyer().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
if (trade.getSeller().getUpdatedMultisigHex() != null && trade.getSeller().getUnsignedPayoutTxHex() == null) trade.getSeller().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
if (trade.getBuyer().getUpdatedMultisigHex() != null) trade.getBuyer().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
if (trade.getSeller().getUpdatedMultisigHex() != null) trade.getSeller().setUnsignedPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
}
trade.requestPersistence();
return payoutTx;
@ -942,21 +988,21 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
return keyRing.getPubKeyRing().equals(dispute.getAgentPubKeyRing());
}
private Optional<Dispute> findDispute(Dispute dispute) {
public Optional<Dispute> findDispute(Dispute dispute) {
return findDispute(dispute.getTradeId(), dispute.getTraderId());
}
protected Optional<Dispute> findDispute(DisputeResult disputeResult) {
public Optional<Dispute> findDispute(DisputeResult disputeResult) {
ChatMessage chatMessage = disputeResult.getChatMessage();
checkNotNull(chatMessage, "chatMessage must not be null");
return findDispute(disputeResult.getTradeId(), disputeResult.getTraderId());
}
private Optional<Dispute> findDispute(ChatMessage message) {
public Optional<Dispute> findDispute(ChatMessage message) {
return findDispute(message.getTradeId(), message.getTraderId());
}
protected Optional<Dispute> findDispute(String tradeId, int traderId) {
public Optional<Dispute> findDispute(String tradeId, int traderId) {
T disputeList = getDisputeList();
if (disputeList == null) {
log.warn("disputes is null");

View File

@ -308,7 +308,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
if (trade.isPayoutPublished()) {
log.info("Dispute payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
} else {
if (e instanceof IllegalArgumentException) throw e;
if (e instanceof IllegalArgumentException || e instanceof IllegalStateException) throw e;
else throw new RuntimeException("Failed to sign and publish dispute payout tx from arbitrator for " + trade.getClass().getSimpleName() + " " + tradeId + ": " + e.getMessage(), e);
}
}
@ -328,14 +328,18 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
requestPersistence(trade);
} catch (Exception e) {
log.warn("Error processing dispute closed message: " + e.getMessage());
log.warn("Error processing dispute closed message: {}", e.getMessage());
e.printStackTrace();
requestPersistence(trade);
// nack bad message and do not reprocess
if (e instanceof IllegalArgumentException) {
if (e instanceof IllegalArgumentException || e instanceof IllegalStateException) {
trade.getArbitrator().setDisputeClosedMessage(null); // message is processed
trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o).";
trade.prependErrorMessage(warningMsg);
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), false, e.getMessage());
HavenoUtils.havenoSetup.getTopErrorMsg().set(warningMsg);
requestPersistence(trade);
throw e;
}
@ -442,12 +446,16 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// sign arbitrator-signed payout tx
if (trade.getPayoutTxHex() == null) {
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(unsignedPayoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing arbitrator-signed payout tx");
String signedMultisigTxHex = result.getSignedMultisigTxHex();
disputeTxSet.setMultisigTxHex(signedMultisigTxHex);
trade.setPayoutTxHex(signedMultisigTxHex);
requestPersistence(trade);
try {
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(unsignedPayoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new RuntimeException("Error signing arbitrator-signed payout tx");
String signedMultisigTxHex = result.getSignedMultisigTxHex();
disputeTxSet.setMultisigTxHex(signedMultisigTxHex);
trade.setPayoutTxHex(signedMultisigTxHex);
requestPersistence(trade);
} catch (Exception e) {
throw new IllegalStateException(e);
}
// verify mining fee is within tolerance by recreating payout tx
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?

View File

@ -321,7 +321,11 @@ public abstract class Trade implements Tradable, Model {
}
public boolean isOpen() {
return this == DisputeState.DISPUTE_OPENED;
return isRequested() && !isClosed();
}
public boolean isCloseRequested() {
return this.ordinal() >= DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG.ordinal();
}
public boolean isClosed() {

View File

@ -37,7 +37,6 @@ import haveno.core.offer.OfferUtil;
import haveno.core.payment.payload.PaymentAccountPayload;
import haveno.core.support.SupportType;
import haveno.core.support.dispute.Dispute;
import haveno.core.support.dispute.DisputeAlreadyOpenException;
import haveno.core.support.dispute.DisputeList;
import haveno.core.support.dispute.DisputeManager;
import haveno.core.support.dispute.arbitration.ArbitrationManager;
@ -67,6 +66,7 @@ import haveno.network.p2p.P2PService;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javafx.beans.property.ObjectProperty;
@ -545,33 +545,38 @@ public class PendingTradesDataModel extends ActivatableDataModel {
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED);
sendDisputeOpenedMessage(dispute, false, disputeManager);
sendDisputeOpenedMessage(dispute, disputeManager);
tradeManager.requestPersistence();
} else if (useArbitration) {
// Only if we have completed mediation we allow arbitration
disputeManager = arbitrationManager;
Dispute dispute = disputesService.createDisputeForTrade(trade, offer, pubKeyRingProvider.get(), isMaker, isSupportTicket);
trade.exportMultisigHex();
sendDisputeOpenedMessage(dispute, false, disputeManager);
sendDisputeOpenedMessage(dispute, disputeManager);
tradeManager.requestPersistence();
} else {
log.warn("Invalid dispute state {}", disputeState.name());
}
}
private void sendDisputeOpenedMessage(Dispute dispute, boolean reOpen, DisputeManager<? extends DisputeList<Dispute>> disputeManager) {
disputeManager.sendDisputeOpenedMessage(dispute, reOpen,
() -> navigation.navigateTo(MainView.class, SupportView.class, ArbitrationClientView.class), (errorMessage, throwable) -> {
if ((throwable instanceof DisputeAlreadyOpenException)) {
errorMessage += "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg");
new Popup().warning(errorMessage)
.actionButtonText(Res.get("portfolio.pending.openAgainDispute.button"))
.onAction(() -> sendDisputeOpenedMessage(dispute, true, disputeManager))
.closeButtonText(Res.get("shared.cancel")).show();
} else {
new Popup().warning(errorMessage).show();
}
});
private void sendDisputeOpenedMessage(Dispute dispute, DisputeManager<? extends DisputeList<Dispute>> disputeManager) {
Optional<Dispute> optionalDispute = disputeManager.findDispute(dispute);
boolean disputeClosed = optionalDispute.isPresent() && optionalDispute.get().isClosed();
if (disputeClosed) {
String msg = "We got a dispute already open for that trade and trading peer.\n" + "TradeId = " + dispute.getTradeId();
new Popup().warning(msg + "\n\n" + Res.get("portfolio.pending.openAgainDispute.msg"))
.actionButtonText(Res.get("portfolio.pending.openAgainDispute.button"))
.onAction(() -> doSendDisputeOpenedMessage(dispute, disputeManager))
.closeButtonText(Res.get("shared.cancel")).show();
} else {
doSendDisputeOpenedMessage(dispute, disputeManager);
}
}
private void doSendDisputeOpenedMessage(Dispute dispute, DisputeManager<? extends DisputeList<Dispute>> disputeManager) {
disputeManager.sendDisputeOpenedMessage(dispute,
() -> navigation.navigateTo(MainView.class, SupportView.class, ArbitrationClientView.class),
(errorMessage, throwable) -> new Popup().warning(errorMessage).show());
}
public boolean isReadyForTxBroadcast() {

View File

@ -190,15 +190,13 @@ public abstract class TradeStepView extends AnchorPane {
}
trade.errorMessageProperty().addListener(errorMessageListener);
if (!isMediationClosedState()) {
tradeStepInfo.setOnAction(e -> {
if (this.isTradePeriodOver()) {
openSupportTicket();
} else {
openChat();
}
});
}
tradeStepInfo.setOnAction(e -> {
if (!isArbitrationOpenedState() && this.isTradePeriodOver()) {
openSupportTicket();
} else {
openChat();
}
});
// We get mailbox messages processed after we have bootstrapped. This will influence the states we
// handle in our disputeStateSubscription and mediationResultStateSubscriptions. To avoid that we show
@ -572,7 +570,7 @@ public abstract class TradeStepView extends AnchorPane {
}
private void updateMediationResultState(boolean blockOpeningOfResultAcceptedPopup) {
if (isInArbitration()) {
if (isInMediation()) {
if (isRefundRequestStartedByPeer()) {
tradeStepInfo.setState(TradeStepInfo.State.IN_REFUND_REQUEST_PEER_REQUESTED);
} else if (isRefundRequestSelfStarted()) {
@ -597,7 +595,7 @@ public abstract class TradeStepView extends AnchorPane {
}
}
private boolean isInArbitration() {
private boolean isInMediation() {
return isRefundRequestStartedByPeer() || isRefundRequestSelfStarted();
}
@ -613,6 +611,10 @@ public abstract class TradeStepView extends AnchorPane {
return trade.getDisputeState() == Trade.DisputeState.MEDIATION_CLOSED;
}
private boolean isArbitrationOpenedState() {
return trade.getDisputeState().isOpen();
}
private boolean isTradePeriodOver() {
return Trade.TradePeriodState.TRADE_PERIOD_OVER == trade.tradePeriodStateProperty().get();
}
@ -741,7 +743,7 @@ public abstract class TradeStepView extends AnchorPane {
}
private void updateTradePeriodState(Trade.TradePeriodState tradePeriodState) {
if (trade.getDisputeState() == Trade.DisputeState.NO_DISPUTE) {
if (!trade.getDisputeState().isOpen()) {
switch (tradePeriodState) {
case FIRST_HALF:
// just for dev testing. not possible to go back in time ;-)

View File

@ -1485,7 +1485,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> implements
@Override
public void onCloseDisputeFromChatWindow(Dispute dispute) {
if (dispute.getDisputeState() == Dispute.State.NEW || dispute.getDisputeState() == Dispute.State.OPEN) {
if (dispute.getDisputeState() == Dispute.State.NEW || dispute.getDisputeState().isOpen()) {
handleOnProcessDispute(dispute);
} else {
closeDisputeFromButton();