diff --git a/core/src/main/java/haveno/core/offer/OpenOffer.java b/core/src/main/java/haveno/core/offer/OpenOffer.java index a3ae9a18a6..bf45657e20 100644 --- a/core/src/main/java/haveno/core/offer/OpenOffer.java +++ b/core/src/main/java/haveno/core/offer/OpenOffer.java @@ -103,6 +103,12 @@ public final class OpenOffer implements Tradable { @Setter transient private long mempoolStatus = -1; transient final private ObjectProperty 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; } diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index a549655294..d6ce814788 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -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 signedOfferPersistenceManager; private final Map placeOfferProtocols = new HashMap(); - 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 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 openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { new Thread(() -> { try { diff --git a/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferModel.java b/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferModel.java index 0e289aa564..65aed8aa9d 100644 --- a/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferModel.java @@ -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 diff --git a/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java index 5e168edcf4..bda7ba99fc 100644 --- a/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java +++ b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerReserveOfferFunds.java @@ -51,6 +51,13 @@ public class MakerReserveOfferFunds extends Task { 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 { openOffer.setReserveTxHex(reserveTx.getFullHex()); openOffer.setReserveTxKey(reserveTx.getKey()); offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages); - model.setReserveTx(reserveTx); } complete(); } catch (Throwable t) { diff --git a/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerSendSignOfferRequest.java b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerSendSignOfferRequest.java index 618945e07e..8f08b77685 100644 --- a/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerSendSignOfferRequest.java +++ b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MakerSendSignOfferRequest.java @@ -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 { 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 { @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 { 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 { 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" +