mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 07:07:43 +01:00
TxBroadcaster improvements
This commit is contained in:
parent
da20c4d5c6
commit
48f159d694
2 changed files with 29 additions and 105 deletions
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* 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.btc.exceptions;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
public class TxMalleabilityException extends TxBroadcastException {
|
||||
@Getter
|
||||
@Nullable
|
||||
private final Transaction localTx;
|
||||
@Getter
|
||||
@Nullable
|
||||
private final Transaction networkTx;
|
||||
|
||||
public TxMalleabilityException(Transaction localTx, Transaction networkTx) {
|
||||
super("The transaction we received from the Bitcoin network has a different txId as the one we broadcasted.\n" +
|
||||
"txId of local tx=" + localTx.getHashAsString() +
|
||||
", txId of received tx=" + localTx.getHashAsString());
|
||||
this.localTx = localTx;
|
||||
this.networkTx = networkTx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TxMalleabilityException{" +
|
||||
"\n localTx=" + localTx +
|
||||
",\n networkTx=" + networkTx +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ package bisq.core.btc.wallet;
|
|||
|
||||
import bisq.core.btc.exceptions.TxBroadcastException;
|
||||
import bisq.core.btc.exceptions.TxBroadcastTimeoutException;
|
||||
import bisq.core.btc.exceptions.TxMalleabilityException;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
|
@ -48,35 +47,21 @@ public class TxBroadcaster {
|
|||
default void onTimeout(TxBroadcastTimeoutException exception) {
|
||||
Transaction tx = exception.getLocalTx();
|
||||
if (tx != null) {
|
||||
log.warn("TxBroadcaster.onTimeout called: {} \n" +
|
||||
"We optimistically assume that the tx broadcast succeeds later and call onSuccess on the " +
|
||||
"callback handler. This behaviour carries less potential problems than if we would trigger " +
|
||||
"a failure (e.g. which would cause a failed create offer attempt of failed take offer attempt).\n" +
|
||||
"We have no guarantee how long it will take to get the information that sufficiently many BTC " +
|
||||
"nodes have reported back to BitcoinJ that the tx is in their mempool.\n" +
|
||||
"In normal situations " +
|
||||
"that's very fast but in some cases it can take minutes (mostly related to Tor connection " +
|
||||
"issues). So if we just go on in the application logic and treat it as successful and the " +
|
||||
"tx will be broadcast successfully later all is fine.\n" +
|
||||
"If it will fail to get broadcast, " +
|
||||
"it will lead to a failure state, the same as if we would trigger a failure due the timeout." +
|
||||
"So we can assume that this behaviour will lead to less problems as otherwise.\n" +
|
||||
"Long term we should implement better monitoring for Tor and the provided Bitcoin nodes to " +
|
||||
"find out why those delays happen and add some rollback behaviour to the app state in case " +
|
||||
"the tx will never get broadcast.",
|
||||
exception.toString());
|
||||
|
||||
// The wallet.maybeCommitTx() call is required in case the tx is spent by a follow up tx as otherwise there would be an
|
||||
// InsufficientMoneyException thrown. But in some test scenarios we also got issues with wallet
|
||||
// inconsistency if the tx was committed twice. It should be prevented by the maybeCommitTx methods but
|
||||
// not 100% if that is always the case. Just added that comment to make clear that this might be a risky
|
||||
// strategy and might need improvement if we get problems.
|
||||
// UPDATE: We got reported an wallet problem that a tx was added twice and wallet could not be loaded anymore even after a SPV resync.
|
||||
// So it seems that this strategy is too risky to cause more problems as it tries to solve.
|
||||
// Need more work from a BitcoinJ expert! For now we comment the call out here but leave it as reference
|
||||
// for future improvements.
|
||||
// exception.getWallet().maybeCommitTx(tx);
|
||||
|
||||
// We optimistically assume that the tx broadcast succeeds later and call onSuccess on the callback handler.
|
||||
// This behaviour carries less potential problems than if we would trigger a failure (e.g. which would cause
|
||||
// a failed create offer attempt or failed take offer attempt).
|
||||
// We have no guarantee how long it will take to get the information that sufficiently many BTC nodes have
|
||||
// reported back to BitcoinJ that the tx is in their mempool.
|
||||
// In normal situations that's very fast but in some cases it can take minutes (mostly related to Tor
|
||||
// connection issues). So if we just go on in the application logic and treat it as successful and the
|
||||
// tx will be broadcast successfully later all is fine.
|
||||
// If it will fail to get broadcast, it will lead to a failure state, the same as if we would trigger a
|
||||
// failure due the timeout.
|
||||
// So we can assume that this behaviour will lead to less problems as otherwise.
|
||||
// Long term we should implement better monitoring for Tor and the provided Bitcoin nodes to find out
|
||||
// why those delays happen and add some rollback behaviour to the app state in case the tx will never
|
||||
// get broadcast.
|
||||
log.warn("TxBroadcaster.onTimeout called: {}", exception.toString());
|
||||
onSuccess(tx);
|
||||
} else {
|
||||
log.error("TxBroadcaster.onTimeout: Tx is null. exception={} ", exception.toString());
|
||||
|
@ -84,11 +69,6 @@ public class TxBroadcaster {
|
|||
}
|
||||
}
|
||||
|
||||
default void onTxMalleability(TxMalleabilityException exception) {
|
||||
log.error("onTxMalleability.onTimeout " + exception.toString());
|
||||
onFailure(exception);
|
||||
}
|
||||
|
||||
void onFailure(TxBroadcastException exception);
|
||||
}
|
||||
|
||||
|
@ -118,30 +98,24 @@ public class TxBroadcaster {
|
|||
"which has an open timeoutTimer. txId=" + txId, txId)));
|
||||
}
|
||||
|
||||
// We decided the least risky scenario is to commit the tx to the wallet and broadcast it later.
|
||||
// If it's a bsq tx WalletManager.publishAndCommitBsqTx() should have commited the tx to both bsq and btc
|
||||
// wallets so the next line causes no effect.
|
||||
// If it's a btc tx, the next line adds the tx to the wallet.
|
||||
wallet.maybeCommitTx(tx);
|
||||
|
||||
Futures.addCallback(peerGroup.broadcastTransaction(tx).future(), new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction result) {
|
||||
if (result != null) {
|
||||
if (txId.equals(result.getHashAsString())) {
|
||||
// We expect that there is still a timeout in our map, otherwise the timeout got triggered
|
||||
if (broadcastTimerMap.containsKey(txId)) {
|
||||
wallet.maybeCommitTx(tx);
|
||||
stopAndRemoveTimer(txId);
|
||||
// At regtest we get called immediately back but we want to make sure that the handler is not called
|
||||
// before the caller is finished.
|
||||
UserThread.execute(() -> callback.onSuccess(tx));
|
||||
} else {
|
||||
stopAndRemoveTimer(txId);
|
||||
log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout.", txId);
|
||||
}
|
||||
} else {
|
||||
stopAndRemoveTimer(txId);
|
||||
UserThread.execute(() -> callback.onTxMalleability(new TxMalleabilityException(tx, result)));
|
||||
}
|
||||
} else {
|
||||
// We expect that there is still a timeout in our map, otherwise the timeout got triggered
|
||||
if (broadcastTimerMap.containsKey(txId)) {
|
||||
stopAndRemoveTimer(txId);
|
||||
UserThread.execute(() -> callback.onFailure(new TxBroadcastException("Transaction returned from the " +
|
||||
"broadcastTransaction call back is null.", txId)));
|
||||
// At regtest we get called immediately back but we want to make sure that the handler is not called
|
||||
// before the caller is finished.
|
||||
UserThread.execute(() -> callback.onSuccess(tx));
|
||||
} else {
|
||||
stopAndRemoveTimer(txId); //useless - txId was already removed.
|
||||
log.warn("We got an onSuccess callback for a broadcast which already triggered the timeout.", txId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue