mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-09 07:02:24 -04:00
refactor arbitration protocol
add dispute states and open/close messages routed through arbitrator both traders publish dispute payout tx, winner is default verify signatures of payment sent and received messages seller sends deposit confirmed message to arbitrator buyer sends payment sent message to arbitrator arbitrator slows trade wallet sync rate after deposits confirmed various refactoring, fixes, and cleanup
This commit is contained in:
parent
363f783f30
commit
247087ef46
79 changed files with 1770 additions and 2480 deletions
|
@ -33,7 +33,7 @@ import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
|
|||
import bisq.core.trade.protocol.tasks.MakerSetLockTime;
|
||||
import bisq.core.trade.protocol.tasks.RemoveOffer;
|
||||
import bisq.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
|
||||
import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.ProcessPaymentSentMessage;
|
||||
import bisq.core.trade.protocol.tasks.SellerPublishDepositTx;
|
||||
import bisq.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
||||
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToBuyer;
|
||||
|
@ -100,7 +100,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
SellerPublishDepositTx.class,
|
||||
SellerPublishTradeStatistics.class,
|
||||
|
||||
SellerProcessPaymentSentMessage.class,
|
||||
ProcessPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
TakerVerifyMakerFeePayment.class,
|
||||
|
||||
|
@ -157,7 +157,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
|||
SellerPublishDepositTx.class,
|
||||
SellerPublishTradeStatistics.class,
|
||||
|
||||
SellerProcessPaymentSentMessage.class,
|
||||
ProcessPaymentSentMessage.class,
|
||||
ApplyFilter.class,
|
||||
|
||||
ApplyFilter.class,
|
||||
|
|
|
@ -92,10 +92,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
private final CoinFormatter formatter;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
private final MediationManager mediationManager;
|
||||
private final XmrWalletService walletService;
|
||||
private final TradeWalletService tradeWalletService; // TODO (woodser): remove for xmr or adapt to get/create multisig wallets for tx creation utils
|
||||
private final CoreDisputesService disputesService;
|
||||
private Dispute dispute;
|
||||
private final CoreDisputesService disputesService; private Dispute dispute;
|
||||
private ToggleGroup tradeAmountToggleGroup, reasonToggleGroup;
|
||||
private DisputeResult disputeResult;
|
||||
private RadioButton buyerGetsTradeAmountRadioButton, sellerGetsTradeAmountRadioButton,
|
||||
|
@ -115,7 +112,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
private ChangeListener<Toggle> reasonToggleSelectionListener;
|
||||
private InputTextField buyerPayoutAmountInputTextField, sellerPayoutAmountInputTextField;
|
||||
private ChangeListener<String> buyerPayoutAmountListener, sellerPayoutAmountListener;
|
||||
private CheckBox isLoserPublisherCheckBox;
|
||||
private ChangeListener<Toggle> tradeAmountToggleGroupListener;
|
||||
|
||||
|
||||
|
@ -134,8 +130,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
this.formatter = formatter;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
this.mediationManager = mediationManager;
|
||||
this.walletService = walletService;
|
||||
this.tradeWalletService = tradeWalletService;
|
||||
this.disputesService = disputesService;
|
||||
|
||||
type = Type.Confirmation;
|
||||
|
@ -220,7 +214,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
disputeResult.setBuyerPayoutAmount(peersDisputeResult.getBuyerPayoutAmount());
|
||||
disputeResult.setSellerPayoutAmount(peersDisputeResult.getSellerPayoutAmount());
|
||||
disputeResult.setWinner(peersDisputeResult.getWinner());
|
||||
disputeResult.setLoserPublisher(peersDisputeResult.isLoserPublisher());
|
||||
disputeResult.setReason(peersDisputeResult.getReason());
|
||||
disputeResult.setSummaryNotes(peersDisputeResult.summaryNotesProperty().get());
|
||||
|
||||
|
@ -248,13 +241,8 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
reasonWasPeerWasLateRadioButton.setDisable(true);
|
||||
reasonWasTradeAlreadySettledRadioButton.setDisable(true);
|
||||
|
||||
isLoserPublisherCheckBox.setDisable(true);
|
||||
isLoserPublisherCheckBox.setSelected(peersDisputeResult.isLoserPublisher());
|
||||
|
||||
applyPayoutAmounts(tradeAmountToggleGroup.selectedToggleProperty().get());
|
||||
applyTradeAmountRadioButtonStates();
|
||||
} else {
|
||||
isLoserPublisherCheckBox.setSelected(false);
|
||||
}
|
||||
|
||||
setReasonRadioButtonState();
|
||||
|
@ -426,11 +414,9 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
sellerPayoutAmountInputTextField.setPromptText(Res.get("disputeSummaryWindow.payoutAmount.seller"));
|
||||
sellerPayoutAmountInputTextField.setEditable(false);
|
||||
|
||||
isLoserPublisherCheckBox = new AutoTooltipCheckBox(Res.get("disputeSummaryWindow.payoutAmount.invert"));
|
||||
|
||||
VBox vBox = new VBox();
|
||||
vBox.setSpacing(15);
|
||||
vBox.getChildren().addAll(buyerPayoutAmountInputTextField, sellerPayoutAmountInputTextField, isLoserPublisherCheckBox);
|
||||
vBox.getChildren().addAll(buyerPayoutAmountInputTextField, sellerPayoutAmountInputTextField);
|
||||
GridPane.setMargin(vBox, new Insets(Layout.FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||
GridPane.setRowIndex(vBox, rowIndex);
|
||||
GridPane.setColumnIndex(vBox, 1);
|
||||
|
@ -590,7 +576,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
Button cancelButton = tuple.second;
|
||||
|
||||
closeTicketButton.setOnAction(e -> {
|
||||
disputesService.applyDisputePayout(dispute, disputeResult, contract);
|
||||
doClose(closeTicketButton);
|
||||
|
||||
// if (dispute.getDepositTxSerialized() == null) {
|
||||
|
@ -763,19 +748,14 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
|
||||
summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty());
|
||||
|
||||
boolean isRefundAgent = disputeManager instanceof RefundManager;
|
||||
disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected());
|
||||
disputeResult.setCloseDate(new Date());
|
||||
disputesService.closeDispute(disputeManager, dispute, disputeResult, isRefundAgent);
|
||||
disputesService.closeDisputeTicket(disputeManager, dispute, disputeResult, () -> {
|
||||
if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) {
|
||||
new Popup().attention(Res.get("disputeSummaryWindow.close.closePeer")).show();
|
||||
}
|
||||
disputeManager.requestPersistence();
|
||||
});
|
||||
|
||||
if (peersDisputeOptional.isPresent() && !peersDisputeOptional.get().isClosed() && !DevEnv.isDevMode()) {
|
||||
UserThread.runAfter(() -> new Popup()
|
||||
.attention(Res.get("disputeSummaryWindow.close.closePeer"))
|
||||
.show(),
|
||||
200, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
disputeManager.requestPersistence();
|
||||
closeTicketButton.disableProperty().unbind();
|
||||
hide();
|
||||
}
|
||||
|
|
|
@ -465,7 +465,6 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
byte[] payoutTxSerialized = null;
|
||||
String payoutTxHashAsString = null;
|
||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
||||
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
||||
if (trade.getPayoutTxId() != null) {
|
||||
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
|
||||
// payoutTxHashAsString = payoutTx.getHashAsString();
|
||||
|
@ -477,9 +476,9 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
// If mediation is not activated we use arbitration
|
||||
if (false) { // TODO (woodser): use mediation for xmr? if (MediationManager.isMediationActivated()) {
|
||||
// In case we re-open a dispute we allow Trade.DisputeState.MEDIATION_REQUESTED or
|
||||
useMediation = disputeState == Trade.DisputeState.NO_DISPUTE || disputeState == Trade.DisputeState.MEDIATION_REQUESTED;
|
||||
useMediation = disputeState == Trade.DisputeState.NO_DISPUTE || disputeState == Trade.DisputeState.MEDIATION_REQUESTED || disputeState == Trade.DisputeState.DISPUTE_OPENED;
|
||||
// in case of arbitration disputeState == Trade.DisputeState.ARBITRATION_REQUESTED
|
||||
useArbitration = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.DISPUTE_REQUESTED;
|
||||
useArbitration = disputeState == Trade.DisputeState.MEDIATION_CLOSED || disputeState == Trade.DisputeState.DISPUTE_REQUESTED || disputeState == Trade.DisputeState.DISPUTE_OPENED;
|
||||
} else {
|
||||
useMediation = false;
|
||||
useArbitration = true;
|
||||
|
@ -549,27 +548,27 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
|
||||
|
||||
trade.setDisputeState(Trade.DisputeState.MEDIATION_REQUESTED);
|
||||
sendOpenNewDisputeMessage(dispute, false, disputeManager, updatedMultisigHex);
|
||||
sendDisputeOpenedMessage(dispute, false, disputeManager, trade.getSelf().getUpdatedMultisigHex());
|
||||
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);
|
||||
sendOpenNewDisputeMessage(dispute, false, disputeManager, updatedMultisigHex);
|
||||
sendDisputeOpenedMessage(dispute, false, disputeManager, trade.getSelf().getUpdatedMultisigHex());
|
||||
tradeManager.requestPersistence();
|
||||
} else {
|
||||
log.warn("Invalid dispute state {}", disputeState.name());
|
||||
}
|
||||
}
|
||||
|
||||
private void sendOpenNewDisputeMessage(Dispute dispute, boolean reOpen, DisputeManager<? extends DisputeList<Dispute>> disputeManager, String senderMultisigHex) {
|
||||
disputeManager.sendOpenNewDisputeMessage(dispute, reOpen, senderMultisigHex,
|
||||
private void sendDisputeOpenedMessage(Dispute dispute, boolean reOpen, DisputeManager<? extends DisputeList<Dispute>> disputeManager, String senderMultisigHex) {
|
||||
disputeManager.sendDisputeOpenedMessage(dispute, reOpen, senderMultisigHex,
|
||||
() -> 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(() -> sendOpenNewDisputeMessage(dispute, true, disputeManager, senderMultisigHex))
|
||||
.onAction(() -> sendDisputeOpenedMessage(dispute, true, disputeManager, senderMultisigHex))
|
||||
.closeButtonText(Res.get("shared.cancel")).show();
|
||||
} else {
|
||||
new Popup().warning(errorMessage).show();
|
||||
|
|
|
@ -511,7 +511,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
|||
if (trade instanceof ArbitratorTrade) return;
|
||||
|
||||
switch (payoutState) {
|
||||
case PUBLISHED:
|
||||
case PAYOUT_PUBLISHED:
|
||||
sellerState.set(SellerState.STEP4);
|
||||
buyerState.set(BuyerState.STEP4);
|
||||
break;
|
||||
|
|
|
@ -31,6 +31,7 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.mediation.MediationResultState;
|
||||
import bisq.core.trade.ArbitratorTrade;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.MakerTrade;
|
||||
import bisq.core.trade.TakerTrade;
|
||||
|
@ -480,31 +481,25 @@ public abstract class TradeStepView extends AnchorPane {
|
|||
switch (disputeState) {
|
||||
case NO_DISPUTE:
|
||||
break;
|
||||
|
||||
case DISPUTE_REQUESTED:
|
||||
case DISPUTE_OPENED:
|
||||
if (tradeStepInfo != null) {
|
||||
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
|
||||
}
|
||||
applyOnDisputeOpened();
|
||||
|
||||
// update trade view unless arbitrator
|
||||
if (trade instanceof ArbitratorTrade) break;
|
||||
ownDispute = model.dataModel.arbitrationManager.findDispute(trade.getId());
|
||||
ownDispute.ifPresent(dispute -> {
|
||||
if (tradeStepInfo != null)
|
||||
tradeStepInfo.setState(TradeStepInfo.State.IN_ARBITRATION_SELF_REQUESTED);
|
||||
});
|
||||
|
||||
break;
|
||||
case DISPUTE_STARTED_BY_PEER:
|
||||
if (tradeStepInfo != null) {
|
||||
tradeStepInfo.setFirstHalfOverWarnTextSupplier(this::getFirstHalfOverWarnText);
|
||||
}
|
||||
applyOnDisputeOpened();
|
||||
|
||||
ownDispute = model.dataModel.arbitrationManager.findDispute(trade.getId());
|
||||
ownDispute.ifPresent(dispute -> {
|
||||
if (tradeStepInfo != null)
|
||||
tradeStepInfo.setState(TradeStepInfo.State.IN_ARBITRATION_PEER_REQUESTED);
|
||||
if (tradeStepInfo != null) {
|
||||
boolean isOpener = dispute.isDisputeOpenerIsBuyer() ? trade.isBuyer() : trade.isSeller();
|
||||
tradeStepInfo.setState(isOpener ? TradeStepInfo.State.IN_ARBITRATION_SELF_REQUESTED : TradeStepInfo.State.IN_ARBITRATION_PEER_REQUESTED);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case DISPUTE_CLOSED:
|
||||
break;
|
||||
case MEDIATION_REQUESTED:
|
||||
|
|
|
@ -190,7 +190,7 @@ public class BuyerStep2View extends TradeStepView {
|
|||
model.setMessageStateProperty(MessageState.FAILED);
|
||||
break;
|
||||
default:
|
||||
log.warn("Unexpected case: State={}, tradeId={} " + state.name(), trade.getId());
|
||||
log.warn("Unexpected case: State={}, tradeId={} ", state.name(), trade.getId());
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmationAgain"));
|
||||
break;
|
||||
|
@ -608,12 +608,6 @@ public class BuyerStep2View extends TradeStepView {
|
|||
busyAnimation.play();
|
||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||
|
||||
//TODO seems this was a hack to enable repeated confirm???
|
||||
if (trade.isPaymentSent()) {
|
||||
trade.setState(Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
model.dataModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
model.dataModel.onPaymentStarted(() -> {
|
||||
}, errorMessage -> {
|
||||
busyAnimation.stop();
|
||||
|
|
|
@ -145,6 +145,11 @@ public class SellerStep3View extends TradeStepView {
|
|||
busyAnimation.stop();
|
||||
statusLabel.setText("");
|
||||
break;
|
||||
case TRADE_COMPLETED:
|
||||
if (!trade.isPayoutPublished()) log.warn("Payout is expected to be published for {} {} state {}", trade.getClass().getSimpleName(), trade.getId(), trade.getState());
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText("");
|
||||
break;
|
||||
default:
|
||||
log.warn("Unexpected case: State={}, tradeId={} " + state.name(), trade.getId());
|
||||
busyAnimation.stop();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue