reprocess scheduled offers on new block

This commit is contained in:
woodser 2024-05-22 19:09:35 -04:00
parent a1e554473a
commit 35f275805b
5 changed files with 55 additions and 27 deletions

View file

@ -103,6 +103,12 @@ public final class OpenOffer implements Tradable {
@Setter @Setter
transient private long mempoolStatus = -1; transient private long mempoolStatus = -1;
transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state); transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state);
@Getter
@Setter
transient boolean isProcessing = false;
@Getter
@Setter
transient int numProcessingAttempts = 0;
public OpenOffer(Offer offer) { public OpenOffer(Offer offer) {
this(offer, 0, false); this(offer, 0, false);
@ -193,9 +199,9 @@ public final class OpenOffer implements Tradable {
proto.getScheduledTxHashesList(), proto.getScheduledTxHashesList(),
ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()), ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()),
proto.getSplitOutputTxFee(), proto.getSplitOutputTxFee(),
proto.getReserveTxHash(), ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
proto.getReserveTxHex(), ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
proto.getReserveTxKey()); ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()));
return openOffer; return openOffer;
} }

View file

@ -130,6 +130,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30; private static final long REPUBLISH_AGAIN_AT_STARTUP_DELAY_SEC = 30;
private static final long REPUBLISH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30); private static final long REPUBLISH_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30);
private static final long REFRESH_INTERVAL_MS = OfferPayload.TTL / 2; private static final long REFRESH_INTERVAL_MS = OfferPayload.TTL / 2;
private static final int MAX_PROCESS_ATTEMPTS = 5;
private final CoreContext coreContext; private final CoreContext coreContext;
private final KeyRing keyRing; private final KeyRing keyRing;
@ -156,7 +157,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final SignedOfferList signedOffers = new SignedOfferList(); private final SignedOfferList signedOffers = new SignedOfferList();
private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager; private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager;
private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>(); private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>();
private BigInteger lastUnlockedBalance;
private boolean stopped; private boolean stopped;
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer; private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
@Getter @Getter
@ -471,17 +471,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.warn("Error processing unposted offers: " + errorMessage); log.warn("Error processing unposted offers: " + errorMessage);
}); });
// register to process unposted offers when unlocked balance increases // register to process unposted offers on new block
if (xmrWalletService.getWallet() != null) lastUnlockedBalance = xmrWalletService.getAvailableBalance();
xmrWalletService.addWalletListener(new MoneroWalletListener() { xmrWalletService.addWalletListener(new MoneroWalletListener() {
@Override @Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { public void onNewBlock(long height) {
if (lastUnlockedBalance == null || lastUnlockedBalance.compareTo(newUnlockedBalance) < 0) { processScheduledOffers((transaction) -> {}, (errorMessage) -> {
processScheduledOffers((transaction) -> {}, (errorMessage) -> { log.warn("Error processing unposted offers on new block {}: {}", height, errorMessage);
log.warn("Error processing unposted offers on new unlocked balance: " + errorMessage); // TODO: popup to notify user that offer did not post });
});
}
lastUnlockedBalance = newUnlockedBalance;
} }
}); });
@ -860,8 +856,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
processUnpostedOffer(openOffers, scheduledOffer, (transaction) -> { processUnpostedOffer(openOffers, scheduledOffer, (transaction) -> {
latch.countDown(); latch.countDown();
}, errorMessage -> { }, errorMessage -> {
log.warn("Error processing unposted offer {}: {}", scheduledOffer.getId(), errorMessage); log.warn("Error processing unposted offer, offerId={}, attempt={}/{}, error={}", scheduledOffer.getId(), scheduledOffer.getNumProcessingAttempts(), MAX_PROCESS_ATTEMPTS, errorMessage);
onCancelled(scheduledOffer); if (scheduledOffer.getNumProcessingAttempts() >= MAX_PROCESS_ATTEMPTS) {
log.warn("Offer canceled after {} attempts, offerId={}, error={}", scheduledOffer.getNumProcessingAttempts(), scheduledOffer.getId(), errorMessage);
HavenoUtils.havenoSetup.getTopErrorMsg().set("Offer canceled after " + scheduledOffer.getNumProcessingAttempts() + " attempts. Please switch to a better Monero connection and try again.\n\nOffer ID: " + scheduledOffer.getId() + "\nError: " + errorMessage);
onCancelled(scheduledOffer);
}
errorMessages.add(errorMessage); errorMessages.add(errorMessage);
latch.countDown(); latch.countDown();
}); });
@ -875,6 +875,26 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
private void processUnpostedOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { private void processUnpostedOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
// skip if already processing
if (openOffer.isProcessing()) {
resultHandler.handleResult(null);
return;
}
// process offer
openOffer.setProcessing(true);
doProcessUnpostedOffer(openOffers, openOffer, (transaction) -> {
openOffer.setProcessing(false);
resultHandler.handleResult(transaction);
}, (errorMsg) -> {
openOffer.setProcessing(false);
openOffer.setNumProcessingAttempts(openOffer.getNumProcessingAttempts() + 1);
errorMessageHandler.handleErrorMessage(errorMsg);
});
}
private void doProcessUnpostedOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
new Thread(() -> { new Thread(() -> {
try { try {

View file

@ -36,7 +36,6 @@ import haveno.network.p2p.P2PService;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTxWallet;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import java.math.BigInteger; import java.math.BigInteger;
@ -71,8 +70,6 @@ public class PlaceOfferModel implements Model {
@Setter @Setter
private Transaction transaction; private Transaction transaction;
@Setter @Setter
private MoneroTxWallet reserveTx;
@Setter
private SignOfferResponse signOfferResponse; private SignOfferResponse signOfferResponse;
@Setter @Setter
@Getter @Getter

View file

@ -51,6 +51,13 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
try { try {
runInterceptHook(); runInterceptHook();
// skip if reserve tx already created
if (openOffer.getReserveTxHash() != null && !openOffer.getReserveTxHash().isEmpty()) {
log.info("Reserve tx already created for offerId={}", openOffer.getShortId());
complete();
return;
}
// verify monero connection // verify monero connection
model.getXmrWalletService().getConnectionService().verifyConnection(); model.getXmrWalletService().getConnectionService().verifyConnection();
@ -102,7 +109,6 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
openOffer.setReserveTxHex(reserveTx.getFullHex()); openOffer.setReserveTxHex(reserveTx.getFullHex());
openOffer.setReserveTxKey(reserveTx.getKey()); openOffer.setReserveTxKey(reserveTx.getKey());
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages); offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
model.setReserveTx(reserveTx);
} }
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View file

@ -24,6 +24,7 @@ import haveno.common.handlers.ResultHandler;
import haveno.common.taskrunner.Task; import haveno.common.taskrunner.Task;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.core.offer.Offer; import haveno.core.offer.Offer;
import haveno.core.offer.OpenOffer;
import haveno.core.offer.availability.DisputeAgentSelection; import haveno.core.offer.availability.DisputeAgentSelection;
import haveno.core.offer.messages.SignOfferRequest; import haveno.core.offer.messages.SignOfferRequest;
import haveno.core.offer.placeoffer.PlaceOfferModel; import haveno.core.offer.placeoffer.PlaceOfferModel;
@ -47,8 +48,6 @@ import java.util.UUID;
public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> { public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(MakerSendSignOfferRequest.class); private static final Logger log = LoggerFactory.getLogger(MakerSendSignOfferRequest.class);
private boolean failed = false;
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public MakerSendSignOfferRequest(TaskRunner taskHandler, PlaceOfferModel model) { public MakerSendSignOfferRequest(TaskRunner taskHandler, PlaceOfferModel model) {
super(taskHandler, model); super(taskHandler, model);
@ -56,7 +55,8 @@ public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> {
@Override @Override
protected void run() { protected void run() {
Offer offer = model.getOpenOffer().getOffer(); OpenOffer openOffer = model.getOpenOffer();
Offer offer = openOffer.getOffer();
try { try {
runInterceptHook(); runInterceptHook();
@ -71,9 +71,9 @@ public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> {
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
new Date().getTime(), new Date().getTime(),
model.getReserveTx().getHash(), openOffer.getReserveTxHash(),
model.getReserveTx().getFullHex(), openOffer.getReserveTxHex(),
model.getReserveTx().getKey(), openOffer.getReserveTxKey(),
offer.getOfferPayload().getReserveTxKeyImages(), offer.getOfferPayload().getReserveTxKeyImages(),
returnAddress); returnAddress);
@ -81,8 +81,7 @@ public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> {
sendSignOfferRequests(request, () -> { sendSignOfferRequests(request, () -> {
complete(); complete();
}, (errorMessage) -> { }, (errorMessage) -> {
appendToErrorMessage("Error signing offer " + request.getOfferId() + ": " + errorMessage); failed("Error signing offer " + request.getOfferId() + ": " + errorMessage);
failed(errorMessage);
}); });
} catch (Throwable t) { } catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" + offer.setErrorMessage("An error occurred.\n" +