mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Reattach addresses when unfailing trade
This commit is contained in:
parent
bd8e30c708
commit
817819dc51
@ -598,6 +598,13 @@ public class BtcWalletService extends WalletService {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AddressEntry recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
||||||
|
var available = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
|
||||||
|
if (!available.isPresent())
|
||||||
|
return null;
|
||||||
|
return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
|
||||||
|
}
|
||||||
|
|
||||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
||||||
if (addressEntry.isPresent()) {
|
if (addressEntry.isPresent()) {
|
||||||
return addressEntry.get();
|
return addressEntry.get();
|
||||||
|
@ -64,6 +64,8 @@ import bisq.common.handlers.ResultHandler;
|
|||||||
import bisq.common.proto.network.NetworkEnvelope;
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
import bisq.common.storage.Storage;
|
import bisq.common.storage.Storage;
|
||||||
|
import bisq.common.util.Tuple2;
|
||||||
|
import bisq.common.util.Utilities;
|
||||||
|
|
||||||
import org.bitcoinj.core.AddressFormatException;
|
import org.bitcoinj.core.AddressFormatException;
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
@ -89,6 +91,7 @@ import org.spongycastle.crypto.params.KeyParameter;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -604,12 +607,35 @@ public class TradeManager implements PersistedDataHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If trade still has funds locked up it might come back from failed trades
|
// If trade still has funds locked up it might come back from failed trades
|
||||||
private void unfailTrade(Trade trade) {
|
// Aborts unfailing if the address entries needed are not available
|
||||||
|
private boolean unfailTrade(Trade trade) {
|
||||||
|
if (!recoverAddresses(trade)) {
|
||||||
|
log.warn("Failed to recover address during unfail trade");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tradableList.contains(trade)) {
|
if (!tradableList.contains(trade)) {
|
||||||
tradableList.add(trade);
|
tradableList.add(trade);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The trade is added to pending trades if the associated address entries are AVAILABLE and
|
||||||
|
// the relevant entries are changed, otherwise it's not added and no address entries are changed
|
||||||
|
private boolean recoverAddresses(Trade trade) {
|
||||||
|
// Find addresses associated with this trade.
|
||||||
|
var entries = TradeUtils.getAvailableAddresses(trade, btcWalletService, keyRing);
|
||||||
|
if (entries == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
btcWalletService.recoverAddressEntry(trade.getId(), entries.first,
|
||||||
|
AddressEntry.Context.MULTI_SIG);
|
||||||
|
btcWalletService.recoverAddressEntry(trade.getId(), entries.second,
|
||||||
|
AddressEntry.Context.TRADE_PAYOUT);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// If trade is in preparation (if taker role: before taker fee is paid; both roles: before deposit published)
|
// If trade is in preparation (if taker role: before taker fee is paid; both roles: before deposit published)
|
||||||
// we just remove the trade from our list. We don't store those trades.
|
// we just remove the trade from our list. We don't store those trades.
|
||||||
public void removePreparedTrade(Trade trade) {
|
public void removePreparedTrade(Trade trade) {
|
||||||
|
79
core/src/main/java/bisq/core/trade/TradeUtils.java
Normal file
79
core/src/main/java/bisq/core/trade/TradeUtils.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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.BtcWalletService;
|
||||||
|
|
||||||
|
import bisq.common.crypto.KeyRing;
|
||||||
|
import bisq.common.util.Tuple2;
|
||||||
|
import bisq.common.util.Utilities;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class TradeUtils {
|
||||||
|
|
||||||
|
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
||||||
|
static Tuple2<String, String> getAvailableAddresses(Trade trade, BtcWalletService btcWalletService,
|
||||||
|
KeyRing keyRing) {
|
||||||
|
var addresses = getTradeAddresses(trade, btcWalletService, keyRing);
|
||||||
|
if (addresses == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (btcWalletService.getAvailableAddressEntries().stream()
|
||||||
|
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.first)))
|
||||||
|
return null;
|
||||||
|
if (btcWalletService.getAvailableAddressEntries().stream()
|
||||||
|
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.second)))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Tuple2<>(addresses.first, addresses.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns <MULTI_SIG, TRADE_PAYOUT> addresses as strings if they're known by the wallet
|
||||||
|
public static Tuple2<String, String> getTradeAddresses(Trade trade, BtcWalletService btcWalletService,
|
||||||
|
KeyRing keyRing) {
|
||||||
|
var contract = trade.getContract();
|
||||||
|
if (contract == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get multisig address
|
||||||
|
var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
|
||||||
|
var multiSigPubKey = isMyRoleBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
|
||||||
|
if (multiSigPubKey == null)
|
||||||
|
return null;
|
||||||
|
var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
|
||||||
|
var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream()
|
||||||
|
.filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
if (multiSigAddress == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Get payout address
|
||||||
|
var payoutAddress = isMyRoleBuyer ?
|
||||||
|
contract.getBuyerPayoutAddressString() : contract.getSellerPayoutAddressString();
|
||||||
|
var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream()
|
||||||
|
.filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
|
||||||
|
.findAny()
|
||||||
|
.orElse(null);
|
||||||
|
if (payoutAddressEntry == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
|
||||||
|
}
|
||||||
|
}
|
@ -17,12 +17,14 @@
|
|||||||
|
|
||||||
package bisq.core.trade.failed;
|
package bisq.core.trade.failed;
|
||||||
|
|
||||||
|
import bisq.core.btc.model.AddressEntry;
|
||||||
import bisq.core.btc.wallet.BtcWalletService;
|
import bisq.core.btc.wallet.BtcWalletService;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.trade.DumpDelayedPayoutTx;
|
import bisq.core.trade.DumpDelayedPayoutTx;
|
||||||
import bisq.core.trade.TradableList;
|
import bisq.core.trade.TradableList;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.core.trade.TradeUtils;
|
||||||
|
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.proto.persistable.PersistedDataHost;
|
import bisq.common.proto.persistable.PersistedDataHost;
|
||||||
@ -33,7 +35,7 @@ import com.google.inject.Inject;
|
|||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -50,7 +52,7 @@ public class FailedTradesManager implements PersistedDataHost {
|
|||||||
private final Storage<TradableList<Trade>> tradableListStorage;
|
private final Storage<TradableList<Trade>> tradableListStorage;
|
||||||
private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
|
private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
|
||||||
@Setter
|
@Setter
|
||||||
private Consumer<Trade> unfailTradeCallback;
|
private Function<Trade, Boolean> unfailTradeCallback;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FailedTradesManager(KeyRing keyRing,
|
public FailedTradesManager(KeyRing keyRing,
|
||||||
@ -103,8 +105,29 @@ public class FailedTradesManager implements PersistedDataHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void unfailTrade(Trade trade) {
|
public void unfailTrade(Trade trade) {
|
||||||
if (unfailTradeCallback == null) return;
|
if (unfailTradeCallback == null)
|
||||||
unfailTradeCallback.accept(trade);
|
return;
|
||||||
failedTrades.remove(trade);
|
|
||||||
|
if (unfailTradeCallback.apply(trade)) {
|
||||||
|
failedTrades.remove(trade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String checkUnfail(Trade trade) {
|
||||||
|
var addresses = TradeUtils.getTradeAddresses(trade, btcWalletService, keyRing);
|
||||||
|
if (addresses == null) {
|
||||||
|
return "Addresses not found";
|
||||||
|
}
|
||||||
|
StringBuilder blockingTrades = new StringBuilder();
|
||||||
|
for (var entry : btcWalletService.getAddressEntryListAsImmutableList()) {
|
||||||
|
if (entry.getContext() == AddressEntry.Context.AVAILABLE)
|
||||||
|
continue;
|
||||||
|
if (entry.getAddressString() != null &&
|
||||||
|
(entry.getAddressString().equals(addresses.first) ||
|
||||||
|
entry.getAddressString().equals(addresses.second))) {
|
||||||
|
blockingTrades.append(entry.getOfferId()).append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return blockingTrades.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -859,8 +859,11 @@ portfolio.closed.ticketClosed=Arbitrated
|
|||||||
portfolio.closed.mediationTicketClosed=Mediated
|
portfolio.closed.mediationTicketClosed=Mediated
|
||||||
portfolio.closed.canceled=Canceled
|
portfolio.closed.canceled=Canceled
|
||||||
portfolio.failed.Failed=Failed
|
portfolio.failed.Failed=Failed
|
||||||
portfolio.failed.unfail=Do you want to move this trade back to pending trades? \
|
portfolio.failed.unfail=Before proceeding, make sure you have a backup of your data directory!\n\
|
||||||
Only do this if you need to open a support ticket.
|
Do you want to move this trade back to open trades?\n\
|
||||||
|
This is a way to unlock funds stuck in a failed trade.
|
||||||
|
portfolio.failed.cantUnfail=This trade cannot be moved back to open trades at the moment. \n\
|
||||||
|
Try again after completion of trade(s) {0}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
|
@ -77,4 +77,8 @@ class FailedTradesDataModel extends ActivatableDataModel {
|
|||||||
public void unfailTrade(Trade trade) {
|
public void unfailTrade(Trade trade) {
|
||||||
failedTradesManager.unfailTrade(trade);
|
failedTradesManager.unfailTrade(trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String checkUnfail(Trade trade) {
|
||||||
|
return failedTradesManager.checkUnfail(trade);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,9 +108,16 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
|
|||||||
|
|
||||||
keyEventEventHandler = keyEvent -> {
|
keyEventEventHandler = keyEvent -> {
|
||||||
if (Utilities.isAltOrCtrlPressed(KeyCode.Y, keyEvent)) {
|
if (Utilities.isAltOrCtrlPressed(KeyCode.Y, keyEvent)) {
|
||||||
new Popup().warning(Res.get("portfolio.failed.unfail"))
|
var checkUnfailString = checkUnfail();
|
||||||
.onAction(this::onUnfail)
|
if (!checkUnfailString.isEmpty()) {
|
||||||
.show();
|
new Popup().warning(Res.get("portfolio.failed.cantUnfail", checkUnfailString))
|
||||||
|
.onAction(this::onUnfail)
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
new Popup().warning(Res.get("portfolio.failed.unfail"))
|
||||||
|
.onAction(this::onUnfail)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -119,6 +126,11 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
|
|||||||
private void onUnfail() {
|
private void onUnfail() {
|
||||||
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
|
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
|
||||||
model.dataModel.unfailTrade(trade);
|
model.dataModel.unfailTrade(trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String checkUnfail() {
|
||||||
|
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
|
||||||
|
return model.dataModel.checkUnfail(trade);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user