mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-12-22 11:39:29 +00:00
arbitrator sends original unsigned payout tx if published
synchronize on trade when processing dispute messages
This commit is contained in:
parent
17ac09fa4d
commit
6dca11f471
5 changed files with 135 additions and 123 deletions
|
@ -36,7 +36,6 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.lang.String.format;
|
||||
|
@ -171,25 +170,25 @@ public class CoreDisputesService {
|
|||
applyPayoutAmountsToDisputeResult(payout, winningDispute, disputeResult, customWinnerAmount);
|
||||
|
||||
// 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
|
||||
closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, disputePayoutTx, () -> {
|
||||
closeDisputeTicket(arbitrationManager, winningDispute, disputeResult, () -> {
|
||||
arbitrationManager.requestPersistence();
|
||||
}, (errMessage, err) -> {
|
||||
throw new IllegalStateException(errMessage, err);
|
||||
});
|
||||
|
||||
// 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())
|
||||
.findFirst();
|
||||
if (!peersDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
|
||||
var peerDispute = peersDisputeOptional.get();
|
||||
var peerDisputeResult = createDisputeResult(peerDispute, winner, reason, summaryNotes, closeDate);
|
||||
peerDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
|
||||
peerDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
|
||||
closeDisputeTicket(arbitrationManager, peerDispute, peerDisputeResult, disputePayoutTx, () -> {
|
||||
if (!loserDisputeOptional.isPresent()) throw new IllegalStateException("could not find peer dispute");
|
||||
var loserDispute = loserDisputeOptional.get();
|
||||
var loserDisputeResult = createDisputeResult(loserDispute, winner, reason, summaryNotes, closeDate);
|
||||
loserDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
|
||||
loserDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
|
||||
closeDisputeTicket(arbitrationManager, loserDispute, loserDisputeResult, () -> {
|
||||
arbitrationManager.requestPersistence();
|
||||
}, (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();
|
||||
|
||||
String role = Res.get("shared.arbitrator");
|
||||
|
@ -279,7 +278,7 @@ public class CoreDisputesService {
|
|||
String summaryText = DisputeSummaryVerification.signAndApply(disputeManager, disputeResult, textToSign);
|
||||
summaryText += Res.get("disputeSummaryWindow.close.nextStepsForRefundAgentArbitration");
|
||||
|
||||
disputeManager.closeDisputeTicket(disputeResult, dispute, summaryText, payoutTx, () -> {
|
||||
disputeManager.closeDisputeTicket(disputeResult, dispute, summaryText, () -> {
|
||||
dispute.setDisputeResult(disputeResult);
|
||||
dispute.setIsClosed();
|
||||
resultHandler.handleResult();
|
||||
|
|
|
@ -443,105 +443,106 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
Dispute dispute = message.getDispute();
|
||||
log.info("{}.onDisputeOpenedMessage() with trade {}, dispute {}", getClass().getSimpleName(), dispute.getTradeId(), dispute.getId());
|
||||
|
||||
Trade trade = null;
|
||||
String errorMessage = null;
|
||||
PubKeyRing senderPubKeyRing = null;
|
||||
try {
|
||||
|
||||
// initialize
|
||||
T disputeList = getDisputeList();
|
||||
if (disputeList == null) {
|
||||
log.warn("disputes is null");
|
||||
return;
|
||||
}
|
||||
dispute.setSupportType(message.getSupportType());
|
||||
dispute.setState(Dispute.State.NEW);
|
||||
Contract contract = dispute.getContract();
|
||||
|
||||
// validate dispute
|
||||
// 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;
|
||||
PubKeyRing senderPubKeyRing = null;
|
||||
try {
|
||||
DisputeValidation.validateDisputeData(dispute);
|
||||
DisputeValidation.validateNodeAddresses(dispute, config);
|
||||
DisputeValidation.validateSenderNodeAddress(dispute, message.getSenderNodeAddress());
|
||||
DisputeValidation.validatePaymentAccountPayload(dispute);
|
||||
//DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
|
||||
} catch (DisputeValidation.ValidationException e) {
|
||||
validationExceptions.add(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
|
||||
senderPubKeyRing = trade.isArbitrator() ? (dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing()) : trade.getArbitrator().getPubKeyRing();
|
||||
TradePeer sender = trade.getTradePeer(senderPubKeyRing);
|
||||
if (sender == null) throw new RuntimeException("Pub key ring is not from arbitrator, buyer, or seller");
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
// arbitrator verifies signature of payment sent message if given
|
||||
if (trade.isArbitrator() && message.getPaymentSentMessage() != null) {
|
||||
HavenoUtils.verifyPaymentSentMessage(trade, message.getPaymentSentMessage());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getPaymentSentMessage().getUpdatedMultisigHex());
|
||||
trade.advanceState(sender == trade.getBuyer() ? Trade.State.BUYER_SENT_PAYMENT_SENT_MSG : Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
}
|
||||
|
||||
// update multisig hex
|
||||
if (message.getUpdatedMultisigHex() != null) sender.setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
|
||||
// update peer node address
|
||||
// TODO: tests can reuse the same addresses so nullify equal peer
|
||||
sender.setNodeAddress(message.getSenderNodeAddress());
|
||||
|
||||
// add chat message with price info
|
||||
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
|
||||
|
||||
// add dispute
|
||||
synchronized (disputeList) {
|
||||
if (!disputeList.contains(dispute)) {
|
||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||
if (!storedDisputeOptional.isPresent()) {
|
||||
disputeList.add(dispute);
|
||||
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
|
||||
|
||||
// send dispute opened message to peer if arbitrator
|
||||
if (trade.isArbitrator()) sendDisputeOpenedMessageToPeer(dispute, contract, dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(), trade.getSelf().getUpdatedMultisigHex());
|
||||
tradeManager.requestPersistence();
|
||||
errorMessage = null;
|
||||
} else {
|
||||
// valid case if both have opened a dispute and agent was not online
|
||||
log.debug("We got a dispute already open for that trade and trading peer. TradeId = {}",
|
||||
dispute.getTradeId());
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
// initialize
|
||||
T disputeList = getDisputeList();
|
||||
if (disputeList == null) {
|
||||
log.warn("disputes is null");
|
||||
return;
|
||||
}
|
||||
dispute.setSupportType(message.getSupportType());
|
||||
dispute.setState(Dispute.State.NEW);
|
||||
Contract contract = dispute.getContract();
|
||||
|
||||
// validate dispute
|
||||
try {
|
||||
DisputeValidation.validateDisputeData(dispute);
|
||||
DisputeValidation.validateNodeAddresses(dispute, config);
|
||||
DisputeValidation.validateSenderNodeAddress(dispute, message.getSenderNodeAddress());
|
||||
DisputeValidation.validatePaymentAccountPayload(dispute);
|
||||
//DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
|
||||
} catch (DisputeValidation.ValidationException e) {
|
||||
validationExceptions.add(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// get sender
|
||||
senderPubKeyRing = trade.isArbitrator() ? (dispute.isDisputeOpenerIsBuyer() ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing()) : trade.getArbitrator().getPubKeyRing();
|
||||
TradePeer sender = trade.getTradePeer(senderPubKeyRing);
|
||||
if (sender == null) throw new RuntimeException("Pub key ring is not from arbitrator, buyer, or seller");
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
// arbitrator verifies signature of payment sent message if given
|
||||
if (trade.isArbitrator() && message.getPaymentSentMessage() != null) {
|
||||
HavenoUtils.verifyPaymentSentMessage(trade, message.getPaymentSentMessage());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getPaymentSentMessage().getUpdatedMultisigHex());
|
||||
trade.advanceState(sender == trade.getBuyer() ? Trade.State.BUYER_SENT_PAYMENT_SENT_MSG : Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
}
|
||||
|
||||
// update multisig hex
|
||||
if (message.getUpdatedMultisigHex() != null) sender.setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
|
||||
// update peer node address
|
||||
// TODO: tests can reuse the same addresses so nullify equal peer
|
||||
sender.setNodeAddress(message.getSenderNodeAddress());
|
||||
|
||||
// add chat message with price info
|
||||
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
|
||||
|
||||
// add dispute
|
||||
synchronized (disputeList) {
|
||||
if (!disputeList.contains(dispute)) {
|
||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||
if (!storedDisputeOptional.isPresent()) {
|
||||
disputeList.add(dispute);
|
||||
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
|
||||
|
||||
// send dispute opened message to peer if arbitrator
|
||||
if (trade.isArbitrator()) sendDisputeOpenedMessageToPeer(dispute, contract, dispute.isDisputeOpenerIsBuyer() ? contract.getSellerPubKeyRing() : contract.getBuyerPubKeyRing(), trade.getSelf().getUpdatedMultisigHex());
|
||||
tradeManager.requestPersistence();
|
||||
errorMessage = null;
|
||||
} else {
|
||||
// valid case if both have opened a dispute and agent was not online
|
||||
log.debug("We got a dispute already open for that trade and trading peer. TradeId = {}",
|
||||
dispute.getTradeId());
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errorMessage = e.getMessage();
|
||||
log.warn(errorMessage);
|
||||
if (trade != null) trade.setErrorMessage(errorMessage);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
errorMessage = e.getMessage();
|
||||
log.warn(errorMessage);
|
||||
if (trade != null) trade.setErrorMessage(errorMessage);
|
||||
|
||||
// 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);
|
||||
sendAckMessage(msg, senderPubKeyRing, errorMessage == null, errorMessage);
|
||||
}
|
||||
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
// 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);
|
||||
sendAckMessage(msg, senderPubKeyRing, errorMessage == null, errorMessage);
|
||||
}
|
||||
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
// arbitrator sends dispute opened message to opener's peer
|
||||
|
@ -700,16 +701,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
// get trade
|
||||
Trade trade = tradeManager.getTrade(dispute.getTradeId());
|
||||
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
|
||||
boolean resending = disputeResult.getChatMessage() != null;
|
||||
if (!resending) {
|
||||
|
@ -724,9 +722,15 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
}
|
||||
|
||||
// create dispute closed message
|
||||
// create dispute payout tx if not published
|
||||
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();
|
||||
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,
|
||||
|
@ -799,9 +803,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
);
|
||||
|
||||
// save state
|
||||
if (payoutTx != null) {
|
||||
trade.setPayoutTx(payoutTx);
|
||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
if (unsignedPayoutTx != null) {
|
||||
trade.setPayoutTx(unsignedPayoutTx);
|
||||
trade.setPayoutTxHex(unsignedPayoutTx.getTxSet().getMultisigTxHex());
|
||||
}
|
||||
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SENT_DISPUTE_CLOSED_MSG);
|
||||
requestPersistence();
|
||||
|
|
|
@ -169,6 +169,9 @@ public class ProcessModel implements Model, PersistablePayload {
|
|||
@Getter
|
||||
@Setter
|
||||
transient private MoneroTxWallet depositTxXmr;
|
||||
@Getter
|
||||
@Setter
|
||||
transient private MoneroTxWallet unsignedPayoutTx;
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
|
|
|
@ -58,7 +58,8 @@ public class ResendDisputeClosedMessageWithPayout extends TradeTask {
|
|||
for (Dispute dispute : disputes) {
|
||||
if (!dispute.isClosed()) continue; // dispute must be closed
|
||||
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();
|
||||
}, (errMessage, err) -> {
|
||||
err.printStackTrace();
|
||||
|
|
|
@ -590,8 +590,13 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
|
||||
closeTicketButton.setOnAction(e -> {
|
||||
|
||||
// get or create payout tx
|
||||
MoneroTxWallet payoutTx = trade.isPayoutPublished() ? trade.getPayoutTx() : arbitrationManager.createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
|
||||
// get or create dispute payout tx
|
||||
MoneroTxWallet payoutTx = null;
|
||||
if (trade.isPayoutPublished()) payoutTx = trade.getPayoutTx();
|
||||
else {
|
||||
payoutTx = arbitrationManager.createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
|
||||
trade.getProcessModel().setUnsignedPayoutTx(payoutTx);
|
||||
}
|
||||
|
||||
// show confirmation
|
||||
if (dispute.getSupportType() == SupportType.ARBITRATION &&
|
||||
|
@ -600,9 +605,9 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
showPayoutTxConfirmation(contract,
|
||||
disputeResult,
|
||||
payoutTx,
|
||||
() -> doClose(closeTicketButton, payoutTx));
|
||||
() -> doClose(closeTicketButton));
|
||||
} 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);
|
||||
if (disputeManager == null) {
|
||||
return;
|
||||
|
@ -663,7 +668,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty());
|
||||
|
||||
disputeResult.setCloseDate(new Date());
|
||||
disputesService.closeDisputeTicket(disputeManager, dispute, disputeResult, payoutTx, () -> {
|
||||
disputesService.closeDisputeTicket(disputeManager, dispute, disputeResult, () -> {
|
||||
if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) {
|
||||
new Popup().attention(Res.get("disputeSummaryWindow.close.closePeer")).show();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue