From dc43e1c329fbbd83f96279f3361311fc6c12954a Mon Sep 17 00:00:00 2001 From: woodser <13068859+woodser@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:45:37 -0400 Subject: [PATCH] re-sign offers on edit if applicable --- .../haveno/core/offer/OpenOfferManager.java | 235 +++++++++--------- .../offer/placeoffer/PlaceOfferProtocol.java | 4 +- ...fferBook.java => MaybeAddToOfferBook.java} | 32 ++- .../haveno/desktop/main/debug/DebugView.java | 4 +- .../editoffer/EditOfferDataModel.java | 8 +- .../portfolio/editoffer/EditOfferView.java | 3 +- 6 files changed, 153 insertions(+), 133 deletions(-) rename core/src/main/java/haveno/core/offer/placeoffer/tasks/{AddToOfferBook.java => MaybeAddToOfferBook.java} (58%) diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java index a08b767a47..e68f90e484 100644 --- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java +++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java @@ -135,7 +135,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 NUM_ATTEMPTS_THRESHOLD = 5; // process pending offer only on republish cycle after this many attempts + private static final int NUM_ATTEMPTS_THRESHOLD = 5; // process offer only on republish cycle after this many attempts private final CoreContext coreContext; private final KeyRing keyRing; @@ -475,19 +475,19 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // .forEach(openOffer -> OfferUtil.getInvalidMakerFeeTxErrorMessage(openOffer.getOffer(), btcWalletService) // .ifPresent(errorMsg -> invalidOffers.add(new Tuple2<>(openOffer, errorMsg)))); - // process pending offers - processPendingOffers(false, (transaction) -> {}, (errorMessage) -> { - log.warn("Error processing pending offers on bootstrap: " + errorMessage); + // processs offers + processOffers(false, (transaction) -> {}, (errorMessage) -> { + log.warn("Error processing offers on bootstrap: " + errorMessage); }); - // register to process pending offers on new block + // register to process offers on new block xmrWalletService.addWalletListener(new MoneroWalletListener() { @Override public void onNewBlock(long height) { - // process each pending offer on new block a few times, then rely on period republish - processPendingOffers(true, (transaction) -> {}, (errorMessage) -> { - log.warn("Error processing pending offers on new block {}: {}", height, errorMessage); + // process each offer on new block a few times, then rely on period republish + processOffers(true, (transaction) -> {}, (errorMessage) -> { + log.warn("Error processing offers on new block {}: {}", height, errorMessage); }); } }); @@ -555,13 +555,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe synchronized (processOffersLock) { CountDownLatch latch = new CountDownLatch(1); addOpenOffer(openOffer); - processPendingOffer(getOpenOffers(), openOffer, (transaction) -> { + processOffer(getOpenOffers(), openOffer, (transaction) -> { requestPersistence(); latch.countDown(); resultHandler.handleResult(transaction); }, (errorMessage) -> { if (!openOffer.isCanceled()) { - log.warn("Error processing pending offer {}: {}", openOffer.getId(), errorMessage); + log.warn("Error processing offer {}: {}", openOffer.getId(), errorMessage); doCancelOffer(openOffer, resetAddressEntriesOnError); } latch.countDown(); @@ -578,8 +578,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe if (openOfferOptional.isPresent()) { cancelOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler); } else { - log.warn("Offer was not found in our list of open offers. We still try to remove it from the offerbook."); - errorMessageHandler.handleErrorMessage("Offer was not found in our list of open offers. " + "We still try to remove it from the offerbook."); + String errorMsg = "Offer was not found in our list of open offers. We still try to remove it from the offerbook."; + log.warn(errorMsg); + errorMessageHandler.handleErrorMessage(errorMsg); offerBookService.removeOffer(offer.getOfferPayload(), () -> offer.setState(Offer.State.REMOVED), null); } } @@ -706,12 +707,21 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe addOpenOffer(editedOpenOffer); - if (editedOpenOffer.isAvailable()) - maybeRepublishOffer(editedOpenOffer); + // reset arbitrator signature if invalid + Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(editedOpenOffer.getOffer().getOfferPayload().getArbitratorSigner()); + if (arbitrator == null || !HavenoUtils.isArbitratorSignatureValid(editedOpenOffer.getOffer().getOfferPayload(), arbitrator)) { + editedOpenOffer.getOffer().getOfferPayload().setArbitratorSignature(null); + editedOpenOffer.getOffer().getOfferPayload().setArbitratorSigner(null); + } - offersToBeEdited.remove(openOffer.getId()); - requestPersistence(); - resultHandler.handleResult(); + // process offer which might sign and publish + processOffer(getOpenOffers(), editedOpenOffer, (transaction) -> { + offersToBeEdited.remove(openOffer.getId()); + requestPersistence(); + resultHandler.handleResult(); + }, (errorMsg) -> { + errorMessageHandler.handleErrorMessage(errorMsg); + }); } else { errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published."); } @@ -728,6 +738,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } else { resultHandler.handleResult(); } + requestPersistence(); } else { errorMessageHandler.handleErrorMessage("Editing of offer can't be canceled as it is not edited."); } @@ -882,7 +893,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe /////////////////////////////////////////////////////////////////////////////////////////// // Place offer helpers /////////////////////////////////////////////////////////////////////////////////////////// - private void processPendingOffers(boolean skipOffersWithTooManyAttempts, + private void processOffers(boolean skipOffersWithTooManyAttempts, TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler ErrorMessageHandler errorMessageHandler) { ThreadUtils.execute(() -> { @@ -890,23 +901,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe synchronized (processOffersLock) { List openOffers = getOpenOffers(); removeOffersWithDuplicateKeyImages(openOffers); - for (OpenOffer pendingOffer : openOffers) { - if (pendingOffer.getState() != OpenOffer.State.PENDING) continue; - if (skipOffersWithTooManyAttempts && pendingOffer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts + for (OpenOffer offer : openOffers) { + if (skipOffersWithTooManyAttempts && offer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts CountDownLatch latch = new CountDownLatch(1); - processPendingOffer(openOffers, pendingOffer, (transaction) -> { + processOffer(openOffers, offer, (transaction) -> { latch.countDown(); }, errorMessage -> { - if (!pendingOffer.isCanceled()) { - String warnMessage = "Error processing pending offer, offerId=" + pendingOffer.getId() + ", attempt=" + pendingOffer.getNumProcessingAttempts() + ": " + errorMessage; - errorMessages.add(warnMessage); - - // cancel offer if invalid - if (pendingOffer.getOffer().getState() == Offer.State.INVALID) { - log.warn("Canceling offer because it's invalid: {}", pendingOffer.getId()); - doCancelOffer(pendingOffer); - } - } + errorMessages.add(errorMessage); latch.countDown(); }); HavenoUtils.awaitLatch(latch); @@ -943,7 +944,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } - private void processPendingOffer(List openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + private void processOffer(List openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { // skip if already processing if (openOffer.isProcessing()) { @@ -953,23 +954,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // process offer openOffer.setProcessing(true); - doProcessPendingOffer(openOffers, openOffer, (transaction) -> { + doProcessOffer(openOffers, openOffer, (transaction) -> { openOffer.setProcessing(false); resultHandler.handleResult(transaction); }, (errorMsg) -> { openOffer.setProcessing(false); openOffer.setNumProcessingAttempts(openOffer.getNumProcessingAttempts() + 1); openOffer.getOffer().setErrorMessage(errorMsg); + if (!openOffer.isCanceled()) { + errorMsg = "Error processing offer, offerId=" + openOffer.getId() + ", attempt=" + openOffer.getNumProcessingAttempts() + ": " + errorMsg; + openOffer.getOffer().setErrorMessage(errorMsg); + + // cancel offer if invalid + if (openOffer.getOffer().getState() == Offer.State.INVALID) { + log.warn("Canceling offer because it's invalid: {}", openOffer.getId()); + doCancelOffer(openOffer); + } + } errorMessageHandler.handleErrorMessage(errorMsg); }); } - private void doProcessPendingOffer(List openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + private void doProcessOffer(List openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { new Thread(() -> { try { - // done processing if wallet not initialized - if (xmrWalletService.getWallet() == null) { + // done processing if canceled or wallet not initialized + if (openOffer.isCanceled() || xmrWalletService.getWallet() == null) { resultHandler.handleResult(null); return; } @@ -982,6 +993,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe return; } + // validate non-pending state + if (!openOffer.isPending()) { + boolean isValid = true; + Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(openOffer.getOffer().getOfferPayload().getArbitratorSigner()); + if (openOffer.getOffer().getOfferPayload().getArbitratorSigner() == null) { + isValid = false; + } else if (arbitrator == null) { + log.warn("Offer {} signed by unavailable arbitrator, reposting", openOffer.getId()); + isValid = false; + } else if (!HavenoUtils.isArbitratorSignatureValid(openOffer.getOffer().getOfferPayload(), arbitrator)) { + log.warn("Offer {} has invalid arbitrator signature, reposting", openOffer.getId()); + isValid = false; + } + if ((openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() != null || openOffer.getOffer().getOfferPayload().getReserveTxKeyImages().isEmpty()) && (openOffer.getReserveTxHash() == null || openOffer.getReserveTxHash().isEmpty())) { + log.warn("Offer {} is missing reserve tx hash but has reserved key images, reposting", openOffer.getId()); + isValid = false; + } + if (isValid) { + resultHandler.handleResult(null); + return; + } else { + openOffer.getOffer().getOfferPayload().setArbitratorSignature(null); + openOffer.getOffer().getOfferPayload().setArbitratorSigner(null); + if (openOffer.isAvailable()) openOffer.setState(OpenOffer.State.PENDING); + } + } + // cancel offer if scheduled txs unavailable if (openOffer.getScheduledTxHashes() != null) { boolean scheduledTxsAvailable = true; @@ -999,6 +1037,12 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } + // sign and post offer if already funded + if (openOffer.getReserveTxHash() != null) { + signAndPostOffer(openOffer, false, resultHandler, errorMessageHandler); + return; + } + // get amount needed to reserve offer BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded(); @@ -1020,13 +1064,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } else { splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler); } - } else { // sign and post offer if enough funds - boolean hasFundsReserved = openOffer.getReserveTxHash() != null; boolean hasSufficientBalance = xmrWalletService.getAvailableBalance().compareTo(amountNeeded) >= 0; - if (hasFundsReserved || hasSufficientBalance) { + if (hasSufficientBalance) { signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler); return; } else if (openOffer.getScheduledTxHashes() == null) { @@ -1036,7 +1078,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } } catch (Exception e) { - if (!openOffer.isCanceled()) log.error("Error processing pending offer: {}\n", e.getMessage(), e); + if (!openOffer.isCanceled()) log.error("Error processing offer: {}\n", e.getMessage(), e); errorMessageHandler.handleErrorMessage(e.getMessage()); } }).start(); @@ -1335,7 +1377,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe transaction -> { // set offer state - openOffer.setState(OpenOffer.State.AVAILABLE); openOffer.setScheduledTxHashes(null); openOffer.setScheduledAmount(null); requestPersistence(); @@ -1949,10 +1990,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } } - private void maybeRepublishOffer(OpenOffer openOffer) { - maybeRepublishOffer(openOffer, null); - } - private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) { ThreadUtils.execute(() -> { @@ -1962,76 +1999,48 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe return; } - // determine if offer is valid - boolean isValid = true; - Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(openOffer.getOffer().getOfferPayload().getArbitratorSigner()); - if (arbitrator == null) { - log.warn("Offer {} signed by unavailable arbitrator, reposting", openOffer.getId()); - isValid = false; - } else if (!HavenoUtils.isArbitratorSignatureValid(openOffer.getOffer().getOfferPayload(), arbitrator)) { - log.warn("Offer {} has invalid arbitrator signature, reposting", openOffer.getId()); - isValid = false; - } - if ((openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() != null || openOffer.getOffer().getOfferPayload().getReserveTxKeyImages().isEmpty()) && (openOffer.getReserveTxHash() == null || openOffer.getReserveTxHash().isEmpty())) { - log.warn("Offer {} is missing reserve tx hash but has reserved key images, reposting", openOffer.getId()); - isValid = false; - } + // reprocess offer then publish + synchronized (processOffersLock) { + CountDownLatch latch = new CountDownLatch(1); + processOffer(getOpenOffers(), openOffer, (transaction) -> { + requestPersistence(); + latch.countDown(); - // if valid, re-add offer to book - if (isValid) { - offerBookService.addOffer(openOffer.getOffer(), - () -> { - if (!stopped) { - - // refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.) - if (periodicRefreshOffersTimer == null) { - startPeriodicRefreshOffersTimer(); - } - if (completeHandler != null) { - completeHandler.run(); - } - } - }, - errorMessage -> { - if (!stopped) { - log.error("Adding offer to P2P network failed. " + errorMessage); - stopRetryRepublishOffersTimer(); - retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers, - RETRY_REPUBLISH_DELAY_SEC); - if (completeHandler != null) completeHandler.run(); - } - }); - } else { - - // reset offer state to pending - openOffer.getOffer().getOfferPayload().setArbitratorSignature(null); - openOffer.getOffer().getOfferPayload().setArbitratorSigner(null); - openOffer.getOffer().setState(Offer.State.UNKNOWN); - openOffer.setState(OpenOffer.State.PENDING); - - // republish offer - synchronized (processOffersLock) { - CountDownLatch latch = new CountDownLatch(1); - processPendingOffer(getOpenOffers(), openOffer, (transaction) -> { - requestPersistence(); - latch.countDown(); + // skip if prevented from publishing + if (preventedFromPublishing(openOffer)) { if (completeHandler != null) completeHandler.run(); - }, (errorMessage) -> { - if (!openOffer.isCanceled()) { - log.warn("Error republishing offer {}: {}", openOffer.getId(), errorMessage); - openOffer.getOffer().setErrorMessage(errorMessage); + return; + } + + // publish offer to books + offerBookService.addOffer(openOffer.getOffer(), + () -> { + if (!stopped) { - // cancel offer if invalid - if (openOffer.getOffer().getState() == Offer.State.INVALID) { - log.warn("Canceling offer because it's invalid: {}", openOffer.getId()); - doCancelOffer(openOffer); - } - } - latch.countDown(); - if (completeHandler != null) completeHandler.run(); - }); - HavenoUtils.awaitLatch(latch); - } + // refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.) + if (periodicRefreshOffersTimer == null) { + startPeriodicRefreshOffersTimer(); + } + if (completeHandler != null) { + completeHandler.run(); + } + } + }, + errorMessage -> { + if (!stopped) { + log.error("Adding offer to P2P network failed. " + errorMessage); + stopRetryRepublishOffersTimer(); + retryRepublishOffersTimer = UserThread.runAfter(OpenOfferManager.this::republishOffers, + RETRY_REPUBLISH_DELAY_SEC); + if (completeHandler != null) completeHandler.run(); + } + }); + }, (errorMessage) -> { + log.warn("Error republishing offer {}: {}", openOffer.getId(), errorMessage); + latch.countDown(); + if (completeHandler != null) completeHandler.run(); + }); + HavenoUtils.awaitLatch(latch); } }, THREAD_ID); } diff --git a/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferProtocol.java b/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferProtocol.java index 0b22d9e40e..68d5f9da4f 100644 --- a/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferProtocol.java +++ b/core/src/main/java/haveno/core/offer/placeoffer/PlaceOfferProtocol.java @@ -23,7 +23,7 @@ import haveno.common.handlers.ErrorMessageHandler; import haveno.common.taskrunner.TaskRunner; import haveno.core.locale.Res; import haveno.core.offer.messages.SignOfferResponse; -import haveno.core.offer.placeoffer.tasks.AddToOfferBook; +import haveno.core.offer.placeoffer.tasks.MaybeAddToOfferBook; import haveno.core.offer.placeoffer.tasks.MakerProcessSignOfferResponse; import haveno.core.offer.placeoffer.tasks.MakerReserveOfferFunds; import haveno.core.offer.placeoffer.tasks.MakerSendSignOfferRequest; @@ -135,7 +135,7 @@ public class PlaceOfferProtocol { ); taskRunner.addTasks( MakerProcessSignOfferResponse.class, - AddToOfferBook.class + MaybeAddToOfferBook.class ); taskRunner.run(); diff --git a/core/src/main/java/haveno/core/offer/placeoffer/tasks/AddToOfferBook.java b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MaybeAddToOfferBook.java similarity index 58% rename from core/src/main/java/haveno/core/offer/placeoffer/tasks/AddToOfferBook.java rename to core/src/main/java/haveno/core/offer/placeoffer/tasks/MaybeAddToOfferBook.java index c5c3cf4f46..8e3e3c23bc 100644 --- a/core/src/main/java/haveno/core/offer/placeoffer/tasks/AddToOfferBook.java +++ b/core/src/main/java/haveno/core/offer/placeoffer/tasks/MaybeAddToOfferBook.java @@ -20,13 +20,14 @@ package haveno.core.offer.placeoffer.tasks; import haveno.common.taskrunner.Task; import haveno.common.taskrunner.TaskRunner; import haveno.core.offer.Offer; +import haveno.core.offer.OpenOffer; import haveno.core.offer.placeoffer.PlaceOfferModel; import static com.google.common.base.Preconditions.checkNotNull; -public class AddToOfferBook extends Task { +public class MaybeAddToOfferBook extends Task { - public AddToOfferBook(TaskRunner taskHandler, PlaceOfferModel model) { + public MaybeAddToOfferBook(TaskRunner taskHandler, PlaceOfferModel model) { super(taskHandler, model); } @@ -35,17 +36,22 @@ public class AddToOfferBook extends Task { try { runInterceptHook(); checkNotNull(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature(), "Offer's arbitrator signature is null: " + model.getOpenOffer().getOffer().getId()); - model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), - () -> { - model.setOfferAddedToOfferBook(true); - complete(); - }, - errorMessage -> { - model.getOpenOffer().getOffer().setErrorMessage("Could not add offer to offerbook.\n" + - "Please check your network connection and try again."); - - failed(errorMessage); - }); + if (model.getOpenOffer().isPending() || model.getOpenOffer().isAvailable()) { + model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), + () -> { + model.getOpenOffer().setState(OpenOffer.State.AVAILABLE); + model.setOfferAddedToOfferBook(true); + complete(); + }, + errorMessage -> { + model.getOpenOffer().getOffer().setErrorMessage("Could not add offer to offerbook.\n" + + "Please check your network connection and try again."); + failed(errorMessage); + }); + } else { + complete(); + return; + } } catch (Throwable t) { model.getOpenOffer().getOffer().setErrorMessage("An error occurred.\n" + "Error message:\n" diff --git a/desktop/src/main/java/haveno/desktop/main/debug/DebugView.java b/desktop/src/main/java/haveno/desktop/main/debug/DebugView.java index 00ab2680b5..f1427c16a2 100644 --- a/desktop/src/main/java/haveno/desktop/main/debug/DebugView.java +++ b/desktop/src/main/java/haveno/desktop/main/debug/DebugView.java @@ -22,7 +22,7 @@ import haveno.common.taskrunner.Task; import haveno.common.util.Tuple2; import haveno.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import haveno.core.offer.availability.tasks.SendOfferAvailabilityRequest; -import haveno.core.offer.placeoffer.tasks.AddToOfferBook; +import haveno.core.offer.placeoffer.tasks.MaybeAddToOfferBook; import haveno.core.offer.placeoffer.tasks.MakerReserveOfferFunds; import haveno.core.offer.placeoffer.tasks.ValidateOffer; import haveno.core.trade.protocol.tasks.ApplyFilter; @@ -72,7 +72,7 @@ public class DebugView extends InitializableView { FXCollections.observableArrayList(Arrays.asList( ValidateOffer.class, MakerReserveOfferFunds.class, - AddToOfferBook.class) + MaybeAddToOfferBook.class) )); diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferDataModel.java index b9cd38efb5..151a72c0d7 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -20,6 +20,8 @@ package haveno.desktop.main.portfolio.editoffer; import com.google.inject.Inject; import com.google.inject.name.Named; + +import haveno.common.UserThread; import haveno.common.handlers.ErrorMessageHandler; import haveno.common.handlers.ResultHandler; import haveno.core.account.witness.AccountAgeWitnessService; @@ -226,8 +228,10 @@ class EditOfferDataModel extends MutableOfferDataModel { openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> { openOffer = null; - resultHandler.handleResult(); - }, errorMessageHandler); + UserThread.execute(() -> resultHandler.handleResult()); + }, (errorMsg) -> { + UserThread.execute(() -> errorMessageHandler.handleErrorMessage(errorMsg)); + }); } public void onCancelEditOffer(ErrorMessageHandler errorMessageHandler) { diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferView.java index 3752ab9dcb..bc804b5576 100644 --- a/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferView.java +++ b/desktop/src/main/java/haveno/desktop/main/portfolio/editoffer/EditOfferView.java @@ -205,7 +205,8 @@ public class EditOfferView extends MutableOfferView { cancelButton.setDisable(true); busyAnimation.play(); spinnerInfoLabel.setText(Res.get("editOffer.publishOffer")); - //edit offer + + // edit offer model.onPublishOffer(() -> { String key = "editOfferSuccess"; if (DontShowAgainLookup.showAgain(key)) {