arbitrator sends original unsigned payout tx if published

synchronize on trade when processing dispute messages
This commit is contained in:
woodser 2023-02-25 08:13:44 -05:00
parent 17ac09fa4d
commit 6dca11f471
5 changed files with 135 additions and 123 deletions

View File

@ -36,7 +36,6 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTxWallet;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format; import static java.lang.String.format;
@ -171,25 +170,25 @@ public class CoreDisputesService {
applyPayoutAmountsToDisputeResult(payout, winningDispute, disputeResult, customWinnerAmount); applyPayoutAmountsToDisputeResult(payout, winningDispute, disputeResult, customWinnerAmount);
// create dispute payout tx // create dispute payout tx
MoneroTxWallet disputePayoutTx = arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), disputeResult, false); trade.getProcessModel().setUnsignedPayoutTx(arbitrationManager.createDisputePayoutTx(trade, winningDispute.getContract(), disputeResult, false));
// close winning dispute ticket // close winning dispute ticket
closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, disputePayoutTx, () -> { closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, () -> {
arbitrationManager.requestPersistence(); arbitrationManager.requestPersistence();
}, (errMessage, err) -> { }, (errMessage, err) -> {
throw new IllegalStateException(errMessage, err); throw new IllegalStateException(errMessage, err);
}); });
// close loser's dispute ticket // close loser's dispute ticket
var peersDisputeOptional = arbitrationManager.getDisputesAsObservableList().stream() var loserDisputeOptional = arbitrationManager.getDisputesAsObservableList().stream()
.filter(d -> tradeId.equals(d.getTradeId()) && winningDispute.getTraderId() != d.getTraderId()) .filter(d -> tradeId.equals(d.getTradeId()) && winningDispute.getTraderId() != d.getTraderId())
.findFirst(); .findFirst();
if (!peersDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute"); if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
var peerDispute = peersDisputeOptional.get(); var loserDispute = loserDisputeOptional.get();
var peerDisputeResult = createDisputeResult(peerDispute, winner, reason, summaryNotes, closeDate); var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
peerDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount()); loserDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
peerDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount()); loserDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
closeDisputeTicket(arbitrationManager, peerDispute, peerDisputeResult, disputePayoutTx, () -> { closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> {
arbitrationManager.requestPersistence(); arbitrationManager.requestPersistence();
}, (errMessage, err) -> { }, (errMessage, err) -> {
throw new IllegalStateException(errMessage, err); throw new IllegalStateException(errMessage, err);
@ -248,7 +247,7 @@ public class CoreDisputesService {
} }
} }
public void closeDisputeTicket(DisputeManager disputeManager, Dispute dispute, DisputeResult disputeResult, MoneroTxWallet payoutTx, ResultHandler resultHandler, FaultHandler faultHandler) { public void closeDisputeTicket(DisputeManager disputeManager, Dispute dispute, DisputeResult disputeResult, ResultHandler resultHandler, FaultHandler faultHandler) {
DisputeResult.Reason reason = disputeResult.getReason(); DisputeResult.Reason reason = disputeResult.getReason();
String role = Res.get("shared.arbitrator"); String role = Res.get("shared.arbitrator");
@ -279,7 +278,7 @@ public class CoreDisputesService {
String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign); String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign);
summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration"); summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration");
disputeManager.closeDisputeTicket(disputeResult, dispute, summaryText, payoutTx, () -> { disputeManager.closeDisputeTicket(disputeResult, dispute, summaryText, () -> {
dispute.setDisputeResult(disputeResult); dispute.setDisputeResult(disputeResult);
dispute.setIsClosed(); dispute.setIsClosed();
resultHandler.handleResult(); resultHandler.handleResult();

View File

@ -443,7 +443,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
Dispute dispute = message.getDispute(); Dispute dispute = message.getDispute();
log.info("{}.onDisputeOpenedMessage() with trade {}, dispute {}", getClass().getSimpleName(), dispute.getTradeId(), dispute.getId()); log.info("{}.onDisputeOpenedMessage() with trade {}, dispute {}", getClass().getSimpleName(), dispute.getTradeId(), dispute.getId());
Trade trade = null; // get trade
Trade trade = tradeManager.getTrade(dispute.getTradeId());
if (trade == null) {
log.warn("Dispute trade {} does not exist", dispute.getTradeId());
return;
}
synchronized (trade) {
String errorMessage = null; String errorMessage = null;
PubKeyRing senderPubKeyRing = null; PubKeyRing senderPubKeyRing = null;
try { try {
@ -470,13 +477,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
throw e; throw e;
} }
// get trade
trade = tradeManager.getTrade(dispute.getTradeId());
if (trade == null) {
log.warn("Dispute trade {} does not exist", dispute.getTradeId());
return;
}
// get sender // get sender
senderPubKeyRing = trade.isArbitrator() ? (dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing()) : trade.getArbitrator().getPubKeyRing(); senderPubKeyRing = trade.isArbitrator() ? (dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing()) : trade.getArbitrator().getPubKeyRing();
TradePeer sender = trade.getTradePeer(senderPubKeyRing); TradePeer sender = trade.getTradePeer(senderPubKeyRing);
@ -543,6 +543,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
requestPersistence(); requestPersistence();
} }
}
// arbitrator sends dispute opened message to opener's peer // arbitrator sends dispute opened message to opener's peer
private void sendDisputeOpenedMessageToPeer(Dispute disputeFromOpener, private void sendDisputeOpenedMessageToPeer(Dispute disputeFromOpener,
@ -700,16 +701,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
} }
// arbitrator sends result to trader when their dispute is closed // arbitrator sends result to trader when their dispute is closed
public void closeDisputeTicket(DisputeResult disputeResult, Dispute dispute, String summaryText, MoneroTxWallet payoutTx, ResultHandler resultHandler, FaultHandler faultHandler) { public void closeDisputeTicket(DisputeResult disputeResult, Dispute dispute, String summaryText, ResultHandler resultHandler, FaultHandler faultHandler) {
try { try {
// get trade // get trade
Trade trade = tradeManager.getTrade(dispute.getTradeId()); Trade trade = tradeManager.getTrade(dispute.getTradeId());
if (trade == null) throw new RuntimeException("Dispute trade " + dispute.getTradeId() + " does not exist"); if (trade == null) throw new RuntimeException("Dispute trade " + dispute.getTradeId() + " does not exist");
// create dispute payout tx if not given
if (payoutTx == null) payoutTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false); // can be null if already published or we don't have receiver's multisig hex
// persist result in dispute's chat message once // persist result in dispute's chat message once
boolean resending = disputeResult.getChatMessage() != null; boolean resending = disputeResult.getChatMessage() != null;
if (!resending) { if (!resending) {
@ -724,9 +722,15 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
dispute.addAndPersistChatMessage(chatMessage); dispute.addAndPersistChatMessage(chatMessage);
} }
// create dispute closed message // create dispute payout tx if not published
TradePeer receiver = trade.getTradePeer(dispute.getTraderPubKeyRing()); TradePeer receiver = trade.getTradePeer(dispute.getTraderPubKeyRing());
String unsignedPayoutTxHex = payoutTx == null ? null : payoutTx.getTxSet().getMultisigTxHex(); if (!trade.isPayoutPublished() && receiver.getUpdatedMultisigHex() != null) {
trade.getProcessModel().setUnsignedPayoutTx(createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false)); // can be null if we don't have receiver's multisig hex
}
// create dispute closed message
MoneroTxWallet unsignedPayoutTx = receiver.getUpdatedMultisigHex() == null ? null : trade.getProcessModel().getUnsignedPayoutTx();
String unsignedPayoutTxHex = unsignedPayoutTx == null ? null : unsignedPayoutTx.getTxSet().getMultisigTxHex();
TradePeer receiverPeer = receiver == trade.getBuyer() ? trade.getSeller() : trade.getBuyer(); TradePeer receiverPeer = receiver == trade.getBuyer() ? trade.getSeller() : trade.getBuyer();
boolean deferPublishPayout = !resending && unsignedPayoutTxHex != null && receiverPeer.getUpdatedMultisigHex() != null && trade.getDisputeState().ordinal() >= Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG.ordinal() ; boolean deferPublishPayout = !resending && unsignedPayoutTxHex != null && receiverPeer.getUpdatedMultisigHex() != null && trade.getDisputeState().ordinal() >= Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG.ordinal() ;
DisputeClosedMessage disputeClosedMessage = new DisputeClosedMessage(disputeResult, DisputeClosedMessage disputeClosedMessage = new DisputeClosedMessage(disputeResult,
@ -799,9 +803,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
); );
// save state // save state
if (payoutTx != null) { if (unsignedPayoutTx != null) {
trade.setPayoutTx(payoutTx); trade.setPayoutTx(unsignedPayoutTx);
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex()); trade.setPayoutTxHex(unsignedPayoutTx.getTxSet().getMultisigTxHex());
} }
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG); trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG);
requestPersistence(); requestPersistence();

View File

@ -169,6 +169,9 @@ public class ProcessModel implements Model, PersistablePayload {
@Getter @Getter
@Setter @Setter
transient private MoneroTxWallet depositTxXmr; transient private MoneroTxWallet depositTxXmr;
@Getter
@Setter
transient private MoneroTxWallet unsignedPayoutTx;
@Nullable @Nullable
@Getter @Getter
@Setter @Setter

View File

@ -58,7 +58,8 @@ public class ResendDisputeClosedMessageWithPayout extends TradeTask {
for (Dispute dispute : disputes) { for (Dispute dispute : disputes) {
if (!dispute.isClosed()) continue; // dispute must be closed if (!dispute.isClosed()) continue; // dispute must be closed
if (sender.getPubKeyRing().equals(dispute.getTraderPubKeyRing())) { if (sender.getPubKeyRing().equals(dispute.getTraderPubKeyRing())) {
HavenoUtils.arbitrationManager.closeDisputeTicket(dispute.getDisputeResultProperty().get(), dispute, dispute.getDisputeResultProperty().get().summaryNotesProperty().get(), null, () -> { log.info("Arbitrator resending DisputeClosedMessage for trade {} after receiving updated multisig hex", trade.getId());
HavenoUtils.arbitrationManager.closeDisputeTicket(dispute.getDisputeResultProperty().get(), dispute, dispute.getDisputeResultProperty().get().summaryNotesProperty().get(), () -> {
completeAux(); completeAux();
}, (errMessage, err) -> { }, (errMessage, err) -> {
err.printStackTrace(); err.printStackTrace();

View File

@ -590,8 +590,13 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
closeTicketButton.setOnAction(e -> { closeTicketButton.setOnAction(e -> {
// get or create payout tx // get or create dispute payout tx
MoneroTxWallet payoutTx = trade.isPayoutPublished() ? trade.getPayoutTx() : arbitrationManager.createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false); MoneroTxWallet payoutTx = null;
if (trade.isPayoutPublished()) payoutTx = trade.getPayoutTx();
else {
payoutTx = arbitrationManager.createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
}
// show confirmation // show confirmation
if (dispute.getSupportType() == SupportType.ARBITRATION && if (dispute.getSupportType() == SupportType.ARBITRATION &&
@ -600,9 +605,9 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
showPayoutTxConfirmation(contract, showPayoutTxConfirmation(contract,
disputeResult, disputeResult,
payoutTx, payoutTx,
() -> doClose(closeTicketButton, payoutTx)); () -> doClose(closeTicketButton));
} else { } else {
doClose(closeTicketButton, payoutTx); doClose(closeTicketButton);
} }
}); });
@ -654,7 +659,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
} }
} }
private void doClose(Button closeTicketButton, MoneroTxWallet payoutTx) { private void doClose(Button closeTicketButton) {
DisputeManager<? extends DisputeList<Dispute>> disputeManager = getDisputeManager(dispute); DisputeManager<? extends DisputeList<Dispute>> disputeManager = getDisputeManager(dispute);
if (disputeManager == null) { if (disputeManager == null) {
return; return;
@ -663,7 +668,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty()); summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty());
disputeResult.setCloseDate(new Date()); disputeResult.setCloseDate(new Date());
disputesService.closeDisputeTicket(disputeManager, dispute, disputeResult, payoutTx, () -> { disputesService.closeDisputeTicket(disputeManager, dispute, disputeResult, () -> {
if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) { if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) {
new Popup().attention(Res.get("disputeSummaryWindow.close.closePeer")).show(); new Popup().attention(Res.get("disputeSummaryWindow.close.closePeer")).show();
} }