mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 23:18:17 +01:00
Merge pull request #4917 from chimp1984/handle-invalid-maker-fee-tx
Detect and handle invalid maker fee tx
This commit is contained in:
commit
9498fd9b7d
4 changed files with 131 additions and 3 deletions
|
@ -19,6 +19,7 @@ package bisq.core.offer;
|
|||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
import bisq.core.btc.wallet.BsqWalletService;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
|
@ -44,6 +45,9 @@ import bisq.common.util.MathUtils;
|
|||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
@ -383,4 +387,53 @@ public class OfferUtil {
|
|||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> getInvalidMakerFeeTxErrorMessage(Offer offer, BtcWalletService btcWalletService) {
|
||||
Transaction makerFeeTx = btcWalletService.getTransaction(offer.getOfferFeePaymentTxId());
|
||||
if (makerFeeTx == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String errorMsg = null;
|
||||
String header = "The offer with offer ID '" + offer.getShortId() +
|
||||
"' has an invalid maker fee transaction.\n\n";
|
||||
String spendingTransaction = null;
|
||||
String extraString = "\nYou have to remove that offer to avoid failed trades.\n" +
|
||||
"If this happened because of a bug please contact the Bisq developers " +
|
||||
"and you can request reimbursement for the lost maker fee.";
|
||||
if (makerFeeTx.getOutputs().size() > 1) {
|
||||
// Our output to fund the deposit tx is at index 1
|
||||
TransactionOutput output = makerFeeTx.getOutput(1);
|
||||
TransactionInput spentByTransactionInput = output.getSpentBy();
|
||||
if (spentByTransactionInput != null) {
|
||||
spendingTransaction = spentByTransactionInput.getConnectedTransaction() != null ?
|
||||
spentByTransactionInput.getConnectedTransaction().toString() :
|
||||
"null";
|
||||
// We this is an exceptional case we do not translate that error msg.
|
||||
errorMsg = "The output of the maker fee tx is already spent.\n" +
|
||||
extraString +
|
||||
"\n\nTransaction input which spent the reserved funds for that offer: '" +
|
||||
spentByTransactionInput.getConnectedTransaction().getTxId().toString() + ":" +
|
||||
(spentByTransactionInput.getConnectedOutput() != null ?
|
||||
spentByTransactionInput.getConnectedOutput().getIndex() + "'" :
|
||||
"null'");
|
||||
log.error("spentByTransactionInput {}", spentByTransactionInput);
|
||||
}
|
||||
} else {
|
||||
errorMsg = "The maker fee tx is invalid as it does not has at least 2 outputs." + extraString +
|
||||
"\nMakerFeeTx=" + makerFeeTx.toString();
|
||||
}
|
||||
|
||||
if (errorMsg == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
errorMsg = header + errorMsg;
|
||||
log.error(errorMsg);
|
||||
if (spendingTransaction != null) {
|
||||
log.error("Spending transaction: {}", spendingTransaction);
|
||||
}
|
||||
|
||||
return Optional.of(errorMsg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,11 +63,13 @@ import bisq.common.handlers.ResultHandler;
|
|||
import bisq.common.persistence.PersistenceManager;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -82,6 +84,8 @@ import java.util.stream.Collectors;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -118,6 +122,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
private final TradableList<OpenOffer> openOffers = new TradableList<>();
|
||||
private boolean stopped;
|
||||
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
|
||||
@Getter
|
||||
private final ObservableList<Tuple2<OpenOffer, String>> invalidOffers = FXCollections.observableArrayList();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -190,6 +196,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
|
||||
cleanUpAddressEntries();
|
||||
|
||||
openOffers.stream()
|
||||
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
|
||||
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
|
||||
}
|
||||
|
||||
private void cleanUpAddressEntries() {
|
||||
|
|
|
@ -48,6 +48,8 @@ import bisq.core.btc.wallet.BtcWalletService;
|
|||
import bisq.core.locale.CryptoCurrency;
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.offer.OpenOffer;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.AliPayAccount;
|
||||
import bisq.core.payment.CryptoCurrencyAccount;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
|
@ -70,6 +72,7 @@ import bisq.common.app.DevEnv;
|
|||
import bisq.common.app.Version;
|
||||
import bisq.common.config.Config;
|
||||
import bisq.common.file.CorruptedStorageFileHandler;
|
||||
import bisq.common.util.Tuple2;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
|
@ -86,6 +89,7 @@ import javafx.beans.property.SimpleDoubleProperty;
|
|||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -115,6 +119,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||
private final SettingsPresentation settingsPresentation;
|
||||
private final P2PService p2PService;
|
||||
private final TradeManager tradeManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
@Getter
|
||||
private final Preferences preferences;
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
|
@ -160,6 +165,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||
SettingsPresentation settingsPresentation,
|
||||
P2PService p2PService,
|
||||
TradeManager tradeManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
Preferences preferences,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
WalletPasswordWindow walletPasswordWindow,
|
||||
|
@ -184,6 +190,7 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||
this.settingsPresentation = settingsPresentation;
|
||||
this.p2PService = p2PService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
this.preferences = preferences;
|
||||
this.privateNotificationManager = privateNotificationManager;
|
||||
this.walletPasswordWindow = walletPasswordWindow;
|
||||
|
@ -432,6 +439,17 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||
this.footerVersionInfo.setValue("v" + Version.VERSION);
|
||||
}
|
||||
});
|
||||
|
||||
if (p2PService.isBootstrapped()) {
|
||||
setupInvalidOpenOffersHandler();
|
||||
} else {
|
||||
p2PService.addP2PServiceListener(new BootstrapListener() {
|
||||
@Override
|
||||
public void onUpdatedDataReceived() {
|
||||
setupInvalidOpenOffersHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void showRevolutAccountUpdateWindow(List<RevolutAccount> revolutAccountList) {
|
||||
|
@ -573,6 +591,34 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
|
|||
}
|
||||
}
|
||||
|
||||
private void setupInvalidOpenOffersHandler() {
|
||||
openOfferManager.getInvalidOffers().addListener((ListChangeListener<Tuple2<OpenOffer, String>>) c -> {
|
||||
c.next();
|
||||
if (c.wasAdded()) {
|
||||
handleInvalidOpenOffers(c.getAddedSubList());
|
||||
}
|
||||
});
|
||||
handleInvalidOpenOffers(openOfferManager.getInvalidOffers());
|
||||
}
|
||||
|
||||
private void handleInvalidOpenOffers(List<? extends Tuple2<OpenOffer, String>> list) {
|
||||
list.forEach(tuple2 -> {
|
||||
String errorMsg = tuple2.second;
|
||||
OpenOffer openOffer = tuple2.first;
|
||||
new Popup().warning(errorMsg)
|
||||
.width(1000)
|
||||
.actionButtonText(Res.get("shared.removeOffer"))
|
||||
.onAction(() -> {
|
||||
openOfferManager.removeOpenOffer(openOffer, () -> {
|
||||
log.info("Invalid open offer with ID {} was successfully removed.", openOffer.getId());
|
||||
}, log::error);
|
||||
|
||||
})
|
||||
.hideCloseButton()
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MainView delegate getters
|
||||
|
|
|
@ -20,17 +20,21 @@ package bisq.desktop.main.overlays.windows;
|
|||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.BusyAnimation;
|
||||
import bisq.desktop.components.TitledGroupBg;
|
||||
import bisq.desktop.components.TxIdTextField;
|
||||
import bisq.desktop.main.overlays.Overlay;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
import bisq.desktop.util.Layout;
|
||||
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.locale.BankUtil;
|
||||
import bisq.core.locale.CountryUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.offer.OfferUtil;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.payload.PaymentMethod;
|
||||
import bisq.core.user.User;
|
||||
|
@ -74,6 +78,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||
private final User user;
|
||||
private final KeyRing keyRing;
|
||||
private final Navigation navigation;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private Offer offer;
|
||||
private Coin tradeAmount;
|
||||
private Price tradePrice;
|
||||
|
@ -90,11 +95,13 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||
public OfferDetailsWindow(@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
User user,
|
||||
KeyRing keyRing,
|
||||
Navigation navigation) {
|
||||
Navigation navigation,
|
||||
BtcWalletService btcWalletService) {
|
||||
this.formatter = formatter;
|
||||
this.user = user;
|
||||
this.keyRing = keyRing;
|
||||
this.navigation = navigation;
|
||||
this.btcWalletService = btcWalletService;
|
||||
type = Type.Confirmation;
|
||||
}
|
||||
|
||||
|
@ -313,13 +320,13 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||
textArea.setEditable(false);
|
||||
}
|
||||
|
||||
rows = 3;
|
||||
rows = 4;
|
||||
if (countryCode != null)
|
||||
rows++;
|
||||
if (!isF2F)
|
||||
rows++;
|
||||
|
||||
addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE);
|
||||
TitledGroupBg titledGroupBg = addTitledGroupBg(gridPane, ++rowIndex, rows, Res.get("shared.details"), Layout.GROUP_DISTANCE);
|
||||
addConfirmationLabelTextFieldWithCopyIcon(gridPane, rowIndex, Res.get("shared.offerId"), offer.getId(),
|
||||
Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE);
|
||||
addConfirmationLabelTextFieldWithCopyIcon(gridPane, ++rowIndex, Res.get("offerDetailsWindow.makersOnion"),
|
||||
|
@ -335,6 +342,18 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
|
|||
formatter.formatCoinWithCode(offer.getSellerSecurityDeposit());
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), value);
|
||||
|
||||
TxIdTextField makerFeeTxIdTextField = addLabelTxIdTextField(gridPane, ++rowIndex,
|
||||
Res.get("shared.makerFeeTxId"), offer.getOfferFeePaymentTxId()).second;
|
||||
|
||||
int finalRows = rows;
|
||||
OfferUtil.getInvalidMakerFeeTxErrorMessage(offer, btcWalletService)
|
||||
.ifPresent(errorMsg -> {
|
||||
makerFeeTxIdTextField.getTextField().setId("address-text-field-error");
|
||||
GridPane.setRowSpan(titledGroupBg, finalRows + 1);
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.errorMessage"),
|
||||
errorMsg.replace("\n\n", "\n"));
|
||||
});
|
||||
|
||||
if (countryCode != null && !isF2F)
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("offerDetailsWindow.countryBank"),
|
||||
CountryUtil.getNameAndCode(countryCode));
|
||||
|
|
Loading…
Add table
Reference in a new issue