diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 5536a41f89..c1642e2c1c 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -575,19 +575,22 @@ public class WalletService { return outputs; } + /////////////////////////////////////////////////////////////////////////////////////////// // Revert unconfirmed transaction (unlock in case we got into a tx with a too low mining fee) /////////////////////////////////////////////////////////////////////////////////////////// public void doubleSpendTransaction(String txId, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) throws InsufficientMoneyException { + AddressEntry addressEntry = getOrCreateUnusedAddressEntry(AddressEntry.Context.AVAILABLE); + checkNotNull(addressEntry.getAddress(), "addressEntry.getAddress() must not be null"); Optional transactionOptional = wallet.getTransactions(true).stream() .filter(t -> t.getHashAsString().equals(txId)) .findAny(); if (transactionOptional.isPresent()) - doubleSpendTransaction(transactionOptional.get(), resultHandler, errorMessageHandler); + doubleSpendTransaction(transactionOptional.get(), addressEntry.getAddress(), resultHandler, errorMessageHandler); } - public void doubleSpendTransaction(Transaction txToDoubleSpend, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) throws InsufficientMoneyException { + public void doubleSpendTransaction(Transaction txToDoubleSpend, Address newOutputAddress, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) throws InsufficientMoneyException { final TransactionConfidence.ConfidenceType confidenceType = txToDoubleSpend.getConfidence().getConfidenceType(); if (confidenceType == TransactionConfidence.ConfidenceType.PENDING) { Transaction newTransaction = new Transaction(params); @@ -595,31 +598,26 @@ public class WalletService { if (input.getConnectedOutput() != null && input.getConnectedOutput().isMine(wallet) && input.getConnectedOutput().getParentTransaction() != null && input.getValue() != null) { newTransaction.addInput(new TransactionInput(params, - input.getParentTransaction(), + newTransaction, new byte[]{}, new TransactionOutPoint(params, input.getOutpoint().getIndex(), new Transaction(params, input.getConnectedOutput().getParentTransaction().bitcoinSerialize())), Coin.valueOf(input.getValue().value))); } else { - log.error("input had null values: " + input.toString()); + log.error("Input had null values: " + input.toString()); } } ); if (!newTransaction.getInputs().isEmpty() && txToDoubleSpend.getFee() != null) { - AddressEntry addressEntry = getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE); // We use a higher fee to be sure we get that tx confirmed final Coin newFee = txToDoubleSpend.getFee().add(FeePolicy.getFixedTxFeeForTrades()); - checkNotNull(addressEntry.getAddress(), "addressEntry.getAddress() must not be null"); - newTransaction.addOutput(txToDoubleSpend.getValueSentFromMe(wallet).subtract(newFee), addressEntry.getAddress()); - - // We set the old tx to dead state, as we double spent its inputs - wallet.killTx(txToDoubleSpend); + newTransaction.addOutput(txToDoubleSpend.getValueSentFromMe(wallet).subtract(newFee), newOutputAddress); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(newTransaction); sendRequest.aesKey = aesKey; - sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.getAddress()); + sendRequest.coinSelector = new TradeWalletCoinSelector(params, newOutputAddress); // We don't expect change but set it just in case - sendRequest.changeAddress = addressEntry.getAddress(); + sendRequest.changeAddress = newOutputAddress; sendRequest.feePerKb = newFee; Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); Futures.addCallback(sendResult.broadcastComplete, new FutureCallback() { @@ -635,12 +633,12 @@ public class WalletService { } }); } else { - errorMessageHandler.handleErrorMessage("We could not generate inputs for the new transaction."); + errorMessageHandler.handleErrorMessage("We could not find inputs we control in the transaction we want to double spend."); } } else if (confidenceType == TransactionConfidence.ConfidenceType.BUILDING) { - errorMessageHandler.handleErrorMessage("That transaction is already in the blockchain so we cannot revert it."); + errorMessageHandler.handleErrorMessage("That transaction is already in the blockchain so we cannot double spend it."); } else if (confidenceType == TransactionConfidence.ConfidenceType.DEAD) { - errorMessageHandler.handleErrorMessage("One of the inputs of that transaction has been double spended."); + errorMessageHandler.handleErrorMessage("One of the inputs of that transaction has been already double spent."); } } diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index dd960e4750..347e190303 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -369,10 +369,11 @@ public class TradeManager { public void removeTrade(Trade trade) { trades.remove(trade); - walletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.OFFER_FUNDING); - walletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); - walletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); - walletService.swapTradeEntryToAvailableEntry(trade.getId(), AddressEntry.Context.TRADE_PAYOUT); + final String id = trade.getId(); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.MULTI_SIG); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.TRADE_PAYOUT); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java index e6138445c3..240df6d16b 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java @@ -19,6 +19,7 @@ package io.bitsquare.gui.main.funds.transactions; import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.arbitration.DisputeManager; +import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.WalletService; import io.bitsquare.common.util.Tuple2; @@ -57,6 +58,7 @@ import javafx.util.Callback; import org.bitcoinj.core.*; import org.bitcoinj.script.Script; +import javax.annotation.Nullable; import javax.inject.Inject; import java.text.DateFormat; import java.util.*; @@ -499,7 +501,7 @@ public class TransactionsView extends ActivatableView { if (walletService.getConfidenceForTxId(item.getTxId()).getConfidenceType() == TransactionConfidence.ConfidenceType.PENDING) { if (button == null) { button = new Button("Revert"); - button.setOnAction(e -> revertTransaction(item.getTxId())); + button.setOnAction(e -> revertTransaction(item.getTxId(), item.getTradable())); setGraphic(button); } } else { @@ -521,9 +523,17 @@ public class TransactionsView extends ActivatableView { }); } - private void revertTransaction(String txId) { + private void revertTransaction(String txId, @Nullable Tradable tradable) { try { walletService.doubleSpendTransaction(txId, () -> { + if (tradable != null) { + final String id = tradable.getId(); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.MULTI_SIG); + walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.TRADE_PAYOUT); + } + new Popup().information("Transaction successfully sent to a new address in the local Bitsquare wallet.").show(); }, errorMessage -> { new Popup().warning(errorMessage).show();