re-sign offers on edit if applicable

This commit is contained in:
woodser 2025-03-28 15:45:37 -04:00 committed by woodser
parent 93369c4211
commit dc43e1c329
6 changed files with 153 additions and 133 deletions

View file

@ -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_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 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 CoreContext coreContext;
private final KeyRing keyRing; private final KeyRing keyRing;
@ -475,19 +475,19 @@ 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 pending offers // processs offers
processPendingOffers(false, (transaction) -> {}, (errorMessage) -> { processOffers(false, (transaction) -> {}, (errorMessage) -> {
log.warn("Error processing pending offers on bootstrap: " + 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() { xmrWalletService.addWalletListener(new MoneroWalletListener() {
@Override @Override
public void onNewBlock(long height) { public void onNewBlock(long height) {
// process each pending offer on new block a few times, then rely on period republish // process each offer on new block a few times, then rely on period republish
processPendingOffers(true, (transaction) -> {}, (errorMessage) -> { processOffers(true, (transaction) -> {}, (errorMessage) -> {
log.warn("Error processing pending offers on new block {}: {}", height, errorMessage); log.warn("Error processing offers on new block {}: {}", height, errorMessage);
}); });
} }
}); });
@ -555,13 +555,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
synchronized (processOffersLock) { synchronized (processOffersLock) {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
addOpenOffer(openOffer); addOpenOffer(openOffer);
processPendingOffer(getOpenOffers(), openOffer, (transaction) -> { processOffer(getOpenOffers(), openOffer, (transaction) -> {
requestPersistence(); requestPersistence();
latch.countDown(); latch.countDown();
resultHandler.handleResult(transaction); resultHandler.handleResult(transaction);
}, (errorMessage) -> { }, (errorMessage) -> {
if (!openOffer.isCanceled()) { if (!openOffer.isCanceled()) {
log.warn("Error processing pending offer {}: {}", openOffer.getId(), errorMessage); log.warn("Error processing offer {}: {}", openOffer.getId(), errorMessage);
doCancelOffer(openOffer, resetAddressEntriesOnError); doCancelOffer(openOffer, resetAddressEntriesOnError);
} }
latch.countDown(); latch.countDown();
@ -578,8 +578,9 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (openOfferOptional.isPresent()) { if (openOfferOptional.isPresent()) {
cancelOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler); cancelOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler);
} else { } else {
log.warn("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.";
errorMessageHandler.handleErrorMessage("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); offerBookService.removeOffer(offer.getOfferPayload(), () -> offer.setState(Offer.State.REMOVED), null);
} }
} }
@ -706,12 +707,21 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
addOpenOffer(editedOpenOffer); addOpenOffer(editedOpenOffer);
if (editedOpenOffer.isAvailable()) // reset arbitrator signature if invalid
maybeRepublishOffer(editedOpenOffer); 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()); // process offer which might sign and publish
requestPersistence(); processOffer(getOpenOffers(), editedOpenOffer, (transaction) -> {
resultHandler.handleResult(); offersToBeEdited.remove(openOffer.getId());
requestPersistence();
resultHandler.handleResult();
}, (errorMsg) -> {
errorMessageHandler.handleErrorMessage(errorMsg);
});
} else { } else {
errorMessageHandler.handleErrorMessage("There is no offer with this id existing to be published."); 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 { } else {
resultHandler.handleResult(); resultHandler.handleResult();
} }
requestPersistence();
} else { } else {
errorMessageHandler.handleErrorMessage("Editing of offer can't be canceled as it is not edited."); 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 // Place offer helpers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void processPendingOffers(boolean skipOffersWithTooManyAttempts, private void processOffers(boolean skipOffersWithTooManyAttempts,
TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler TransactionResultHandler resultHandler, // TODO (woodser): transaction not needed with result handler
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
ThreadUtils.execute(() -> { ThreadUtils.execute(() -> {
@ -890,23 +901,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
synchronized (processOffersLock) { synchronized (processOffersLock) {
List<OpenOffer> openOffers = getOpenOffers(); List<OpenOffer> openOffers = getOpenOffers();
removeOffersWithDuplicateKeyImages(openOffers); removeOffersWithDuplicateKeyImages(openOffers);
for (OpenOffer pendingOffer : openOffers) { for (OpenOffer offer : openOffers) {
if (pendingOffer.getState() != OpenOffer.State.PENDING) continue; if (skipOffersWithTooManyAttempts && offer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts
if (skipOffersWithTooManyAttempts && pendingOffer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
processPendingOffer(openOffers, pendingOffer, (transaction) -> { processOffer(openOffers, offer, (transaction) -> {
latch.countDown(); latch.countDown();
}, errorMessage -> { }, errorMessage -> {
if (!pendingOffer.isCanceled()) { errorMessages.add(errorMessage);
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);
}
}
latch.countDown(); latch.countDown();
}); });
HavenoUtils.awaitLatch(latch); HavenoUtils.awaitLatch(latch);
@ -943,7 +944,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
} }
private void processPendingOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { private void processOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
// skip if already processing // skip if already processing
if (openOffer.isProcessing()) { if (openOffer.isProcessing()) {
@ -953,23 +954,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// process offer // process offer
openOffer.setProcessing(true); openOffer.setProcessing(true);
doProcessPendingOffer(openOffers, openOffer, (transaction) -> { doProcessOffer(openOffers, openOffer, (transaction) -> {
openOffer.setProcessing(false); openOffer.setProcessing(false);
resultHandler.handleResult(transaction); resultHandler.handleResult(transaction);
}, (errorMsg) -> { }, (errorMsg) -> {
openOffer.setProcessing(false); openOffer.setProcessing(false);
openOffer.setNumProcessingAttempts(openOffer.getNumProcessingAttempts() + 1); openOffer.setNumProcessingAttempts(openOffer.getNumProcessingAttempts() + 1);
openOffer.getOffer().setErrorMessage(errorMsg); 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); errorMessageHandler.handleErrorMessage(errorMsg);
}); });
} }
private void doProcessPendingOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { private void doProcessOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
new Thread(() -> { new Thread(() -> {
try { try {
// done processing if wallet not initialized // done processing if canceled or wallet not initialized
if (xmrWalletService.getWallet() == null) { if (openOffer.isCanceled() || xmrWalletService.getWallet() == null) {
resultHandler.handleResult(null); resultHandler.handleResult(null);
return; return;
} }
@ -982,6 +993,33 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return; 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 // cancel offer if scheduled txs unavailable
if (openOffer.getScheduledTxHashes() != null) { if (openOffer.getScheduledTxHashes() != null) {
boolean scheduledTxsAvailable = true; 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 // get amount needed to reserve offer
BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded(); BigInteger amountNeeded = openOffer.getOffer().getAmountNeeded();
@ -1020,13 +1064,11 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} else { } else {
splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler); splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler);
} }
} else { } else {
// sign and post offer if enough funds // sign and post offer if enough funds
boolean hasFundsReserved = openOffer.getReserveTxHash() != null;
boolean hasSufficientBalance = xmrWalletService.getAvailableBalance().compareTo(amountNeeded) >= 0; boolean hasSufficientBalance = xmrWalletService.getAvailableBalance().compareTo(amountNeeded) >= 0;
if (hasFundsReserved || hasSufficientBalance) { if (hasSufficientBalance) {
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler); signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
return; return;
} else if (openOffer.getScheduledTxHashes() == null) { } else if (openOffer.getScheduledTxHashes() == null) {
@ -1036,7 +1078,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
} }
} catch (Exception e) { } 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()); errorMessageHandler.handleErrorMessage(e.getMessage());
} }
}).start(); }).start();
@ -1335,7 +1377,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
transaction -> { transaction -> {
// set offer state // set offer state
openOffer.setState(OpenOffer.State.AVAILABLE);
openOffer.setScheduledTxHashes(null); openOffer.setScheduledTxHashes(null);
openOffer.setScheduledAmount(null); openOffer.setScheduledAmount(null);
requestPersistence(); 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) { private void maybeRepublishOffer(OpenOffer openOffer, @Nullable Runnable completeHandler) {
ThreadUtils.execute(() -> { ThreadUtils.execute(() -> {
@ -1962,76 +1999,48 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return; return;
} }
// determine if offer is valid // reprocess offer then publish
boolean isValid = true; synchronized (processOffersLock) {
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(openOffer.getOffer().getOfferPayload().getArbitratorSigner()); CountDownLatch latch = new CountDownLatch(1);
if (arbitrator == null) { processOffer(getOpenOffers(), openOffer, (transaction) -> {
log.warn("Offer {} signed by unavailable arbitrator, reposting", openOffer.getId()); requestPersistence();
isValid = false; latch.countDown();
} 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 valid, re-add offer to book // skip if prevented from publishing
if (isValid) { if (preventedFromPublishing(openOffer)) {
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();
if (completeHandler != null) completeHandler.run(); if (completeHandler != null) completeHandler.run();
}, (errorMessage) -> { return;
if (!openOffer.isCanceled()) { }
log.warn("Error republishing offer {}: {}", openOffer.getId(), errorMessage);
openOffer.getOffer().setErrorMessage(errorMessage); // publish offer to books
offerBookService.addOffer(openOffer.getOffer(),
() -> {
if (!stopped) {
// cancel offer if invalid // refresh means we send only the data needed to refresh the TTL (hash, signature and sequence no.)
if (openOffer.getOffer().getState() == Offer.State.INVALID) { if (periodicRefreshOffersTimer == null) {
log.warn("Canceling offer because it's invalid: {}", openOffer.getId()); startPeriodicRefreshOffersTimer();
doCancelOffer(openOffer); }
} if (completeHandler != null) {
} completeHandler.run();
latch.countDown(); }
if (completeHandler != null) completeHandler.run(); }
}); },
HavenoUtils.awaitLatch(latch); 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); }, THREAD_ID);
} }

View file

@ -23,7 +23,7 @@ import haveno.common.handlers.ErrorMessageHandler;
import haveno.common.taskrunner.TaskRunner; import haveno.common.taskrunner.TaskRunner;
import haveno.core.locale.Res; import haveno.core.locale.Res;
import haveno.core.offer.messages.SignOfferResponse; 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.MakerProcessSignOfferResponse;
import haveno.core.offer.placeoffer.tasks.MakerReserveOfferFunds; import haveno.core.offer.placeoffer.tasks.MakerReserveOfferFunds;
import haveno.core.offer.placeoffer.tasks.MakerSendSignOfferRequest; import haveno.core.offer.placeoffer.tasks.MakerSendSignOfferRequest;
@ -135,7 +135,7 @@ public class PlaceOfferProtocol {
); );
taskRunner.addTasks( taskRunner.addTasks(
MakerProcessSignOfferResponse.class, MakerProcessSignOfferResponse.class,
AddToOfferBook.class MaybeAddToOfferBook.class
); );
taskRunner.run(); taskRunner.run();

View file

@ -20,13 +20,14 @@ package haveno.core.offer.placeoffer.tasks;
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.placeoffer.PlaceOfferModel; import haveno.core.offer.placeoffer.PlaceOfferModel;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class AddToOfferBook extends Task<PlaceOfferModel> { public class MaybeAddToOfferBook extends Task<PlaceOfferModel> {
public AddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) { public MaybeAddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
super(taskHandler, model); super(taskHandler, model);
} }
@ -35,17 +36,22 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
try { try {
runInterceptHook(); runInterceptHook();
checkNotNull(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature(), "Offer's arbitrator signature is null: " + model.getOpenOffer().getOffer().getId()); checkNotNull(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature(), "Offer's arbitrator signature is null: " + model.getOpenOffer().getOffer().getId());
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), if (model.getOpenOffer().isPending() || model.getOpenOffer().isAvailable()) {
() -> { model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
model.setOfferAddedToOfferBook(true); () -> {
complete(); model.getOpenOffer().setState(OpenOffer.State.AVAILABLE);
}, model.setOfferAddedToOfferBook(true);
errorMessage -> { complete();
model.getOpenOffer().getOffer().setErrorMessage("Could not add offer to offerbook.\n" + },
"Please check your network connection and try again."); errorMessage -> {
model.getOpenOffer().getOffer().setErrorMessage("Could not add offer to offerbook.\n" +
failed(errorMessage); "Please check your network connection and try again.");
}); failed(errorMessage);
});
} else {
complete();
return;
}
} catch (Throwable t) { } catch (Throwable t) {
model.getOpenOffer().getOffer().setErrorMessage("An error occurred.\n" + model.getOpenOffer().getOffer().setErrorMessage("An error occurred.\n" +
"Error message:\n" "Error message:\n"

View file

@ -22,7 +22,7 @@ import haveno.common.taskrunner.Task;
import haveno.common.util.Tuple2; import haveno.common.util.Tuple2;
import haveno.core.offer.availability.tasks.ProcessOfferAvailabilityResponse; import haveno.core.offer.availability.tasks.ProcessOfferAvailabilityResponse;
import haveno.core.offer.availability.tasks.SendOfferAvailabilityRequest; 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.MakerReserveOfferFunds;
import haveno.core.offer.placeoffer.tasks.ValidateOffer; import haveno.core.offer.placeoffer.tasks.ValidateOffer;
import haveno.core.trade.protocol.tasks.ApplyFilter; import haveno.core.trade.protocol.tasks.ApplyFilter;
@ -72,7 +72,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList( FXCollections.observableArrayList(Arrays.asList(
ValidateOffer.class, ValidateOffer.class,
MakerReserveOfferFunds.class, MakerReserveOfferFunds.class,
AddToOfferBook.class) MaybeAddToOfferBook.class)
)); ));

View file

@ -20,6 +20,8 @@ package haveno.desktop.main.portfolio.editoffer;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.name.Named; import com.google.inject.name.Named;
import haveno.common.UserThread;
import haveno.common.handlers.ErrorMessageHandler; import haveno.common.handlers.ErrorMessageHandler;
import haveno.common.handlers.ResultHandler; import haveno.common.handlers.ResultHandler;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
@ -226,8 +228,10 @@ class EditOfferDataModel extends MutableOfferDataModel {
openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> { openOfferManager.editOpenOfferPublish(editedOffer, triggerPrice, initialState, () -> {
openOffer = null; openOffer = null;
resultHandler.handleResult(); UserThread.execute(() -> resultHandler.handleResult());
}, errorMessageHandler); }, (errorMsg) -> {
UserThread.execute(() -> errorMessageHandler.handleErrorMessage(errorMsg));
});
} }
public void onCancelEditOffer(ErrorMessageHandler errorMessageHandler) { public void onCancelEditOffer(ErrorMessageHandler errorMessageHandler) {

View file

@ -205,7 +205,8 @@ public class EditOfferView extends MutableOfferView<EditOfferViewModel> {
cancelButton.setDisable(true); cancelButton.setDisable(true);
busyAnimation.play(); busyAnimation.play();
spinnerInfoLabel.setText(Res.get("editOffer.publishOffer")); spinnerInfoLabel.setText(Res.get("editOffer.publishOffer"));
//edit offer
// edit offer
model.onPublishOffer(() -> { model.onPublishOffer(() -> {
String key = "editOfferSuccess"; String key = "editOfferSuccess";
if (DontShowAgainLookup.showAgain(key)) { if (DontShowAgainLookup.showAgain(key)) {