Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add cancel trade feature #4514

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
59860f6
Refactor: Rename method
chimp1984 Sep 10, 2020
3ef7599
Refactor: Move initialize before activate, add separator lines
chimp1984 Sep 10, 2020
2bfdcfb
Refactor: Move methods
chimp1984 Sep 10, 2020
1159836
Refactor: Make initialize public
chimp1984 Sep 10, 2020
59e4e98
Functional change: Do not call initialize in constructor
chimp1984 Sep 10, 2020
17ba6e9
Refactor: Do check for trade.getPayoutTx() == null at beginning and r…
chimp1984 Sep 10, 2020
1e2ea6d
Refactor: Move check for trade.getPayoutTx() == null to sub classes
chimp1984 Sep 10, 2020
88cd5b2
Refactor: Rename class for more generic use cases.
chimp1984 Sep 10, 2020
5f1d5f3
Functional change: Avoid possibility to add duplicated tradable to list
chimp1984 Sep 10, 2020
1f2241f
Add cancel trade domain
chimp1984 Sep 10, 2020
5dd81cb
Refactor: Rename id to tradeId
chimp1984 Sep 10, 2020
d50d825
Remove unused field
chimp1984 Sep 10, 2020
25db775
Refactor: Rename CanceledTradeState to HandleCancelTradeRequestState
chimp1984 Sep 10, 2020
68add41
Add RequestCancelTradeState
chimp1984 Sep 10, 2020
b28f507
Separate states completed
chimp1984 Sep 10, 2020
8236ce2
Remove cancel support at step 3. Once buyer has send payment we do
chimp1984 Sep 10, 2020
6f5e386
Rename RequestCancelTradePresentation and HandleCancelTradeRequestPre…
chimp1984 Sep 10, 2020
79a9ac9
Refactor: Move classes
chimp1984 Sep 10, 2020
7313458
Refactor: Rename enums
chimp1984 Sep 10, 2020
e483efd
Refactor: Move enums to seller and buyer trade
chimp1984 Sep 10, 2020
3e85ff6
Refactor: Rename enums
chimp1984 Sep 10, 2020
a134c74
Split buyer seller protocol classes.
chimp1984 Sep 10, 2020
fa00593
Refactor: Move method, improve comments
chimp1984 Sep 10, 2020
b4d9807
Refactor: Move message classes to new package
chimp1984 Sep 10, 2020
9ee586f
Refactor: Move dispute message classes to new dispute package
chimp1984 Sep 10, 2020
482c73a
Refactor: Rename package
chimp1984 Sep 10, 2020
54e4a08
Only log error at DelayedPayoutTx check if deposit tx is not null
chimp1984 Sep 10, 2020
dce1873
Merge branch 'master_upstream' into add-cancel-trade-feature
chimp1984 Sep 10, 2020
eae6a46
Improve text, dont use action style for button
chimp1984 Sep 10, 2020
4985435
Refactor: Rename classes
chimp1984 Sep 10, 2020
aa6721a
Getting the feature polished... Many small changes...
chimp1984 Sep 10, 2020
5148c77
Mark refreshInterval transient. Move static MAX_REFRESH_INTERVAL to top
chimp1984 Sep 10, 2020
9e917d5
Use more convenient TimeUnit.HOURS.toMillis API
chimp1984 Sep 10, 2020
5c62454
Add comments about wrong usage of trade protocol
chimp1984 Sep 10, 2020
8885e85
Move cancelTrade states to sub classes
chimp1984 Sep 10, 2020
f71b2fb
Update comments, remove todo
chimp1984 Sep 10, 2020
9047ab9
Remove mailbox msg at failure cases in trade protocol tasks
chimp1984 Sep 10, 2020
82fd068
Remove commented out code.
chimp1984 Sep 11, 2020
0f7f3d2
Try to get Codacy to ignore lines
chimp1984 Sep 11, 2020
0e9540d
Remove constructor as Codacy does not like empty constructors.
chimp1984 Sep 11, 2020
6b5e08d
Moved witness signing and msg sending to trade protocol task
chimp1984 Sep 11, 2020
e3c6aa2
Refactoring: Rename param to avoid confusion with allowBroadcast and
chimp1984 Sep 11, 2020
286402f
Add comments
chimp1984 Sep 11, 2020
f819a0d
Let signer broadcast signedWitness as well.
chimp1984 Sep 11, 2020
396c6f4
Use a deterministic id instead of uid
chimp1984 Sep 11, 2020
93f24a4
Refactor: Use SendMailboxMessageTask to avoid duplicated code
chimp1984 Sep 11, 2020
ab2e54f
Resend payment started message as long we dont get an ACK
chimp1984 Sep 11, 2020
873d2cf
Start BuyerSendCounterCurrencyTransferStartedMessage at startup again
chimp1984 Sep 11, 2020
00bb8db
Add comment
chimp1984 Sep 11, 2020
2b05638
Remove dev test value
chimp1984 Sep 11, 2020
bf3ce34
Refactor: Rename signAccountAgeWitness to signAndPublishAccountAgeWit…
chimp1984 Sep 11, 2020
5e46366
Remove duplicate broadcast of witness data
chimp1984 Sep 11, 2020
c2296d4
Send witness with PayoutTxPublishedMessage
chimp1984 Sep 11, 2020
37496c2
Remove refresh button
chimp1984 Sep 11, 2020
d53f5ed
Refactor: Rename display strings
chimp1984 Sep 11, 2020
1742da3
Refactor: Move display strings
chimp1984 Sep 11, 2020
8f7fe51
Merge branch 'master_upstream' into add-cancel-trade-feature
chimp1984 Sep 11, 2020
71e452f
Apply suggestions from code review. Add todos for required changes
chimp1984 Sep 17, 2020
7318b10
Merge branch 'master_upstream' into add-cancel-trade-feature
chimp1984 Sep 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion core/src/main/java/bisq/core/btc/wallet/TradeWalletService.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import org.slf4j.Logger;
Expand Down Expand Up @@ -833,6 +832,67 @@ public Transaction sellerSignsAndFinalizesPayoutTx(Transaction depositTx,
return payoutTx;
}

///////////////////////////////////////////////////////////////////////////////////////////
// Canceled trade payoutTx
///////////////////////////////////////////////////////////////////////////////////////////

public byte[] signCanceledTradePayoutTx(Transaction depositTx,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
String buyerPayoutAddressString,
String sellerPayoutAddressString,
DeterministicKey myMultiSigKeyPair,
byte[] buyerPubKey,
byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException {
Transaction preparedPayoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
chimp1984 marked this conversation as resolved.
Show resolved Hide resolved
// MS redeemScript
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
Sha256Hash sigHash = preparedPayoutTx.hashForSignature(0, redeemScript, Transaction.SigHash.ALL, false);
checkNotNull(myMultiSigKeyPair, "myMultiSigKeyPair must not be null");
if (myMultiSigKeyPair.isEncrypted()) {
checkNotNull(aesKey);
}
ECKey.ECDSASignature mySignature = myMultiSigKeyPair.sign(sigHash, aesKey).toCanonicalised();
WalletService.printTx("prepared canceled trade payoutTx for sig creation", preparedPayoutTx);
WalletService.verifyTransaction(preparedPayoutTx);
return mySignature.encodeToDER();
}

public Transaction finalizeCanceledTradePayoutTx(Transaction depositTx,
chimp1984 marked this conversation as resolved.
Show resolved Hide resolved
byte[] buyerSignature,
byte[] sellerSignature,
Coin buyerPayoutAmount,
Coin sellerPayoutAmount,
String buyerPayoutAddressString,
String sellerPayoutAddressString,
DeterministicKey multiSigKeyPair,
byte[] buyerPubKey,
byte[] sellerPubKey)
throws AddressFormatException, TransactionVerificationException, WalletException {
Transaction payoutTx = createPayoutTx(depositTx, buyerPayoutAmount, sellerPayoutAmount, buyerPayoutAddressString, sellerPayoutAddressString);
// MS redeemScript
Script redeemScript = get2of2MultiSigRedeemScript(buyerPubKey, sellerPubKey);
// MS output from prev. tx is index 0
checkNotNull(multiSigKeyPair, "multiSigKeyPair must not be null");
TransactionSignature buyerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(buyerSignature),
Transaction.SigHash.ALL, false);
TransactionSignature sellerTxSig = new TransactionSignature(ECKey.ECDSASignature.decodeFromDER(sellerSignature),
Transaction.SigHash.ALL, false);
// Take care of order of signatures. Need to be reversed here. See comment below at getMultiSigRedeemScript (seller, buyer)
Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript(ImmutableList.of(sellerTxSig, buyerTxSig), redeemScript);
TransactionInput input = payoutTx.getInput(0);
input.setScriptSig(inputScript);
WalletService.printTx("canceled trade payoutTx", payoutTx);
WalletService.verifyTransaction(payoutTx);
WalletService.checkWalletConsistency(wallet);
WalletService.checkScriptSig(payoutTx, input, 0);
checkNotNull(input.getConnectedOutput(), "input.getConnectedOutput() must not be null");
input.verify(input.getConnectedOutput());
return payoutTx;
}


///////////////////////////////////////////////////////////////////////////////////////////
// Mediated payoutTx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
import bisq.core.support.messages.ChatMessage;
import bisq.core.trade.messages.CancelTradeRequestAcceptedMessage;
import bisq.core.trade.messages.CancelTradeRequestRejectedMessage;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
Expand All @@ -58,6 +60,7 @@
import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.RequestCancelTradeMessage;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.statistics.TradeStatistics;

Expand Down Expand Up @@ -174,6 +177,13 @@ public NetworkEnvelope fromProto(protobuf.NetworkEnvelope proto) throws Protobuf
case MEDIATED_PAYOUT_TX_PUBLISHED_MESSAGE:
return MediatedPayoutTxPublishedMessage.fromProto(proto.getMediatedPayoutTxPublishedMessage(), messageVersion);

case REQUEST_CANCEL_TRADE_MESSAGE:
return RequestCancelTradeMessage.fromProto(proto.getRequestCancelTradeMessage(), messageVersion);
case CANCEL_TRADE_ACCEPTED_MESSAGE:
return CancelTradeRequestAcceptedMessage.fromProto(proto.getCancelTradeAcceptedMessage(), messageVersion);
case CANCEL_TRADE_REJECTED_MESSAGE:
return CancelTradeRequestRejectedMessage.fromProto(proto.getCancelTradeRejectedMessage(), messageVersion);

case OPEN_NEW_DISPUTE_MESSAGE:
return OpenNewDisputeMessage.fromProto(proto.getOpenNewDisputeMessage(), this, messageVersion);
case PEER_OPENED_DISPUTE_MESSAGE:
Expand Down
50 changes: 50 additions & 0 deletions core/src/main/java/bisq/core/trade/CanceledTradeState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.core.trade;

public enum CanceledTradeState {
REQUEST_MSG_SENT,
REQUEST_MSG_ARRIVED,
REQUEST_MSG_IN_MAILBOX,
REQUEST_MSG_SEND_FAILED,

// Peer received request
RECEIVED_CANCEL_REQUEST,

// Requester received reject msg
RECEIVED_ACCEPTED_MSG,

// Peer accepted
PAYOUT_TX_PUBLISHED,
PAYOUT_TX_PUBLISHED_MSG_SENT,
PAYOUT_TX_PUBLISHED_MSG_ARRIVED,
PAYOUT_TX_PUBLISHED_MSG_IN_MAILBOX,
PAYOUT_TX_PUBLISHED_MSG_SEND_FAILED,

// Request sees tx
PAYOUT_TX_SEEN_IN_NETWORK,

// Peer rejected
REQUEST_CANCELED_MSG_SENT,
REQUEST_CANCELED_MSG_ARRIVED,
REQUEST_CANCELED_MSG_IN_MAILBOX,
REQUEST_CANCELED_MSG_SEND_FAILED,

// Requester received reject msg
RECEIVED_REJECTED_MSG,
}
15 changes: 15 additions & 0 deletions core/src/main/java/bisq/core/trade/Trade.java
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ public static protobuf.Trade.TradePeriodState toProtoMessage(Trade.TradePeriodSt
@Getter
transient final private IntegerProperty assetTxProofResultUpdateProperty = new SimpleIntegerProperty();

@Getter
// Added at v1.3.9
public CanceledTradeState canceledTradeState;
@Getter
transient final private ObjectProperty<CanceledTradeState> canceledTradeStateProperty = new SimpleObjectProperty<>();


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization
Expand Down Expand Up @@ -563,6 +569,7 @@ public Message toProtoMessage() {
Optional.ofNullable(delayedPayoutTxBytes).ifPresent(e -> builder.setDelayedPayoutTxBytes(ByteString.copyFrom(delayedPayoutTxBytes)));
Optional.ofNullable(counterCurrencyExtraData).ifPresent(e -> builder.setCounterCurrencyExtraData(counterCurrencyExtraData));
Optional.ofNullable(assetTxProofResult).ifPresent(e -> builder.setAssetTxProofResult(assetTxProofResult.name()));
Optional.ofNullable(canceledTradeState).ifPresent(e -> builder.setCanceledTradeState(canceledTradeState.name()));

return builder.build();
}
Expand Down Expand Up @@ -605,6 +612,8 @@ public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolv
}
trade.setAssetTxProofResult(persistedAssetTxProofResult);

trade.setCanceledTradeState(ProtoUtil.enumFromProto(CanceledTradeState.class, proto.getCanceledTradeState()));

trade.chatMessages.addAll(proto.getChatMessageList().stream()
.map(ChatMessage::fromPayloadProto)
.collect(Collectors.toList()));
Expand Down Expand Up @@ -893,6 +902,11 @@ public void setAssetTxProofResult(@Nullable AssetTxProofResult assetTxProofResul
persist();
}

public void setCanceledTradeState(CanceledTradeState canceledTradeState) {
this.canceledTradeState = canceledTradeState;
canceledTradeStateProperty.set(canceledTradeState);
persist();
}

///////////////////////////////////////////////////////////////////////////////////////////
// Getter
Expand Down Expand Up @@ -1230,6 +1244,7 @@ public String toString() {
",\n refundResultState=" + refundResultState +
",\n refundResultStateProperty=" + refundResultStateProperty +
",\n lastRefreshRequestDate=" + lastRefreshRequestDate +
",\n canceledTradeState=" + canceledTradeState +
"\n}";
}
}
117 changes: 117 additions & 0 deletions core/src/main/java/bisq/core/trade/TradeCancellationManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.core.trade;

import bisq.core.btc.wallet.Restrictions;
import bisq.core.offer.Offer;
import bisq.core.trade.protocol.ProcessModel;

import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;

import org.bitcoinj.core.Coin;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import lombok.extern.slf4j.Slf4j;

import static com.google.common.base.Preconditions.checkNotNull;

@Slf4j
@Singleton
public final class TradeCancellationManager {

///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////

@Inject
public TradeCancellationManager() {
}


///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////

public void requestCancelTrade(Trade trade,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
ProcessModel processModel = trade.getProcessModel();
Offer offer = checkNotNull(trade.getOffer());
Coin secDepositOfRequester = getSecurityDepositForRequester();
Coin totalSecDeposit = offer.getSellerSecurityDeposit().add(offer.getBuyerSecurityDeposit());
Coin secDepositForForPeer = totalSecDeposit.subtract(secDepositOfRequester);
Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null");
if (trade instanceof BuyerTrade) {
processModel.setBuyerPayoutAmountFromCanceledTrade(secDepositOfRequester.value);
processModel.setSellerPayoutAmountFromCanceledTrade(tradeAmount.add(secDepositForForPeer).value);
} else {
processModel.setBuyerPayoutAmountFromCanceledTrade(secDepositForForPeer.value);
processModel.setSellerPayoutAmountFromCanceledTrade(tradeAmount.add(secDepositOfRequester).value);
}
trade.getTradeProtocol().onRequestCancelTrade(resultHandler, errorMessageHandler);
}

public Coin getSecurityDepositForRequester() {
return Restrictions.getMinRefundAtMediatedDispute();
}

private Coin getTotalSecDepositForAcceptingTrader(Offer offer) {
Coin totalSecDeposit = offer.getSellerSecurityDeposit().add(offer.getBuyerSecurityDeposit());
return totalSecDeposit.subtract(getSecurityDepositForRequester());
}

public Coin getDefaultSecDepositOfAcceptingTrader(Trade trade) {
Offer offer = checkNotNull(trade.getOffer());
return trade instanceof BuyerTrade ?
offer.getBuyerSecurityDeposit() :
offer.getSellerSecurityDeposit();
}

public Coin getLostSecDepositOfRequestingTrader(Trade trade) {
Offer offer = checkNotNull(trade.getOffer());
return getTotalSecDepositForAcceptingTrader(offer).subtract(getDefaultSecDepositOfAcceptingTrader(trade));
}

public void acceptCancelTradeRequest(Trade trade,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
ProcessModel processModel = trade.getProcessModel();
Offer offer = checkNotNull(trade.getOffer());
Coin secDepositOfRequester = getSecurityDepositForRequester();
Coin totalSecDeposit = offer.getSellerSecurityDeposit().add(offer.getBuyerSecurityDeposit());
Coin secDepositOfAcceptingTrader = totalSecDeposit.subtract(secDepositOfRequester);
Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null");
if (trade instanceof BuyerTrade) {
processModel.setBuyerPayoutAmountFromCanceledTrade(secDepositOfAcceptingTrader.value);
processModel.setSellerPayoutAmountFromCanceledTrade(tradeAmount.add(secDepositOfRequester).value);
} else {
processModel.setBuyerPayoutAmountFromCanceledTrade(secDepositOfRequester.value);
processModel.setSellerPayoutAmountFromCanceledTrade(tradeAmount.add(secDepositOfAcceptingTrader).value);
}
trade.getTradeProtocol().onAcceptCancelTradeRequest(resultHandler, errorMessageHandler);
}

public void rejectCancelTradeRequest(Trade trade,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
trade.getTradeProtocol().onRejectCancelTradeRequest(resultHandler, errorMessageHandler);
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/bisq/core/trade/TradeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,16 @@ private void removeTrade(Trade trade) {
}


///////////////////////////////////////////////////////////////////////////////////////////
// Canceled trade
///////////////////////////////////////////////////////////////////////////////////////////

public void closeCanceledTrade(Trade trade) {
addTradeToClosedTrades(trade);
btcWalletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Dispute
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down
Loading