mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-05-21 16:00:47 -04:00
recover from offer funds being unexpectedly unavailable
This commit is contained in:
parent
fc3407cd50
commit
13d6eaee7d
6 changed files with 64 additions and 22 deletions
|
@ -556,13 +556,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
latch.countDown();
|
||||
resultHandler.handleResult(transaction);
|
||||
}, (errorMessage) -> {
|
||||
if (openOffer.isCanceled()) latch.countDown();
|
||||
else {
|
||||
if (!openOffer.isCanceled()) {
|
||||
log.warn("Error processing pending offer {}: {}", openOffer.getId(), errorMessage);
|
||||
doCancelOffer(openOffer);
|
||||
latch.countDown();
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
}
|
||||
latch.countDown();
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
HavenoUtils.awaitLatch(latch);
|
||||
}
|
||||
|
@ -943,8 +942,18 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
// if not found, create tx to split exact output
|
||||
if (splitOutputTx == null) {
|
||||
if (openOffer.getSplitOutputTxHash() != null) log.warn("Split output tx not found for offer {}", openOffer.getId());
|
||||
splitOrSchedule(openOffers, openOffer, amountNeeded);
|
||||
if (openOffer.getSplitOutputTxHash() != null) {
|
||||
log.warn("Split output tx unexpectedly unavailable for offer, offerId={}, split output tx={}", openOffer.getId(), openOffer.getSplitOutputTxHash());
|
||||
setSplitOutputTx(openOffer, null);
|
||||
}
|
||||
try {
|
||||
splitOrSchedule(openOffers, openOffer, amountNeeded);
|
||||
} catch (Exception e) {
|
||||
log.warn("Unable to split or schedule funds for offer {}: {}", openOffer.getId(), e.getMessage());
|
||||
openOffer.getOffer().setState(Offer.State.INVALID);
|
||||
errorMessageHandler.handleErrorMessage(e.getMessage());
|
||||
return;
|
||||
}
|
||||
} else if (!splitOutputTx.isLocked()) {
|
||||
|
||||
// otherwise sign and post offer if split output available
|
||||
|
@ -981,7 +990,23 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
|
||||
// return split output tx if already assigned
|
||||
if (openOffer != null && openOffer.getSplitOutputTxHash() != null) {
|
||||
return xmrWalletService.getTx(openOffer.getSplitOutputTxHash());
|
||||
|
||||
// get recorded split output tx
|
||||
MoneroTxWallet splitOutputTx = xmrWalletService.getTx(openOffer.getSplitOutputTxHash());
|
||||
|
||||
// check if split output tx is available for offer
|
||||
if (splitOutputTx.isLocked()) return splitOutputTx;
|
||||
else {
|
||||
boolean isAvailable = true;
|
||||
for (MoneroOutputWallet output : splitOutputTx.getOutputsWallet()) {
|
||||
if (output.isSpent() || output.isFrozen()) {
|
||||
isAvailable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isAvailable || isReservedByOffer(openOffer, splitOutputTx)) return splitOutputTx;
|
||||
else log.warn("Split output tx is no longer available for offer {}", openOffer.getId());
|
||||
}
|
||||
}
|
||||
|
||||
// get split output tx to offer's preferred subaddress
|
||||
|
@ -996,6 +1021,15 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
return getEarliestUnscheduledTx(openOffers, openOffer, fundingTxs);
|
||||
}
|
||||
|
||||
private boolean isReservedByOffer(OpenOffer openOffer, MoneroTxWallet tx) {
|
||||
if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) return false;
|
||||
Set<String> offerKeyImages = new HashSet<String>(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages());
|
||||
for (MoneroOutputWallet output : tx.getOutputsWallet()) {
|
||||
if (offerKeyImages.contains(output.getKeyImage().getHex())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<MoneroTxWallet> getSplitOutputFundingTxs(BigInteger reserveAmount, Integer preferredSubaddressIndex) {
|
||||
List<MoneroTxWallet> splitOutputTxs = xmrWalletService.getTxs(new MoneroTxQuery().setIsIncoming(true).setIsFailed(false));
|
||||
Set<MoneroTxWallet> removeTxs = new HashSet<MoneroTxWallet>();
|
||||
|
@ -1064,7 +1098,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
.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 (e.getMessage().contains("not enough")) throw e; // do not retry if not enough funds
|
||||
log.warn("Error creating split output tx to fund offer, offerId={}, subaddress={}, attempt={}/{}, error={}", openOffer.getShortId(), entry.getSubaddressIndex(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (stopped || i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
if (xmrConnectionService.isConnected()) xmrWalletService.requestSwitchToNextBestConnection();
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
|
@ -1080,10 +1115,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
}
|
||||
|
||||
private void setSplitOutputTx(OpenOffer openOffer, MoneroTxWallet splitOutputTx) {
|
||||
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
|
||||
openOffer.setSplitOutputTxFee(splitOutputTx.getFee().longValueExact());
|
||||
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
|
||||
openOffer.setScheduledAmount(openOffer.getOffer().getAmountNeeded().toString());
|
||||
openOffer.setSplitOutputTxHash(splitOutputTx == null ? null : splitOutputTx.getHash());
|
||||
openOffer.setSplitOutputTxFee(splitOutputTx == null ? 0l : splitOutputTx.getFee().longValueExact());
|
||||
openOffer.setScheduledTxHashes(splitOutputTx == null ? null : Arrays.asList(splitOutputTx.getHash()));
|
||||
openOffer.setScheduledAmount(splitOutputTx == null ? null : openOffer.getOffer().getAmountNeeded().toString());
|
||||
if (!openOffer.isCanceled()) openOffer.setState(OpenOffer.State.PENDING);
|
||||
}
|
||||
|
||||
|
@ -1139,9 +1174,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
|||
for (OpenOffer otherOffer : openOffers) {
|
||||
if (otherOffer == openOffer) continue;
|
||||
if (otherOffer.getState() != OpenOffer.State.PENDING) continue;
|
||||
if (otherOffer.getScheduledTxHashes() == null) continue;
|
||||
for (String scheduledTxHash : otherOffer.getScheduledTxHashes()) {
|
||||
if (txHash.equals(scheduledTxHash)) return true;
|
||||
if (txHash.equals(otherOffer.getSplitOutputTxHash())) return true;
|
||||
if (otherOffer.getScheduledTxHashes() != null) {
|
||||
for (String scheduledTxHash : otherOffer.getScheduledTxHashes()) {
|
||||
if (txHash.equals(scheduledTxHash)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -86,7 +86,7 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
|
|||
//if (true) throw new RuntimeException("Pretend error");
|
||||
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
|
||||
} catch (Exception e) {
|
||||
log.warn("Error creating reserve tx, attempt={}/{}, offerId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, openOffer.getShortId(), e.getMessage());
|
||||
log.warn("Error creating reserve tx, offerId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, openOffer.getShortId(), e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
model.getProtocol().startTimeoutTimer(); // reset protocol timeout
|
||||
if (model.getXmrWalletService().getConnectionService().isConnected()) model.getXmrWalletService().requestSwitchToNextBestConnection();
|
||||
|
|
|
@ -476,7 +476,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
break;
|
||||
} catch (Exception e) {
|
||||
if (trade.isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
|
||||
log.warn("Failed to submit dispute payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, trade.getShortId(), e.getMessage());
|
||||
log.warn("Failed to submit dispute payout tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
if (trade.getXmrConnectionService().isConnected()) trade.requestSwitchToNextBestConnection();
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
|
|
|
@ -1073,7 +1073,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to import multisig hex, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
||||
if (isReadTimeoutError(e.getMessage())) forceRestartTradeWallet(); // wallet can be stuck a while
|
||||
|
@ -1180,7 +1180,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to create payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
log.warn("Failed to create payout tx, tradeId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
|
@ -1241,7 +1241,7 @@ public abstract class Trade implements Tradable, Model {
|
|||
throw e;
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("not possible")) throw new IllegalArgumentException("Loser payout is too small to cover the mining fee");
|
||||
log.warn("Failed to create dispute payout tx, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
log.warn("Failed to create dispute payout tx, tradeId={}, attempt={}/{}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
|
||||
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
|
||||
if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection();
|
||||
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue