Skip to content

Commit

Permalink
Merge pull request #4917 from chimp1984/handle-invalid-maker-fee-tx
Browse files Browse the repository at this point in the history
Detect and handle invalid maker fee tx
  • Loading branch information
ripcurlx authored Dec 10, 2020
2 parents aa0e091 + 960b835 commit 9498fd9
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 3 deletions.
53 changes: 53 additions & 0 deletions core/src/main/java/bisq/core/offer/OfferUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

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;
Expand All @@ -44,6 +45,9 @@
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;
Expand Down Expand Up @@ -383,4 +387,53 @@ private Optional<Volume> getFeeInUserFiatCurrency(Coin makerFee,
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);
}
}
10 changes: 10 additions & 0 deletions core/src/main/java/bisq/core/offer/OpenOfferManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@
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;
Expand All @@ -82,6 +84,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import lombok.Getter;

import org.jetbrains.annotations.NotNull;

import javax.annotation.Nullable;
Expand Down Expand Up @@ -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();


///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -190,6 +196,10 @@ public void onUpdatedDataReceived() {
}

cleanUpAddressEntries();

openOffers.stream()
.forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
.ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
}

private void cleanUpAddressEntries() {
Expand Down
46 changes: 46 additions & 0 deletions desktop/src/main/java/bisq/desktop/main/MainViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
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;
Expand All @@ -70,6 +72,7 @@
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;

Expand All @@ -86,6 +89,7 @@
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

import java.util.ArrayList;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -160,6 +165,7 @@ public MainViewModel(BisqSetup bisqSetup,
SettingsPresentation settingsPresentation,
P2PService p2PService,
TradeManager tradeManager,
OpenOfferManager openOfferManager,
Preferences preferences,
PrivateNotificationManager privateNotificationManager,
WalletPasswordWindow walletPasswordWindow,
Expand All @@ -184,6 +190,7 @@ public MainViewModel(BisqSetup bisqSetup,
this.settingsPresentation = settingsPresentation;
this.p2PService = p2PService;
this.tradeManager = tradeManager;
this.openOfferManager = openOfferManager;
this.preferences = preferences;
this.privateNotificationManager = privateNotificationManager;
this.walletPasswordWindow = walletPasswordWindow;
Expand Down Expand Up @@ -432,6 +439,17 @@ private void setupHandlers() {
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) {
Expand Down Expand Up @@ -573,6 +591,34 @@ private void updateBtcSyncProgress() {
}
}

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@
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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -313,13 +320,13 @@ else if (BankUtil.isBankNameRequired(countryCode))
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"),
Expand All @@ -335,6 +342,18 @@ else if (BankUtil.isBankNameRequired(countryCode))
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));
Expand Down

0 comments on commit 9498fd9

Please sign in to comment.