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;
|
MoneroTxWallet splitOutputTx = null;
|
||||||
synchronized (XmrWalletService.WALLET_LOCK) {
|
synchronized (XmrWalletService.WALLET_LOCK) {
|
||||||
XmrAddressEntry entry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
XmrAddressEntry entry = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING);
|
||||||
long startTime = System.currentTimeMillis();
|
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||||
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
long startTime = System.currentTimeMillis();
|
||||||
try {
|
for (int i = 0; i < TradeProtocol.MAX_ATTEMPTS; i++) {
|
||||||
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
try {
|
||||||
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
log.info("Creating split output tx to fund offer {} at subaddress {}", openOffer.getShortId(), entry.getSubaddressIndex());
|
||||||
.setAccountIndex(0)
|
splitOutputTx = xmrWalletService.createTx(new MoneroTxConfig()
|
||||||
.setAddress(entry.getAddressString())
|
.setAccountIndex(0)
|
||||||
.setAmount(reserveAmount)
|
.setAddress(entry.getAddressString())
|
||||||
.setRelay(true)
|
.setAmount(reserveAmount)
|
||||||
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
.setRelay(true)
|
||||||
break;
|
.setPriority(XmrWalletService.PROTOCOL_FEE_PRIORITY));
|
||||||
} catch (Exception e) {
|
break;
|
||||||
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());
|
} catch (Exception e) {
|
||||||
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw 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());
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
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
|
// set split tx
|
||||||
|
@ -79,7 +79,7 @@ public class HavenoUtils {
|
|||||||
|
|
||||||
// synchronize requests to the daemon
|
// 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_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();
|
private static Object DAEMON_LOCK = new Object();
|
||||||
public static Object getDaemonLock() {
|
public static Object getDaemonLock() {
|
||||||
return SYNC_DAEMON_REQUESTS ? DAEMON_LOCK : new Object();
|
return SYNC_DAEMON_REQUESTS ? DAEMON_LOCK : new Object();
|
||||||
|
@ -94,7 +94,6 @@ import lombok.Getter;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.common.MoneroRpcConnection;
|
import monero.common.MoneroRpcConnection;
|
||||||
import monero.common.MoneroUtils;
|
|
||||||
import monero.common.TaskLooper;
|
import monero.common.TaskLooper;
|
||||||
import monero.daemon.MoneroDaemon;
|
import monero.daemon.MoneroDaemon;
|
||||||
import monero.daemon.model.MoneroKeyImage;
|
import monero.daemon.model.MoneroKeyImage;
|
||||||
@ -964,6 +963,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
private void forceCloseWallet() {
|
private void forceCloseWallet() {
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
xmrWalletService.forceCloseWallet(wallet, wallet.getPath());
|
xmrWalletService.forceCloseWallet(wallet, wallet.getPath());
|
||||||
|
stopPolling();
|
||||||
wallet = null;
|
wallet = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1099,36 +1099,18 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// check if multisig import needed
|
// check if multisig import needed
|
||||||
if (wallet.isMultisigImportNeeded()) throw new RuntimeException("Cannot create payout tx because multisig import is needed");
|
if (wallet.isMultisigImportNeeded()) throw new RuntimeException("Cannot create payout tx because multisig import is needed");
|
||||||
|
|
||||||
// gather info
|
// TODO: wallet sometimes returns empty data, after disconnect?
|
||||||
String sellerPayoutAddress = this.getSeller().getPayoutAddressString();
|
List<MoneroTxWallet> txs = wallet.getTxs(); // TODO: this fetches from pool
|
||||||
String buyerPayoutAddress = this.getBuyer().getPayoutAddressString();
|
if (txs.isEmpty()) {
|
||||||
Preconditions.checkNotNull(sellerPayoutAddress, "Seller payout address must not be null");
|
log.warn("Restarting wallet for {} {} because deposit txs are missing to create payout tx", getClass().getSimpleName(), getId());
|
||||||
Preconditions.checkNotNull(buyerPayoutAddress, "Buyer payout address must not be null");
|
forceRestartTradeWallet();
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 sellerDepositAmount = getSeller().getDepositTx().getIncomingAmount();
|
||||||
BigInteger buyerDepositAmount = getBuyer().getDepositTx().getIncomingAmount();
|
BigInteger buyerDepositAmount = getBuyer().getDepositTx().getIncomingAmount();
|
||||||
BigInteger tradeAmount = getAmount();
|
BigInteger tradeAmount = getAmount();
|
||||||
@ -1163,7 +1145,7 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
return createTx(txConfig);
|
return createTx(txConfig);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (e.getMessage().contains("not possible")) throw new RuntimeException("Loser payout is too small to cover the mining fee");
|
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;
|
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
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) {
|
public void processPayoutTx(String payoutTxHex, boolean sign, boolean publish) {
|
||||||
log.info("Processing payout tx for {} {}", getClass().getSimpleName(), getId());
|
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
|
// gather relevant info
|
||||||
MoneroWallet wallet = getWallet();
|
MoneroWallet wallet = getWallet();
|
||||||
Contract contract = getContract();
|
Contract contract = getContract();
|
||||||
@ -2514,12 +2512,13 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void forceRestartTradeWallet() {
|
private void forceRestartTradeWallet() {
|
||||||
log.warn("Force restarting trade wallet for {} {}", getClass().getSimpleName(), getId());
|
|
||||||
if (isShutDownStarted || restartInProgress) return;
|
if (isShutDownStarted || restartInProgress) return;
|
||||||
|
log.warn("Force restarting trade wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
restartInProgress = true;
|
restartInProgress = true;
|
||||||
forceCloseWallet();
|
forceCloseWallet();
|
||||||
if (!isShutDownStarted) wallet = getWallet();
|
if (!isShutDownStarted) wallet = getWallet();
|
||||||
restartInProgress = false;
|
restartInProgress = false;
|
||||||
|
doPollWallet();
|
||||||
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitPolling(), getId());
|
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2609,15 +2608,15 @@ public abstract class Trade implements Tradable, Model {
|
|||||||
// skip if arbitrator
|
// skip if arbitrator
|
||||||
if (this instanceof ArbitratorTrade) return;
|
if (this instanceof ArbitratorTrade) return;
|
||||||
|
|
||||||
// freeze outputs until spent
|
|
||||||
xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages());
|
|
||||||
|
|
||||||
// close open offer or reset address entries
|
// close open offer or reset address entries
|
||||||
if (this instanceof MakerTrade) {
|
if (this instanceof MakerTrade) {
|
||||||
processModel.getOpenOfferManager().closeOpenOffer(getOffer());
|
processModel.getOpenOfferManager().closeOpenOffer(getOffer());
|
||||||
} else {
|
} else {
|
||||||
getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
|
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;
|
private byte[] payoutTxSignature;
|
||||||
@Nullable
|
@Nullable
|
||||||
@Setter
|
@Setter
|
||||||
private byte[] preparedDepositTx;
|
private byte[] preparedDepositTx; // TODO: remove this unused field
|
||||||
@Setter
|
@Setter
|
||||||
private boolean useSavingsWallet;
|
private boolean useSavingsWallet;
|
||||||
@Setter
|
@Setter
|
||||||
|
@ -1735,9 +1735,9 @@ public class XmrWalletService {
|
|||||||
|
|
||||||
private void forceCloseMainWallet() {
|
private void forceCloseMainWallet() {
|
||||||
isClosingWallet = true;
|
isClosingWallet = true;
|
||||||
|
forceCloseWallet(wallet, getWalletPath(MONERO_WALLET_NAME));
|
||||||
stopPolling();
|
stopPolling();
|
||||||
stopSyncWithProgress();
|
stopSyncWithProgress();
|
||||||
forceCloseWallet(wallet, getWalletPath(MONERO_WALLET_NAME));
|
|
||||||
wallet = null;
|
wallet = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user