mirror of
https://github.com/boldsuck/haveno.git
synced 2024-12-22 12:09:22 +00:00
support re-opening dispute if payout fails
This commit is contained in:
parent
79cd9f3e82
commit
3b0080dbba
9 changed files with 173 additions and 85 deletions
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 ;-)
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue