process unposted offers within lock

This commit is contained in:
woodser 2023-07-27 08:02:29 -04:00
parent 900d3a91e1
commit a088f685c1

View file

@ -152,6 +152,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds private static final long KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL = 20000; // 20 seconds
private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes private static final long KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE = 300000; // 5 minutes
private Object processOffersLock = new Object(); // lock for processing offers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialization // Constructor, Initialization
@ -423,8 +425,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// .forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService) // .forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService)
// .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg)))); // .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg))));
// process unposted offers // process scheduled offers
processUnpostedOffers((transaction) -> {}, (errorMessage) -> { processScheduledOffers((transaction) -> {}, (errorMessage) -> {
log.warn("Error processing unposted offers: " + errorMessage); log.warn("Error processing unposted offers: " + errorMessage);
}); });
@ -434,7 +436,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
@Override @Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
if (lastUnlockedBalance == null || lastUnlockedBalance.compareTo(newUnlockedBalance) < 0) { if (lastUnlockedBalance == null || lastUnlockedBalance.compareTo(newUnlockedBalance) < 0) {
processUnpostedOffers((transaction) -> {}, (errorMessage) -> { processScheduledOffers((transaction) -> {}, (errorMessage) -> {
log.warn("Error processing unposted offers on new unlocked balance: " + errorMessage); // TODO: popup to notify user that offer did not post log.warn("Error processing unposted offers on new unlocked balance: " + errorMessage); // TODO: popup to notify user that offer did not post
}); });
} }
@ -498,17 +500,25 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// create open offer // create open offer
OpenOffer openOffer = new OpenOffer(offer, triggerPrice, reserveExactAmount); OpenOffer openOffer = new OpenOffer(offer, triggerPrice, reserveExactAmount);
// process open offer to schedule or post // schedule or post offer
processUnpostedOffer(getOpenOffers(), openOffer, (transaction) -> { new Thread(() -> {
addOpenOffer(openOffer); synchronized (processOffersLock) {
requestPersistence(); CountDownLatch latch = new CountDownLatch(1);
resultHandler.handleResult(transaction); processUnpostedOffer(getOpenOffers(), openOffer, (transaction) -> {
}, (errorMessage) -> { addOpenOffer(openOffer);
log.warn("Error processing unposted offer {}: {}", openOffer.getId(), errorMessage); requestPersistence();
onCancelled(openOffer); latch.countDown();
offer.setErrorMessage(errorMessage); resultHandler.handleResult(transaction);
errorMessageHandler.handleErrorMessage(errorMessage); }, (errorMessage) -> {
}); log.warn("Error processing unposted offer {}: {}", openOffer.getId(), errorMessage);
onCancelled(openOffer);
offer.setErrorMessage(errorMessage);
latch.countDown();
errorMessageHandler.handleErrorMessage(errorMessage);
});
HavenoUtils.awaitLatch(latch);
}
}).start();
} }
// Remove from offerbook // Remove from offerbook
@ -772,94 +782,94 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// Place offer helpers // Place offer helpers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void processUnpostedOffers(TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler private void processScheduledOffers(TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
new Thread(() -> { new Thread(() -> {
List<String> errorMessages = new ArrayList<String>(); synchronized (processOffersLock) {
List<OpenOffer> openOffers = getOpenOffers(); List<String> errorMessages = new ArrayList<String>();
for (OpenOffer scheduledOffer : openOffers) { List<OpenOffer> openOffers = getOpenOffers();
if (scheduledOffer.getState() != OpenOffer.State.SCHEDULED) continue; for (OpenOffer scheduledOffer : openOffers) {
CountDownLatch latch = new CountDownLatch(1); if (scheduledOffer.getState() != OpenOffer.State.SCHEDULED) continue;
processUnpostedOffer(openOffers, scheduledOffer, (transaction) -> { CountDownLatch latch = new CountDownLatch(1);
latch.countDown(); processUnpostedOffer(openOffers, scheduledOffer, (transaction) -> {
}, errorMessage -> { latch.countDown();
log.warn("Error processing unposted offer {}: {}", scheduledOffer.getId(), errorMessage); }, errorMessage -> {
onCancelled(scheduledOffer); log.warn("Error processing unposted offer {}: {}", scheduledOffer.getId(), errorMessage);
errorMessages.add(errorMessage); onCancelled(scheduledOffer);
latch.countDown(); errorMessages.add(errorMessage);
}); latch.countDown();
HavenoUtils.awaitLatch(latch); });
HavenoUtils.awaitLatch(latch);
}
requestPersistence();
if (errorMessages.size() > 0) errorMessageHandler.handleErrorMessage(errorMessages.toString());
else resultHandler.handleResult(null);
} }
requestPersistence();
if (errorMessages.size() > 0) errorMessageHandler.handleErrorMessage(errorMessages.toString());
else resultHandler.handleResult(null);
}).start(); }).start();
} }
private void processUnpostedOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { private void processUnpostedOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
new Thread(() -> { new Thread(() -> {
synchronized (xmrWalletService) { try {
try {
// done processing if wallet not initialized // done processing if wallet not initialized
if (xmrWalletService.getWallet() == null) { if (xmrWalletService.getWallet() == null) {
resultHandler.handleResult(null); resultHandler.handleResult(null);
return;
}
// get offer reserve amount
BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount();
// handle split output offer
if (openOffer.isReserveExactAmount()) {
// find tx with exact input amount
MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer);
if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) {
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash()));
openOffer.setSplitOutputTxHash(splitOutputTx.getHash());
openOffer.setScheduledAmount(offerReserveAmount.toString());
openOffer.setState(OpenOffer.State.SCHEDULED);
}
// if not found, create tx to split exact output
if (splitOutputTx == null) {
splitOrSchedule(openOffers, openOffer, offerReserveAmount);
} else if (!splitOutputTx.isLocked()) {
// otherwise sign and post offer if split output available
signAndPostOffer(openOffer, true, resultHandler, (errMsg) -> {
// on error, create new tx to split output if offer subaddress does not have exact output
int offerSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getSubaddressIndex();
if (!splitOutputTx.getOutgoingTransfer().getSubaddressIndices().equals(Arrays.asList(offerSubaddress))) {
log.warn("Splitting new output because spending existing output(s) failed for offer {}. Split output tx subaddresses={}. Offer funding subadress={}", openOffer.getId(), splitOutputTx.getOutgoingTransfer().getSubaddressIndices(), offerSubaddress);
splitOrSchedule(openOffers, openOffer, offerReserveAmount);
resultHandler.handleResult(null);
} else {
errorMessageHandler.handleErrorMessage(errMsg);
}
});
return; return;
} }
} else {
// get offer reserve amount
BigInteger offerReserveAmount = openOffer.getOffer().getReserveAmount();
// handle split output offer
if (openOffer.isReserveExactAmount()) {
// find tx with exact input amount // handle sufficient balance
MoneroTxWallet splitOutputTx = findSplitOutputFundingTx(openOffers, openOffer); boolean hasSufficientBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0;
if (splitOutputTx != null && openOffer.getScheduledTxHashes() == null) { if (hasSufficientBalance) {
openOffer.setScheduledTxHashes(Arrays.asList(splitOutputTx.getHash())); signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
openOffer.setSplitOutputTxHash(splitOutputTx.getHash()); return;
openOffer.setScheduledAmount(offerReserveAmount.toString()); } else if (openOffer.getScheduledTxHashes() == null) {
openOffer.setState(OpenOffer.State.SCHEDULED); scheduleWithEarliestTxs(openOffers, openOffer);
}
// if not found, create tx to split exact output
if (splitOutputTx == null) {
splitOrSchedule(openOffers, openOffer, offerReserveAmount);
} else if (!splitOutputTx.isLocked()) {
// otherwise sign and post offer if split output available
signAndPostOffer(openOffer, true, resultHandler, (errMsg) -> {
// on error, create new tx to split output if offer subaddress does not have exact output
int offerSubaddress = xmrWalletService.getOrCreateAddressEntry(openOffer.getId(), XmrAddressEntry.Context.OFFER_FUNDING).getSubaddressIndex();
if (!splitOutputTx.getOutgoingTransfer().getSubaddressIndices().equals(Arrays.asList(offerSubaddress))) {
log.warn("Splitting new output because spending existing output(s) failed for offer {}. Split output tx subaddresses={}. Offer funding subadress={}", openOffer.getId(), splitOutputTx.getOutgoingTransfer().getSubaddressIndices(), offerSubaddress);
splitOrSchedule(openOffers, openOffer, offerReserveAmount);
resultHandler.handleResult(null);
} else {
errorMessageHandler.handleErrorMessage(errMsg);
}
});
return;
}
} else {
// handle sufficient balance
boolean hasSufficientBalance = xmrWalletService.getWallet().getUnlockedBalance(0).compareTo(offerReserveAmount) >= 0;
if (hasSufficientBalance) {
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
return;
} else if (openOffer.getScheduledTxHashes() == null) {
scheduleWithEarliestTxs(openOffers, openOffer);
}
} }
// handle result
resultHandler.handleResult(null);
} catch (Exception e) {
e.printStackTrace();
errorMessageHandler.handleErrorMessage(e.getMessage());
} }
// handle result
resultHandler.handleResult(null);
} catch (Exception e) {
e.printStackTrace();
errorMessageHandler.handleErrorMessage(e.getMessage());
} }
}).start(); }).start();
} }
@ -1090,7 +1100,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
synchronized (placeOfferProtocols) { synchronized (placeOfferProtocols) {
placeOfferProtocols.put(openOffer.getOffer().getId(), placeOfferProtocol); placeOfferProtocols.put(openOffer.getOffer().getId(), placeOfferProtocol);
} }
placeOfferProtocol.placeOffer(); // TODO (woodser): if error placing offer (e.g. bad signature), remove protocol and unfreeze trade funds placeOfferProtocol.placeOffer();
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////