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
transient private long mempoolStatus = -1;
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) {
this(offer, 0, false);
@ -193,9 +199,9 @@ public final class OpenOffer implements Tradable {
proto.getScheduledTxHashesList(),
ProtoUtil.stringOrNullFromProto(proto.getSplitOutputTxHash()),
proto.getSplitOutputTxFee(),
proto.getReserveTxHash(),
proto.getReserveTxHex(),
proto.getReserveTxKey());
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()),
ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()),
ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()));
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_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30);
private static final long REFRESH_INTERVAL_MS = OfferPayload.TTL / 2;
private static final int MAX_PROCESS_ATTEMPTS = 5;
private final CoreContext coreContext;
private final KeyRing keyRing;
@ -156,7 +157,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final SignedOfferList signedOffers = new SignedOfferList();
private final PersistenceManager<SignedOfferList> signedOfferPersistenceManager;
private final Map<String, PlaceOfferProtocol> placeOfferProtocols = new HashMap<String, PlaceOfferProtocol>();
private BigInteger lastUnlockedBalance;
private boolean stopped;
private Timer periodicRepublishOffersTimer, periodicRefreshOffersTimer, retryRepublishOffersTimer;
@Getter
@ -471,17 +471,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
log.warn("Error processing unposted offers: " + errorMessage);
});
// register to process unposted offers when unlocked balance increases
if (xmrWalletService.getWallet() != null) lastUnlockedBalance = xmrWalletService.getAvailableBalance();
// register to process unposted offers on new block
xmrWalletService.addWalletListener(new MoneroWalletListener() {
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
if (lastUnlockedBalance == null || lastUnlockedBalance.compareTo(newUnlockedBalance) < 0) {
processScheduledOffers((transaction) -> {}, (errorMessage) -> {
log.warn("Error processing unposted offers on new unlocked balance: " + errorMessage); // TODO: popup to notify user that offer did not post
});
}
lastUnlockedBalance = newUnlockedBalance;
public void onNewBlock(long height) {
processScheduledOffers((transaction) -> {}, (errorMessage) -> {
log.warn("Error processing unposted offers on new block {}: {}", height, errorMessage);
});
}
});
@ -860,8 +856,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
processUnpostedOffer(openOffers, scheduledOffer, (transaction) -> {
latch.countDown();
}, errorMessage -> {
log.warn("Error processing unposted offer {}: {}", scheduledOffer.getId(), errorMessage);
onCancelled(scheduledOffer);
log.warn("Error processing unposted offer, offerId={}, attempt={}/{}, error={}", scheduledOffer.getId(), scheduledOffer.getNumProcessingAttempts(), MAX_PROCESS_ATTEMPTS, errorMessage);
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);
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) {
// 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(() -> {
try {

View File

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

View File

@ -51,6 +51,13 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
try {
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
model.getXmrWalletService().getConnectionService().verifyConnection();
@ -102,7 +109,6 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
openOffer.setReserveTxHex(reserveTx.getFullHex());
openOffer.setReserveTxKey(reserveTx.getKey());
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages);
model.setReserveTx(reserveTx);
}
complete();
} catch (Throwable t) {

View File

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