Add callback for tradeFee tx to be seen in the network to avoid errors at take offer that the trade address is not funded.

This commit is contained in:
Manfred Karrer 2017-06-30 20:17:27 +02:00
parent 7bce5c4943
commit bb0f0ec273
3 changed files with 81 additions and 40 deletions

View file

@ -154,7 +154,8 @@ public class TradeWalletService {
boolean useSavingsWallet,
Coin tradingFee,
Coin txFee,
String feeReceiverAddresses)
String feeReceiverAddresses,
FutureCallback<Transaction> callback)
throws InsufficientMoneyException, AddressFormatException {
log.debug("fundingAddress " + fundingAddress.toString());
log.debug("reservedForTradeAddress " + reservedForTradeAddress.toString());
@ -193,6 +194,10 @@ public class TradeWalletService {
wallet.completeTx(sendRequest);
WalletService.printTx("tradingFeeTx", tradingFeeTx);
checkNotNull(walletConfig, "walletConfig must not be null");
ListenableFuture<Transaction> broadcastComplete = walletConfig.peerGroup().broadcastTransaction(tradingFeeTx).future();
Futures.addCallback(broadcastComplete, callback);
return tradingFeeTx;
}

View file

@ -18,6 +18,7 @@
package io.bisq.core.offer.placeoffer.tasks;
import com.google.common.util.concurrent.FutureCallback;
import io.bisq.common.UserThread;
import io.bisq.common.taskrunner.Task;
import io.bisq.common.taskrunner.TaskRunner;
import io.bisq.core.arbitration.Arbitrator;
@ -43,6 +44,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class);
private Transaction tradeFeeTx = null;
@SuppressWarnings({"WeakerAccess", "unused"})
public CreateMakerFeeTx(TaskRunner taskHandler, PlaceOfferModel model) {
@ -70,8 +72,11 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
Address changeAddress = walletService.getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
final TradeWalletService tradeWalletService = model.getTradeWalletService();
// We dont use a timeout here as we need to get the tradeFee tx callback called to be sure the addressEntry is funded
if (offer.isCurrencyForMakerFeeBtc()) {
Transaction btcTransaction = tradeWalletService.createBtcTradingFeeTx(
tradeFeeTx = tradeWalletService.createBtcTradingFeeTx(
fundingAddress,
reservedForTradeAddress,
changeAddress,
@ -79,17 +84,36 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
model.isUseSavingsWallet(),
offer.getMakerFee(),
offer.getTxFee(),
selectedArbitrator.getBtcAddress());
selectedArbitrator.getBtcAddress(),
new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
// we delay one render frame to be sure we don't get called before the method call has
// returned (tradeFeeTx would be null in that case)
UserThread.execute(() -> {
if (!completed) {
if (tradeFeeTx != null && !tradeFeeTx.getHashAsString().equals(transaction.getHashAsString()))
log.warn("The trade fee tx received from the network had another tx ID than the one we publish");
// We assume there will be no tx malleability. We add a check later in case the published offer has a different hash.
// As the txId is part of the offer and therefore change the hash data we need to be sure to have no
// tx malleability
offer.setOfferFeePaymentTxId(btcTransaction.getHashAsString());
model.setTransaction(btcTransaction);
log.error("CreateMakerFeeTx, offerid={}, OFFER_FUNDING", id);
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
offer.setOfferFeePaymentTxId(transaction.getHashAsString());
model.setTransaction(transaction);
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
complete();
} else {
log.warn("We got the callback called after the timeout has been triggered a complete().");
}
});
}
complete();
@Override
public void onFailure(@NotNull Throwable t) {
if (!completed) {
failed(t);
} else {
log.warn("We got the callback called after the timeout has been triggered a complete().");
}
}
});
} else {
final BsqWalletService bsqWalletService = model.getBsqWalletService();
Transaction preparedBurnFeeTx = model.getBsqWalletService().getPreparedBurnFeeTx(offer.getMakerFee());
@ -107,6 +131,9 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
// We need to create another instance, otherwise the tx would trigger an invalid state exception
// if it gets committed 2 times
tradeWalletService.commitTx(tradeWalletService.getClonedTransaction(signedTx));
// We dont use a timeout here as we need to get the tradeFee tx callback called to be sure the addressEntry is funded
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {

View file

@ -18,7 +18,6 @@
package io.bisq.core.trade.protocol.tasks.taker;
import com.google.common.util.concurrent.FutureCallback;
import io.bisq.common.Timer;
import io.bisq.common.UserThread;
import io.bisq.common.taskrunner.TaskRunner;
import io.bisq.core.arbitration.Arbitrator;
@ -44,6 +43,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class CreateTakerFeeTx extends TradeTask {
private Transaction tradeFeeTx;
@SuppressWarnings({"WeakerAccess", "unused"})
public CreateTakerFeeTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
@ -71,7 +72,10 @@ public class CreateTakerFeeTx extends TradeTask {
Address changeAddress = changeAddressEntry.getAddress();
final TradeWalletService tradeWalletService = processModel.getTradeWalletService();
if (trade.isCurrencyForTakerFeeBtc()) {
Transaction createTakeOfferFeeTx = tradeWalletService.createBtcTradingFeeTx(
// We dont use a timeout here as we need to get the tradeFee tx callback called to be sure the addressEntry is funded
tradeFeeTx = tradeWalletService.createBtcTradingFeeTx(
fundingAddress,
reservedForTradeAddress,
changeAddress,
@ -79,20 +83,40 @@ public class CreateTakerFeeTx extends TradeTask {
processModel.isUseSavingsWallet(),
trade.getTakerFee(),
trade.getTxFee(),
selectedArbitrator.getBtcAddress());
selectedArbitrator.getBtcAddress(),
new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
// we delay one render frame to be sure we don't get called before the method call has
// returned (tradeFeeTx would be null in that case)
UserThread.execute(() -> {
if (!completed) {
if (tradeFeeTx != null && !tradeFeeTx.getHashAsString().equals(transaction.getHashAsString()))
log.warn("The trade fee tx received from the network had another tx ID than the one we publish");
//TODO use handler for broadcastTx success
processModel.setTakeOfferFeeTx(createTakeOfferFeeTx);
trade.setTakerFeeTxId(createTakeOfferFeeTx.getHashAsString());
// TODO cehck
// Don't call that as it caused in some cased a InsufficientMoneyException
// walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
processModel.setTakeOfferFeeTx(tradeFeeTx);
trade.setTakerFeeTxId(tradeFeeTx.getHashAsString());
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
complete();
complete();
} else {
log.warn("We got the callback called after the timeout has been triggered a complete().");
}
});
}
@Override
public void onFailure(@NotNull Throwable t) {
if (!completed) {
failed(t);
} else {
log.warn("We got the callback called after the timeout has been triggered a complete().");
}
}
});
} else {
final BsqWalletService bsqWalletService = processModel.getBsqWalletService();
Transaction preparedBurnFeeTx = processModel.getBsqWalletService().getPreparedBurnFeeTx(trade.getTakerFee());
//Coin txFee = trade.getTxFee().subtract(trade.getTakerFee());
Transaction txWithBsqFee = tradeWalletService.completeBsqTradingFeeTx(preparedBurnFeeTx,
fundingAddress,
reservedForTradeAddress,
@ -108,33 +132,19 @@ public class CreateTakerFeeTx extends TradeTask {
// if it gets committed 2 times
tradeWalletService.commitTx(tradeWalletService.getClonedTransaction(signedTx));
Timer timeoutTimer = UserThread.runAfter(() -> {
log.warn("Broadcast not completed after 5 sec. We go on with the trade protocol.");
trade.setTakerFeeTxId(signedTx.getHashAsString());
processModel.setTakeOfferFeeTx(signedTx);
// TODO cehck
// Don't call that as it caused in some cased a InsufficientMoneyException
// walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
complete();
}, 5);
// We dont use a timeout here as we need to get the tradeFee tx callback called to be sure the addressEntry is funded
bsqWalletService.broadcastTx(signedTx, new FutureCallback<Transaction>() {
@Override
public void onSuccess(@Nullable Transaction transaction) {
if (!completed) {
timeoutTimer.stop();
if (transaction != null) {
log.debug("Successfully sent tx with id " + transaction.getHashAsString());
checkArgument(transaction.equals(signedTx));
trade.setTakerFeeTxId(transaction.getHashAsString());
processModel.setTakeOfferFeeTx(transaction);
// TODO cehck
// Don't call that as it caused in some cased a InsufficientMoneyException
// walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
walletService.swapTradeEntryToAvailableEntry(id, AddressEntry.Context.OFFER_FUNDING);
complete();
}
} else {
@ -145,7 +155,6 @@ public class CreateTakerFeeTx extends TradeTask {
@Override
public void onFailure(@NotNull Throwable t) {
if (!completed) {
timeoutTimer.stop();
log.error(t.toString());
t.printStackTrace();
trade.setErrorMessage("An error occurred.\n" +