mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-10-01 01:35:48 -04:00
force restart trade wallet on confirm payment if missing txs to fix #960
This commit is contained in:
parent
2f0ea48a31
commit
6dfa1841f8
@ -1068,24 +1068,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
MoneroTxWallet splitOutputTx = null;
|
||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||
XmrAddressEntry entry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
||||
long startTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||
try {
|
||||
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
||||
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.setAddress(entry.getAddressString())
|
||||
.setAmount(reserveAmount)
|
||||
.setRelay(true)
|
||||
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.warn("Error creating split output tx to fund offer {} at subaddress {}, attempt={}/{}, error={}", openOffer.getShortId(), entry.getSubaddressIndex(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||
try {
|
||||
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
||||
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
||||
.setAccountIndex(0)
|
||||
.setAddress(entry.getAddressString())
|
||||
.setAmount(reserveAmount)
|
||||
.setRelay(true)
|
||||
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.warn("Error creating split output tx to fund offer {} at subaddress {}, attempt={}/{}, error={}", openOffer.getShortId(), entry.getSubaddressIndex(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
}
|
||||
log.info("Done creating split output tx to fund offer {} in {} ms", openOffer.getId(), System.currentTimeMillis() - startTime);
|
||||
}
|
||||
log.info("Done creating split output tx to fund offer {} in {} ms", openOffer.getId(), System.currentTimeMillis() - startTime);
|
||||
}
|
||||
|
||||
// set split tx
|
||||
|
@ -79,7 +79,7 @@ public class HavenoUtils {
|
||||
|
||||
// synchronize requests to the daemon
|
||||
private static boolean SYNC_DAEMON_REQUESTS = true; // sync long requests to daemon (e.g. refresh, update pool)
|
||||
private static boolean SYNC_WALLET_REQUESTS = false; // additionally sync wallet functions to daemon (e.g. create tx, import multisig hex)
|
||||
private static boolean SYNC_WALLET_REQUESTS = false; // additionally sync wallet functions to daemon (e.g. create txs)
|
||||
private static Object DAEMON_LOCK = new Object();
|
||||
public static Object getDaemonLock() {
|
||||
return SYNC_DAEMON_REQUESTS ? DAEMON_LOCK : new Object();
|
||||
|
@ -94,7 +94,6 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.common.MoneroRpcConnection;
|
||||
import monero.common.MoneroUtils;
|
||||
import monero.common.TaskLooper;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroKeyImage;
|
||||
@ -964,6 +963,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
private void forceCloseWallet() {
|
||||
if (wallet != null) {
|
||||
xmrWalletService.forceCloseWallet(wallet, wallet.getPath());
|
||||
stopPolling();
|
||||
wallet = null;
|
||||
}
|
||||
}
|
||||
@ -1099,36 +1099,18 @@ public abstract class Trade implements Tradable, Model {
|
||||
// check if multisig import needed
|
||||
if (wallet.isMultisigImportNeeded()) throw new RuntimeException("Cannot create payout tx because multisig import is needed");
|
||||
|
||||
// gather info
|
||||
String sellerPayoutAddress = this.getSeller().getPayoutAddressString();
|
||||
String buyerPayoutAddress = this.getBuyer().getPayoutAddressString();
|
||||
Preconditions.checkNotNull(sellerPayoutAddress, "Seller payout address must not be null");
|
||||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
||||
|
||||
// TODO: wallet query to get deposit txs can sometimes return null, maybe when disconnected?
|
||||
if (wallet.getTx(getSeller().getDepositTxHash()) == null || wallet.getTx(getBuyer().getDepositTxHash()) == null) {
|
||||
String warningMsg = "Issue detected with trade wallet " + getShortId() + ". Please send logs to Haveno developers and restart your application if you encounter further problems:";
|
||||
warningMsg += "\n\nSeller deposit tx id: " + getSeller().getDepositTxHash();
|
||||
warningMsg += "\nBuyer deposit tx id: " + getBuyer().getDepositTxHash();
|
||||
warningMsg += "\nSeller deposit tx is initialized: " + (getSeller().getDepositTx() != null);
|
||||
warningMsg += "\nBuyer deposit tx is initialized: " + (getBuyer().getDepositTx() != null);
|
||||
log.warn(warningMsg);
|
||||
|
||||
// request with logging
|
||||
int previousLogLevel = MoneroUtils.getLogLevel();
|
||||
MoneroUtils.setLogLevel(3);
|
||||
log.warn("Requesting seller tx with logging");
|
||||
MoneroTxWallet fetchedTx = wallet.getTx(getSeller().getDepositTxHash());
|
||||
log.info("Seller tx: " + fetchedTx);
|
||||
log.warn("Requesting buyer tx with logging");
|
||||
fetchedTx = wallet.getTx(getBuyer().getDepositTxHash());
|
||||
log.info("Buyer tx: " + fetchedTx);
|
||||
MoneroUtils.setLogLevel(previousLogLevel);
|
||||
|
||||
// set top level error message to notify user
|
||||
HavenoUtils.havenoSetup.getTopErrorMsg().set(warningMsg);
|
||||
// TODO: wallet sometimes returns empty data, after disconnect?
|
||||
List<MoneroTxWallet> txs = wallet.getTxs(); // TODO: this fetches from pool
|
||||
if (txs.isEmpty()) {
|
||||
log.warn("Restarting wallet for {} {} because deposit txs are missing to create payout tx", getClass().getSimpleName(), getId());
|
||||
forceRestartTradeWallet();
|
||||
}
|
||||
|
||||
// gather info
|
||||
String sellerPayoutAddress = getSeller().getPayoutAddressString();
|
||||
String buyerPayoutAddress = getBuyer().getPayoutAddressString();
|
||||
Preconditions.checkNotNull(sellerPayoutAddress, "Seller payout address must not be null");
|
||||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
||||
BigInteger sellerDepositAmount = getSeller().getDepositTx().getIncomingAmount();
|
||||
BigInteger buyerDepositAmount = getBuyer().getDepositTx().getIncomingAmount();
|
||||
BigInteger tradeAmount = getAmount();
|
||||
@ -1163,7 +1145,7 @@ public abstract class Trade implements Tradable, Model {
|
||||
return createTx(txConfig);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("not possible")) throw new RuntimeException("Loser payout is too small to cover the mining fee");
|
||||
log.warn("Failed to create payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
log.warn("Failed to create dispute payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
@ -1183,6 +1165,22 @@ public abstract class Trade implements Tradable, Model {
|
||||
public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) {
|
||||
log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId());
|
||||
|
||||
// TODO: wallet sometimes returns empty data, after disconnect? detect this condition with failure tolerance
|
||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||
try {
|
||||
List<MoneroTxWallet> txs = wallet.getTxs(); // TODO: this fetches from pool
|
||||
if (txs.isEmpty()) {
|
||||
log.warn("Restarting wallet for {} {} because deposit txs are missing to process payout tx", getClass().getSimpleName(), getId());
|
||||
forceRestartTradeWallet();
|
||||
}
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed get wallet txs, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
}
|
||||
}
|
||||
|
||||
// gather relevant info
|
||||
MoneroWallet wallet = getWallet();
|
||||
Contract contract = getContract();
|
||||
@ -2514,12 +2512,13 @@ public abstract class Trade implements Tradable, Model {
|
||||
}
|
||||
|
||||
private void forceRestartTradeWallet() {
|
||||
log.warn("Force restarting trade wallet for {} {}", getClass().getSimpleName(), getId());
|
||||
if (isShutDownStarted || restartInProgress) return;
|
||||
log.warn("Force restarting trade wallet for {} {}", getClass().getSimpleName(), getId());
|
||||
restartInProgress = true;
|
||||
forceCloseWallet();
|
||||
if (!isShutDownStarted) wallet = getWallet();
|
||||
restartInProgress = false;
|
||||
doPollWallet();
|
||||
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||
}
|
||||
|
||||
@ -2609,15 +2608,15 @@ public abstract class Trade implements Tradable, Model {
|
||||
// skip if arbitrator
|
||||
if (this instanceof ArbitratorTrade) return;
|
||||
|
||||
// freeze outputs until spent
|
||||
xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages());
|
||||
|
||||
// close open offer or reset address entries
|
||||
if (this instanceof MakerTrade) {
|
||||
processModel.getOpenOfferManager().closeOpenOffer(getOffer());
|
||||
} else {
|
||||
getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
|
||||
}
|
||||
|
||||
// freeze outputs until spent
|
||||
ThreadUtils.submitToPool(() -> xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages()));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -114,7 +114,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
private byte[] payoutTxSignature;
|
||||
@Nullable
|
||||
@Setter
|
||||
private byte[] preparedDepositTx;
|
||||
private byte[] preparedDepositTx; // TODO: remove this unused field
|
||||
@Setter
|
||||
private boolean useSavingsWallet;
|
||||
@Setter
|
||||
|
@ -1735,9 +1735,9 @@ public class XmrWalletService {
|
||||
|
||||
private void forceCloseMainWallet() {
|
||||
isClosingWallet = true;
|
||||
forceCloseWallet(wallet, getWalletPath(MONERO_WALLET_NAME));
|
||||
stopPolling();
|
||||
stopSyncWithProgress();
|
||||
forceCloseWallet(wallet, getWalletPath(MONERO_WALLET_NAME));
|
||||
wallet = null;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user