Merge branch 'haveno-dex:master' into install_whonix_qubes

This commit is contained in:
PromptPunksFauxCough 2025-03-16 02:55:09 +00:00 committed by GitHub
commit 281264990b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 383 additions and 234 deletions
build.gradle
common/src/main/java/haveno/common/app
core/src/main/java/haveno/core
daemon/src/main/java/haveno/daemon/grpc
desktop
docs
gradle
proto/src/main/proto
seednode/src/main/java/haveno/seednode

View file

@ -71,7 +71,7 @@ configure(subprojects) {
loggingVersion = '1.2'
lombokVersion = '1.18.30'
mockitoVersion = '5.10.0'
netlayerVersion = '700ec94f0f' // Tor browser version 14.0.3 and tor binary version: 0.4.8.13
netlayerVersion = 'd4f9d0ce24' // Tor browser version 14.0.7 and tor binary version: 0.4.8.14
protobufVersion = '3.19.1'
protocVersion = protobufVersion
pushyVersion = '0.13.2'
@ -610,7 +610,7 @@ configure(project(':desktop')) {
apply plugin: 'com.github.johnrengelman.shadow'
apply from: 'package/package.gradle'
version = '1.0.18-SNAPSHOT'
version = '1.0.19-SNAPSHOT'
jar.manifest.attributes(
"Implementation-Title": project.name,

View file

@ -28,7 +28,7 @@ import static com.google.common.base.Preconditions.checkArgument;
public class Version {
// The application versions
// We use semantic versioning with major, minor and patch
public static final String VERSION = "1.0.18";
public static final String VERSION = "1.0.19";
/**
* Holds a list of the tagged resource files for optimizing the getData requests.

View file

@ -159,7 +159,7 @@ public class CoreOffersService {
}
OpenOffer getMyOffer(String id) {
return openOfferManager.getOpenOfferById(id)
return openOfferManager.getOpenOffer(id)
.filter(open -> open.getOffer().isMyOffer(keyRing))
.orElseThrow(() ->
new IllegalStateException(format("openoffer with id '%s' not found", id)));

View file

@ -86,7 +86,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
havenoSetup.setDisplaySecurityRecommendationHandler(key -> log.info("onDisplaySecurityRecommendationHandler"));
havenoSetup.setWrongOSArchitectureHandler(msg -> log.error("onWrongOSArchitectureHandler. msg={}", msg));
havenoSetup.setRejectedTxErrorMessageHandler(errorMessage -> log.warn("setRejectedTxErrorMessageHandler. errorMessage={}", errorMessage));
havenoSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
havenoSetup.setShowPopupIfInvalidXmrConfigHandler(() -> log.error("onShowPopupIfInvalidXmrConfigHandler"));
havenoSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
havenoSetup.setOsxKeyLoggerWarningHandler(() -> log.info("setOsxKeyLoggerWarningHandler"));
havenoSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));

View file

@ -176,7 +176,7 @@ public class HavenoSetup {
private Consumer<PrivateNotificationPayload> displayPrivateNotificationHandler;
@Setter
@Nullable
private Runnable showPopupIfInvalidBtcConfigHandler;
private Runnable showPopupIfInvalidXmrConfigHandler;
@Setter
@Nullable
private Consumer<List<RevolutAccount>> revolutAccountsUpdateHandler;
@ -461,7 +461,7 @@ public class HavenoSetup {
havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet);
walletAppSetup.init(chainFileLockedExceptionHandler,
showFirstPopupIfResyncSPVRequestedHandler,
showPopupIfInvalidBtcConfigHandler,
showPopupIfInvalidXmrConfigHandler,
() -> {},
() -> {});
}

View file

@ -117,7 +117,7 @@ public class WalletAppSetup {
void init(@Nullable Consumer<String> chainFileLockedExceptionHandler,
@Nullable Runnable showFirstPopupIfResyncSPVRequestedHandler,
@Nullable Runnable showPopupIfInvalidBtcConfigHandler,
@Nullable Runnable showPopupIfInvalidXmrConfigHandler,
Runnable downloadCompleteHandler,
Runnable walletInitializedHandler) {
log.info("Initialize WalletAppSetup with monero-java v{}", MoneroUtils.getVersion());
@ -199,8 +199,8 @@ public class WalletAppSetup {
walletInitializedHandler.run();
},
exception -> {
if (exception instanceof InvalidHostException && showPopupIfInvalidBtcConfigHandler != null) {
showPopupIfInvalidBtcConfigHandler.run();
if (exception instanceof InvalidHostException && showPopupIfInvalidXmrConfigHandler != null) {
showPopupIfInvalidXmrConfigHandler.run();
} else {
walletServiceException.set(exception);
}

View file

@ -236,7 +236,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
public void onAdded(Offer offer) {
// cancel offer if reserved funds spent
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
Optional<OpenOffer> openOfferOptional = getOpenOffer(offer.getId());
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() != OpenOffer.State.RESERVED && offer.isReservedFundsSpent()) {
log.warn("Canceling open offer because reserved funds have been spent, offerId={}, state={}", offer.getId(), openOfferOptional.get().getState());
cancelOpenOffer(openOfferOptional.get(), null, null);
@ -573,7 +573,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// Remove from offerbook
public void removeOffer(Offer offer, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(offer.getId());
Optional<OpenOffer> openOfferOptional = getOpenOffer(offer.getId());
if (openOfferOptional.isPresent()) {
cancelOpenOffer(openOfferOptional.get(), resultHandler, errorMessageHandler);
} else {
@ -686,7 +686,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
OpenOffer.State originalState,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(editedOffer.getId());
Optional<OpenOffer> openOfferOptional = getOpenOffer(editedOffer.getId());
if (openOfferOptional.isPresent()) {
OpenOffer openOffer = openOfferOptional.get();
@ -750,7 +750,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// close open offer after key images spent
public void closeOpenOffer(Offer offer) {
getOpenOfferById(offer.getId()).ifPresent(openOffer -> {
getOpenOffer(offer.getId()).ifPresent(openOffer -> {
removeOpenOffer(openOffer);
openOffer.setState(OpenOffer.State.CLOSED);
xmrWalletService.resetAddressEntriesForOpenOffer(offer.getId());
@ -813,14 +813,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return openOffers.getObservableList();
}
public Optional<OpenOffer> getOpenOfferById(String offerId) {
public Optional<OpenOffer> getOpenOffer(String offerId) {
synchronized (openOffers) {
return openOffers.stream().filter(e -> e.getId().equals(offerId)).findFirst();
}
}
public boolean hasOpenOffer(String offerId) {
return getOpenOfferById(offerId).isPresent();
return getOpenOffer(offerId).isPresent();
}
public Optional<SignedOffer> getSignedOfferById(String offerId) {
@ -987,26 +987,16 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
setSplitOutputTx(openOffer, splitOutputTx);
}
// if not found, create tx to split exact output
if (splitOutputTx == null) {
if (openOffer.getSplitOutputTxHash() != null) {
log.warn("Split output tx unexpectedly unavailable for offer, offerId={}, split output tx={}", openOffer.getId(), openOffer.getSplitOutputTxHash());
setSplitOutputTx(openOffer, null);
}
try {
splitOrSchedule(openOffers, openOffer, amountNeeded);
} catch (Exception e) {
log.warn("Unable to split or schedule funds for offer {}: {}", openOffer.getId(), e.getMessage());
openOffer.getOffer().setState(Offer.State.INVALID);
errorMessageHandler.handleErrorMessage(e.getMessage());
return;
}
} else if (!splitOutputTx.isLocked()) {
// otherwise sign and post offer if split output available
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
// if wallet has exact available balance, try to sign and post directly
if (xmrWalletService.getAvailableBalance().equals(amountNeeded)) {
signAndPostOffer(openOffer, true, resultHandler, (errorMessage) -> {
splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler);
});
return;
} else {
splitOrSchedule(splitOutputTx, openOffers, openOffer, amountNeeded, resultHandler, errorMessageHandler);
}
} else {
// sign and post offer if enough funds
@ -1017,11 +1007,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return;
} else if (openOffer.getScheduledTxHashes() == null) {
scheduleWithEarliestTxs(openOffers, openOffer);
resultHandler.handleResult(null);
return;
}
}
// handle result
resultHandler.handleResult(null);
} catch (Exception e) {
if (!openOffer.isCanceled()) log.error("Error processing pending offer: {}\n", e.getMessage(), e);
errorMessageHandler.handleErrorMessage(e.getMessage());
@ -1087,13 +1076,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (output.isSpent() || output.isFrozen()) removeTxs.add(tx);
}
}
if (!hasExactAmount(tx, reserveAmount, preferredSubaddressIndex)) removeTxs.add(tx);
if (!hasExactOutput(tx, reserveAmount, preferredSubaddressIndex)) removeTxs.add(tx);
}
splitOutputTxs.removeAll(removeTxs);
return splitOutputTxs;
}
private boolean hasExactAmount(MoneroTxWallet tx, BigInteger amount, Integer preferredSubaddressIndex) {
private boolean hasExactOutput(MoneroTxWallet tx, BigInteger amount, Integer preferredSubaddressIndex) {
boolean hasExactOutput = (tx.getOutputsWallet(new MoneroOutputQuery()
.setAccountIndex(0)
.setSubaddressIndex(preferredSubaddressIndex)
@ -1115,7 +1104,35 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return earliestUnscheduledTx;
}
private void splitOrSchedule(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger offerReserveAmount) {
// if split tx not found and cannot reserve exact amount directly, create tx to split or reserve exact output
private void splitOrSchedule(MoneroTxWallet splitOutputTx, List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger amountNeeded, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
if (splitOutputTx == null) {
if (openOffer.getSplitOutputTxHash() != null) {
log.warn("Split output tx unexpectedly unavailable for offer, offerId={}, split output tx={}", openOffer.getId(), openOffer.getSplitOutputTxHash());
setSplitOutputTx(openOffer, null);
}
try {
splitOrScheduleAux(openOffers, openOffer, amountNeeded);
resultHandler.handleResult(null);
return;
} catch (Exception e) {
log.warn("Unable to split or schedule funds for offer {}: {}", openOffer.getId(), e.getMessage());
openOffer.getOffer().setState(Offer.State.INVALID);
errorMessageHandler.handleErrorMessage(e.getMessage());
return;
}
} else if (!splitOutputTx.isLocked()) {
// otherwise sign and post offer if split output available
signAndPostOffer(openOffer, true, resultHandler, errorMessageHandler);
return;
} else {
resultHandler.handleResult(null);
return;
}
}
private void splitOrScheduleAux(List<OpenOffer> openOffers, OpenOffer openOffer, BigInteger offerReserveAmount) {
// handle sufficient available balance to split output
boolean sufficientAvailableBalance = xmrWalletService.getAvailableBalance().compareTo(offerReserveAmount) >= 0;
@ -1299,13 +1316,13 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
openOffer.setScheduledAmount(null);
requestPersistence();
resultHandler.handleResult(transaction);
if (!stopped) {
startPeriodicRepublishOffersTimer();
startPeriodicRefreshOffersTimer();
} else {
log.debug("We have stopped already. We ignore that placeOfferProtocol.placeOffer.onResult call.");
}
resultHandler.handleResult(transaction);
},
errorMessageHandler);
@ -1557,6 +1574,14 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return;
}
// Don't allow trade start if not connected to Monero node
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) {
errorMessage = "We got a handleOfferAvailabilityRequest but we are not connected to a Monero node.";
log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
return;
}
if (stopped) {
errorMessage = "We have stopped already. We ignore that handleOfferAvailabilityRequest call.";
log.debug(errorMessage);
@ -1575,7 +1600,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
try {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
Optional<OpenOffer> openOfferOptional = getOpenOffer(request.offerId);
AvailabilityResult availabilityResult;
byte[] makerSignature = null;
if (openOfferOptional.isPresent()) {
@ -1961,6 +1986,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
}
private boolean preventedFromPublishing(OpenOffer openOffer) {
if (!Boolean.TRUE.equals(xmrConnectionService.isConnected())) return true;
return openOffer.isDeactivated() || openOffer.isCanceled() || openOffer.getOffer().getOfferPayload().getArbitratorSigner() == null;
}
@ -1983,25 +2009,27 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
if (periodicRefreshOffersTimer == null)
periodicRefreshOffersTimer = UserThread.runPeriodically(() -> {
if (!stopped) {
int size = openOffers.size();
//we clone our list as openOffers might change during our delayed call
final ArrayList<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
for (int i = 0; i < size; i++) {
// we delay to avoid reaching throttle limits
// roughly 4 offers per second
long delay = 300;
final long minDelay = (i + 1) * delay;
final long maxDelay = (i + 2) * delay;
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
// we need to check if in the meantime the offer has been removed
boolean contained = false;
synchronized (openOffers) {
contained = openOffers.contains(openOffer);
}
if (contained) maybeRefreshOffer(openOffer, 0, 1);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
synchronized (openOffers) {
int size = openOffers.size();
//we clone our list as openOffers might change during our delayed call
final ArrayList<OpenOffer> openOffersList = new ArrayList<>(openOffers.getList());
for (int i = 0; i < size; i++) {
// we delay to avoid reaching throttle limits
// roughly 4 offers per second
long delay = 300;
final long minDelay = (i + 1) * delay;
final long maxDelay = (i + 2) * delay;
final OpenOffer openOffer = openOffersList.get(i);
UserThread.runAfterRandomDelay(() -> {
// we need to check if in the meantime the offer has been removed
boolean contained = false;
synchronized (openOffers) {
contained = openOffers.contains(openOffer);
}
if (contained) maybeRefreshOffer(openOffer, 0, 1);
}, minDelay, maxDelay, TimeUnit.MILLISECONDS);
}
}
} else {
log.debug("We have stopped already. We ignore that periodicRefreshOffersTimer.run call.");

View file

@ -87,6 +87,9 @@ public class MakerReserveOfferFunds extends Task<PlaceOfferModel> {
try {
//if (true) throw new RuntimeException("Pretend error");
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, makerFee, sendAmount, securityDeposit, returnAddress, openOffer.isReserveExactAmount(), preferredSubaddressIndex);
} catch (IllegalStateException e) {
log.warn("Illegal state creating reserve tx, offerId={}, error={}", openOffer.getShortId(), i + 1, e.getMessage());
throw e;
} catch (Exception e) {
log.warn("Error creating reserve tx, offerId={}, attempt={}/{}, error={}", openOffer.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
model.getXmrWalletService().handleWalletError(e, sourceConnection);

View file

@ -77,7 +77,7 @@ public class MakerSendSignOfferRequest extends Task<PlaceOfferModel> {
offer.getOfferPayload().getReserveTxKeyImages(),
returnAddress);
// send request to least used arbitrators until success
// send request to random arbitrators until success
sendSignOfferRequests(request, () -> {
complete();
}, (errorMessage) -> {

View file

@ -196,7 +196,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
tradeManager.requestPersistence();
}
} else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(tradeId);
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
}
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);

View file

@ -196,7 +196,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
tradeManager.requestPersistence();
}
} else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(tradeId);
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
}
sendAckMessage(chatMessage, dispute.getAgentPubKeyRing(), true, null);
@ -205,7 +205,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
if (tradeManager.getOpenTrade(tradeId).isPresent()) {
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.REFUND_REQUEST_CLOSED);
} else {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(tradeId);
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(tradeId);
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
}

View file

@ -143,6 +143,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
private static final long DELETE_AFTER_NUM_BLOCKS = 2; // if deposit requested but not published
private static final long EXTENDED_RPC_TIMEOUT = 600000; // 10 minutes
private static final long DELETE_AFTER_MS = TradeProtocol.TRADE_STEP_TIMEOUT_SECONDS;
private static final int NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT = 10;
protected final Object pollLock = new Object();
protected static final Object importMultisigLock = new Object();
private boolean pollInProgress;
@ -735,12 +736,17 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
// TODO: buyer's payment sent message state property became unsynced if shut down while awaiting ack from seller. fixed in v1.0.19 so this check can be removed?
if (isBuyer()) {
MessageState expectedState = getPaymentSentMessageState();
if (expectedState != null && expectedState != processModel.getPaymentSentMessageStateProperty().get()) {
log.warn("Updating unexpected payment sent message state for {} {}, expected={}, actual={}", getClass().getSimpleName(), getId(), expectedState, processModel.getPaymentSentMessageStateProperty().get());
processModel.getPaymentSentMessageStateProperty().set(expectedState);
if (expectedState != null && expectedState != processModel.getPaymentSentMessageStatePropertySeller().get()) {
log.warn("Updating unexpected payment sent message state for {} {}, expected={}, actual={}", getClass().getSimpleName(), getId(), expectedState, processModel.getPaymentSentMessageStatePropertySeller().get());
processModel.getPaymentSentMessageStatePropertySeller().set(expectedState);
}
}
// handle confirmations
walletHeight.addListener((observable, oldValue, newValue) -> {
importMultisigHexIfScheduled();
});
// trade is initialized
isInitialized = true;
@ -1077,6 +1083,26 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
}
}
public void scheduleImportMultisigHex() {
processModel.setImportMultisigHexScheduled(true);
requestPersistence();
}
private void importMultisigHexIfScheduled() {
if (!isInitialized || isShutDownStarted) return;
if (!isDepositsConfirmed() || getMaker().getDepositTx() == null) return;
if (walletHeight.get() - getMaker().getDepositTx().getHeight() < NUM_CONFIRMATIONS_FOR_SCHEDULED_IMPORT) return;
ThreadUtils.execute(() -> {
if (!isInitialized || isShutDownStarted) return;
synchronized (getLock()) {
if (processModel.isImportMultisigHexScheduled()) {
processModel.setImportMultisigHexScheduled(false);
ThreadUtils.submitToPool(() -> importMultisigHex());
}
}
}, getId());
}
public void importMultisigHex() {
synchronized (walletLock) {
synchronized (HavenoUtils.getDaemonLock()) { // lock on daemon because import calls full refresh
@ -1089,10 +1115,10 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
} catch (IllegalArgumentException | IllegalStateException e) {
throw e;
} catch (Exception e) {
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
handleWalletError(e, sourceConnection);
doPollWallet();
if (isPayoutPublished()) break;
log.warn("Failed to import multisig hex, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
}
@ -1141,6 +1167,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
if (removed) wallet.importMultisigHex(multisigHexes.toArray(new String[0]));
if (wallet.isMultisigImportNeeded()) throw new IllegalStateException(errorMessage);
}
// remove scheduled import
processModel.setImportMultisigHexScheduled(false);
} catch (MoneroError e) {
// import multisig hex individually if one is invalid
@ -1604,7 +1633,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
}
// unreserve maker's open offer
Optional<OpenOffer> openOffer = processModel.getOpenOfferManager().getOpenOfferById(this.getId());
Optional<OpenOffer> openOffer = processModel.getOpenOfferManager().getOpenOffer(this.getId());
if (this instanceof MakerTrade && openOffer.isPresent()) {
processModel.getOpenOfferManager().unreserveOpenOffer(openOffer.get());
}
@ -1618,15 +1647,16 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
// done if wallet already deleted
if (!walletExists()) return;
// move to failed trades
processModel.getTradeManager().onMoveInvalidTradeToFailedTrades(this);
// set error height
if (processModel.getTradeProtocolErrorHeight() == 0) {
log.warn("Scheduling to remove trade if unfunded for {} {} from height {}", getClass().getSimpleName(), getId(), xmrConnectionService.getLastInfo().getHeight());
processModel.setTradeProtocolErrorHeight(xmrConnectionService.getLastInfo().getHeight());
processModel.setTradeProtocolErrorHeight(xmrConnectionService.getLastInfo().getHeight()); // height denotes scheduled error handling
}
// move to failed trades
processModel.getTradeManager().onMoveInvalidTradeToFailedTrades(this);
requestPersistence();
// listen for deposits published to restore trade
protocolErrorStateSubscription = EasyBind.subscribe(stateProperty(), state -> {
if (isDepositsPublished()) {
@ -1680,10 +1710,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
});
}
public boolean isProtocolErrorHandlingScheduled() {
return processModel.getTradeProtocolErrorHeight() > 0;
}
private void restoreDepositsPublishedTrade() {
// close open offer
if (this instanceof MakerTrade && processModel.getOpenOfferManager().getOpenOfferById(getId()).isPresent()) {
if (this instanceof MakerTrade && processModel.getOpenOfferManager().getOpenOffer(getId()).isPresent()) {
log.info("Closing open offer because {} {} was restored after protocol error", getClass().getSimpleName(), getShortId());
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(getOffer()));
}
@ -2017,7 +2051,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
public MessageState getPaymentSentMessageState() {
if (isPaymentReceived()) return MessageState.ACKNOWLEDGED;
if (processModel.getPaymentSentMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return MessageState.ACKNOWLEDGED;
if (processModel.getPaymentSentMessageStatePropertySeller().get() == MessageState.ACKNOWLEDGED) return MessageState.ACKNOWLEDGED;
switch (state) {
case BUYER_SENT_PAYMENT_SENT_MSG:
return MessageState.SENT;
@ -2345,7 +2379,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
return tradeAmountTransferred();
}
public boolean tradeAmountTransferred() {
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private boolean tradeAmountTransferred() {
return isPaymentReceived() || (getDisputeResult() != null && getDisputeResult().getWinner() == DisputeResult.Winner.SELLER);
}
@ -2361,11 +2400,6 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
// lazy initialization
private ObjectProperty<BigInteger> getAmountProperty() {
if (tradeAmountProperty == null)
@ -2436,8 +2470,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
if (!wasWalletSynced) trySyncWallet(true);
updatePollPeriod();
// reprocess pending payout messages
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
// reprocess pending messages
getProtocol().maybeReprocessPaymentSentMessage(false);
getProtocol().maybeReprocessPaymentReceivedMessage(false);
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
startPolling();

View file

@ -450,8 +450,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return;
}
// skip if marked as failed
if (failedTradesManager.getObservableList().contains(trade)) {
// skip if failed and error handling not scheduled
if (failedTradesManager.getObservableList().contains(trade) && !trade.isProtocolErrorHandlingScheduled()) {
log.warn("Skipping initialization of failed trade {} {}", trade.getClass().getSimpleName(), trade.getId());
tradesToSkip.add(trade);
return;
@ -460,8 +460,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// initialize trade
initPersistedTrade(trade);
// remove trade if protocol didn't initialize
if (getOpenTradeByUid(trade.getUid()).isPresent() && !trade.isDepositsPublished()) {
// record if protocol didn't initialize
if (!trade.isDepositsPublished()) {
uninitializedTrades.add(trade);
}
} catch (Exception e) {
@ -556,7 +556,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
if (request.getMakerNodeAddress().equals(p2PService.getNetworkNode().getNodeAddress())) {
// get open offer
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(request.getOfferId());
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOffer(request.getOfferId());
if (!openOfferOptional.isPresent()) return;
OpenOffer openOffer = openOfferOptional.get();
if (openOffer.getState() != OpenOffer.State.AVAILABLE) return;
@ -747,7 +747,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
private void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
log.info("TradeManager handling InitMultisigRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
log.info("TradeManager handling InitMultisigRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
try {
Validator.nonEmptyStringOf(request.getOfferId());
@ -766,7 +766,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
private void handleSignContractRequest(SignContractRequest request, NodeAddress sender) {
log.info("TradeManager handling SignContractRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
log.info("TradeManager handling SignContractRequest for tradeId={}, sender={}, uid={}", request.getOfferId(), sender, request.getUid());
try {
Validator.nonEmptyStringOf(request.getOfferId());
@ -923,8 +923,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
requestPersistence();
}, errorMessage -> {
log.warn("Taker error during trade initialization: " + errorMessage);
xmrWalletService.resetAddressEntriesForOpenOffer(trade.getId()); // TODO: move to maybe remove on error
trade.onProtocolError();
xmrWalletService.resetAddressEntriesForOpenOffer(trade.getId()); // TODO: move this into protocol error handling
errorMessageHandler.handleErrorMessage(errorMessage);
});
@ -1285,6 +1285,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
}
public boolean hasFailedScheduledTrade(String offerId) {
synchronized (failedTradesManager) {
return failedTradesManager.getTradeById(offerId).isPresent() && failedTradesManager.getTradeById(offerId).get().isProtocolErrorHandlingScheduled();
}
}
public Optional<Trade> getOpenTradeByUid(String tradeUid) {
synchronized (tradableList) {
return tradableList.stream().filter(e -> e.getUid().equals(tradeUid)).findFirst();

View file

@ -43,7 +43,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
///////////////////////////////////////////////////////////////////////////////////////////
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
System.out.println("ArbitratorProtocol.handleInitTradeRequest()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();
@ -78,7 +78,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
}
public void handleDepositRequest(DepositRequest request, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleDepositRequest() " + trade.getId());
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleDepositRequest() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();

View file

@ -60,8 +60,8 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
ThreadUtils.execute(() -> {
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();
this.errorMessageHandler = errorMessageHandler;

View file

@ -68,7 +68,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
@Override
public void onTakeOffer(TradeResultHandler tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
System.out.println(getClass().getSimpleName() + ".onTakeOffer()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "onTakerOffer for {} {}", getClass().getSimpleName(), trade.getShortId());
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();
@ -99,7 +99,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer) {
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();

View file

@ -119,7 +119,7 @@ public class BuyerProtocol extends DisputeProtocol {
///////////////////////////////////////////////////////////////////////////////////////////
public void onPaymentSent(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerProtocol.onPaymentSent()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "BuyerProtocol.onPaymentSent() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();

View file

@ -158,12 +158,15 @@ public class ProcessModel implements Model, PersistablePayload {
@Getter
@Setter
private long tradeProtocolErrorHeight;
@Getter
@Setter
private boolean importMultisigHexScheduled;
// We want to indicate the user the state of the message delivery of the
// PaymentSentMessage. As well we do an automatic re-send in case it was not ACKed yet.
// To enable that even after restart we persist the state.
@Setter
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
private ObjectProperty<MessageState> paymentSentMessageStatePropertySeller = new SimpleObjectProperty<>(MessageState.UNDEFINED);
@Setter
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
private ObjectProperty<Boolean> paymentAccountDecryptedProperty = new SimpleObjectProperty<>(false);
@ -203,11 +206,12 @@ public class ProcessModel implements Model, PersistablePayload {
.setPubKeyRing(pubKeyRing.toProtoMessage())
.setUseSavingsWallet(useSavingsWallet)
.setFundsNeededForTrade(fundsNeededForTrade)
.setPaymentSentMessageState(paymentSentMessageStateProperty.get().name())
.setPaymentSentMessageStateSeller(paymentSentMessageStatePropertySeller.get().name())
.setPaymentSentMessageStateArbitrator(paymentSentMessageStatePropertyArbitrator.get().name())
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation)
.setTradeProtocolErrorHeight(tradeProtocolErrorHeight);
.setTradeProtocolErrorHeight(tradeProtocolErrorHeight)
.setImportMultisigHexScheduled(importMultisigHexScheduled);
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradePeer) maker.toProtoMessage()));
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradePeer) taker.toProtoMessage()));
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradePeer) arbitrator.toProtoMessage()));
@ -231,6 +235,7 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setBuyerPayoutAmountFromMediation(proto.getBuyerPayoutAmountFromMediation());
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
processModel.setTradeProtocolErrorHeight(proto.getTradeProtocolErrorHeight());
processModel.setImportMultisigHexScheduled(proto.getImportMultisigHexScheduled());
// nullable
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
@ -240,9 +245,9 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setTradeFeeAddress(ProtoUtil.stringOrNullFromProto(proto.getTradeFeeAddress()));
processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress()));
String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState());
MessageState paymentSentMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateString);
processModel.setPaymentSentMessageState(paymentSentMessageState);
String paymentSentMessageStateSellerString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageStateSeller());
MessageState paymentSentMessageStateSeller = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateSellerString);
processModel.setPaymentSentMessageStateSeller(paymentSentMessageStateSeller);
String paymentSentMessageStateArbitratorString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageStateArbitrator());
MessageState paymentSentMessageStateArbitrator = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateArbitratorString);
@ -274,11 +279,11 @@ public class ProcessModel implements Model, PersistablePayload {
return getP2PService().getAddress();
}
void setPaymentSentAckMessage(AckMessage ackMessage) {
void setPaymentSentAckMessageSeller(AckMessage ackMessage) {
MessageState messageState = ackMessage.isSuccess() ?
MessageState.ACKNOWLEDGED :
MessageState.FAILED;
setPaymentSentMessageState(messageState);
setPaymentSentMessageStateSeller(messageState);
}
void setPaymentSentAckMessageArbitrator(AckMessage ackMessage) {
@ -288,8 +293,8 @@ public class ProcessModel implements Model, PersistablePayload {
setPaymentSentMessageStateArbitrator(messageState);
}
public void setPaymentSentMessageState(MessageState paymentSentMessageStateProperty) {
this.paymentSentMessageStateProperty.set(paymentSentMessageStateProperty);
public void setPaymentSentMessageStateSeller(MessageState paymentSentMessageStateProperty) {
this.paymentSentMessageStatePropertySeller.set(paymentSentMessageStateProperty);
if (tradeManager != null) {
tradeManager.requestPersistence();
}
@ -302,6 +307,14 @@ public class ProcessModel implements Model, PersistablePayload {
}
}
public boolean isPaymentSentMessageAckedBySeller() {
return paymentSentMessageStatePropertySeller.get() == MessageState.ACKNOWLEDGED;
}
public boolean isPaymentSentMessageAckedByArbitrator() {
return paymentSentMessageStatePropertyArbitrator.get() == MessageState.ACKNOWLEDGED;
}
void setDepositTxSentAckMessage(AckMessage ackMessage) {
MessageState messageState = ackMessage.isSuccess() ?
MessageState.ACKNOWLEDGED :

View file

@ -65,7 +65,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();

View file

@ -68,7 +68,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
@Override
public void onTakeOffer(TradeResultHandler tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
System.out.println(getClass().getSimpleName() + ".onTakeOffer()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "onTakerOffer for {} {}", getClass().getSimpleName(), trade.getShortId());
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();
@ -99,7 +99,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer) {
System.out.println(getClass().getCanonicalName() + ".handleInitTradeRequest()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "handleInitTradeRequest() for {} {} from {}", trade.getClass().getSimpleName(), trade.getShortId(), peer);
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();

View file

@ -115,7 +115,7 @@ public class SellerProtocol extends DisputeProtocol {
///////////////////////////////////////////////////////////////////////////////////////////
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
log.info("SellerProtocol.onPaymentReceived()");
log.info(TradeProtocol.LOG_HIGHLIGHT + "SellerProtocol.onPaymentReceived() for {} {}", trade.getClass().getSimpleName(), trade.getShortId());
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
latchTrade();

View file

@ -96,6 +96,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
private static final String TIMEOUT_REACHED = "Timeout reached.";
public static final int MAX_ATTEMPTS = 5; // max attempts to create txs and other wallet functions
public static final long REPROCESS_DELAY_MS = 5000;
public static final String LOG_HIGHLIGHT = "\u001B[0m"; // terminal default
protected final ProcessModel processModel;
protected final Trade trade;
@ -106,6 +107,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
protected ErrorMessageHandler errorMessageHandler;
private boolean depositsConfirmedTasksCalled;
private int reprocessPaymentSentMessageCount;
private int reprocessPaymentReceivedMessageCount;
///////////////////////////////////////////////////////////////////////////////////////////
@ -124,12 +126,12 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
protected void onTradeMessage(TradeMessage message, NodeAddress peerNodeAddress) {
log.info("Received {} as TradeMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getOfferId(), message.getUid());
ThreadUtils.execute(() -> handle(message, peerNodeAddress), trade.getId());
handle(message, peerNodeAddress);
}
protected void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) {
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getOfferId(), message.getUid());
ThreadUtils.execute(() -> handle(message, peerNodeAddress), trade.getId());
handle(message, peerNodeAddress);
}
private void handle(TradeMessage message, NodeAddress peerNodeAddress) {
@ -279,6 +281,22 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}, trade.getId());
}
public void maybeReprocessPaymentSentMessage(boolean reprocessOnError) {
if (trade.isShutDownStarted()) return;
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
// skip if no need to reprocess
if (trade.isBuyer() || trade.getBuyer().getPaymentSentMessage() == null || trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) {
return;
}
log.warn("Reprocessing payment sent message for {} {}", trade.getClass().getSimpleName(), trade.getId());
handle(trade.getBuyer().getPaymentSentMessage(), trade.getBuyer().getPaymentSentMessage().getSenderNodeAddress(), reprocessOnError);
}
}, trade.getId());
}
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
if (trade.isShutDownStarted()) return;
ThreadUtils.execute(() -> {
@ -296,7 +314,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getSimpleName() + ".handleInitMultisigRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.info(LOG_HIGHLIGHT + "handleInitMultisigRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
trade.addInitProgressStep();
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
@ -333,7 +351,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getSimpleName() + ".handleSignContractRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.info(LOG_HIGHLIGHT + "handleSignContractRequest() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
@ -376,7 +394,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getSimpleName() + ".handleSignContractResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.info(LOG_HIGHLIGHT + "handleSignContractResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
trade.addInitProgressStep();
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
@ -422,7 +440,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println(getClass().getSimpleName() + ".handleDepositResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.info(LOG_HIGHLIGHT + "handleDepositResponse() for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
trade.addInitProgressStep();
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
@ -452,7 +470,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
public void handle(DepositsConfirmedMessage message, NodeAddress sender) {
System.out.println(getClass().getSimpleName() + ".handle(DepositsConfirmedMessage) from " + sender + " for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.info(LOG_HIGHLIGHT + "handle(DepositsConfirmedMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + sender);
if (!trade.isInitialized() || trade.isShutDown()) return;
ThreadUtils.execute(() -> {
synchronized (trade.getLock()) {
@ -481,7 +499,25 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// received by seller and arbitrator
protected void handle(PaymentSentMessage message, NodeAddress peer) {
System.out.println(getClass().getSimpleName() + ".handle(PaymentSentMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
handle(message, peer, true);
}
// received by seller and arbitrator
protected void handle(PaymentSentMessage message, NodeAddress peer, boolean reprocessOnError) {
log.info(LOG_HIGHLIGHT + "handle(PaymentSentMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + peer);
// validate signature
try {
HavenoUtils.verifyPaymentSentMessage(trade, message);
} catch (Throwable t) {
log.warn("Ignoring PaymentSentMessage with invalid signature for {} {}, error={}", trade.getClass().getSimpleName(), trade.getId(), t.getMessage());
return;
}
// save message for reprocessing
trade.getBuyer().setPaymentSentMessage(message);
trade.requestPersistence();
if (!trade.isInitialized() || trade.isShutDown()) return;
if (!(trade instanceof SellerTrade || trade instanceof ArbitratorTrade)) {
log.warn("Ignoring PaymentSentMessage since not seller or arbitrator");
@ -521,7 +557,19 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
handleTaskRunnerSuccess(peer, message);
},
(errorMessage) -> {
handleTaskRunnerFault(peer, message, errorMessage);
log.warn("Error processing payment sent message: " + errorMessage);
processModel.getTradeManager().requestPersistence();
// schedule to reprocess message unless deleted
if (trade.getBuyer().getPaymentSentMessage() != null) {
UserThread.runAfter(() -> {
reprocessPaymentSentMessageCount++;
maybeReprocessPaymentSentMessage(reprocessOnError);
}, trade.getReprocessDelayInSeconds(reprocessPaymentSentMessageCount));
} else {
handleTaskRunnerFault(peer, message, errorMessage); // otherwise send nack
}
unlatchTrade();
})))
.executeTasks(true);
awaitTradeLatch();
@ -535,7 +583,20 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
}
private void handle(PaymentReceivedMessage message, NodeAddress peer, boolean reprocessOnError) {
System.out.println(getClass().getSimpleName() + ".handle(PaymentReceivedMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId());
log.info(LOG_HIGHLIGHT + "handle(PaymentReceivedMessage) for " + trade.getClass().getSimpleName() + " " + trade.getShortId() + " from " + peer);
// validate signature
try {
HavenoUtils.verifyPaymentReceivedMessage(trade, message);
} catch (Throwable t) {
log.warn("Ignoring PaymentReceivedMessage with invalid signature for {} {}, error={}", trade.getClass().getSimpleName(), trade.getId(), t.getMessage());
return;
}
// save message for reprocessing
trade.getSeller().setPaymentReceivedMessage(message);
trade.requestPersistence();
if (!trade.isInitialized() || trade.isShutDown()) return;
ThreadUtils.execute(() -> {
if (!(trade instanceof BuyerTrade || trade instanceof ArbitratorTrade)) {
@ -652,11 +713,12 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
// handle ack for PaymentSentMessage, which automatically re-sends if not ACKed in a certain time
if (ackMessage.getSourceMsgClassName().equals(PaymentSentMessage.class.getSimpleName())) {
if (trade.getTradePeer(sender) == trade.getSeller()) {
processModel.setPaymentSentAckMessage(ackMessage);
processModel.setPaymentSentAckMessageSeller(ackMessage);
trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
processModel.getTradeManager().requestPersistence();
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
processModel.setPaymentSentAckMessageArbitrator(ackMessage);
processModel.getTradeManager().requestPersistence();
} else if (!ackMessage.isSuccess()) {
String err = "Received AckMessage with error state for " + ackMessage.getSourceMsgClassName() + " from "+ sender + " with tradeId " + trade.getId() + " and errorMessage=" + ackMessage.getErrorMessage();
log.warn(err);

View file

@ -170,7 +170,7 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
timer.stop();
}
if (listener != null) {
processModel.getPaymentSentMessageStateProperty().removeListener(listener);
processModel.getPaymentSentMessageStatePropertySeller().removeListener(listener);
}
}
@ -194,8 +194,8 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
if (resendCounter == 0) {
listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue);
processModel.getPaymentSentMessageStateProperty().addListener(listener);
onMessageStateChange(processModel.getPaymentSentMessageStateProperty().get());
processModel.getPaymentSentMessageStatePropertySeller().addListener(listener);
onMessageStateChange(processModel.getPaymentSentMessageStatePropertySeller().get());
}
// first re-send is after 2 minutes, then increase the delay exponentially

View file

@ -18,7 +18,6 @@
package haveno.core.trade.protocol.tasks;
import haveno.common.taskrunner.TaskRunner;
import haveno.core.network.MessageState;
import haveno.core.trade.Trade;
import haveno.core.trade.protocol.TradePeer;
import lombok.EqualsAndHashCode;
@ -59,6 +58,6 @@ public class BuyerSendPaymentSentMessageToArbitrator extends BuyerSendPaymentSen
@Override
protected boolean isAckedByReceiver() {
return trade.getProcessModel().getPaymentSentMessageStatePropertyArbitrator().get() == MessageState.ACKNOWLEDGED;
return trade.getProcessModel().isPaymentSentMessageAckedByArbitrator();
}
}

View file

@ -40,25 +40,25 @@ public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMes
@Override
protected void setStateSent() {
trade.getProcessModel().setPaymentSentMessageState(MessageState.SENT);
trade.getProcessModel().setPaymentSentMessageStateSeller(MessageState.SENT);
super.setStateSent();
}
@Override
protected void setStateArrived() {
trade.getProcessModel().setPaymentSentMessageState(MessageState.ARRIVED);
trade.getProcessModel().setPaymentSentMessageStateSeller(MessageState.ARRIVED);
super.setStateArrived();
}
@Override
protected void setStateStoredInMailbox() {
trade.getProcessModel().setPaymentSentMessageState(MessageState.STORED_IN_MAILBOX);
trade.getProcessModel().setPaymentSentMessageStateSeller(MessageState.STORED_IN_MAILBOX);
super.setStateStoredInMailbox();
}
@Override
protected void setStateFault() {
trade.getProcessModel().setPaymentSentMessageState(MessageState.FAILED);
trade.getProcessModel().setPaymentSentMessageStateSeller(MessageState.FAILED);
super.setStateFault();
}
@ -72,6 +72,6 @@ public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMes
@Override
protected boolean isAckedByReceiver() {
return trade.getState().ordinal() >= Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal();
return trade.getProcessModel().isPaymentSentMessageAckedBySeller();
}
}

View file

@ -87,7 +87,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
Integer subaddressIndex = null;
boolean reserveExactAmount = false;
if (trade instanceof MakerTrade) {
reserveExactAmount = processModel.getOpenOfferManager().getOpenOfferById(trade.getId()).get().isReserveExactAmount();
reserveExactAmount = processModel.getOpenOfferManager().getOpenOffer(trade.getId()).get().isReserveExactAmount();
if (reserveExactAmount) subaddressIndex = model.getXmrWalletService().getAddressEntry(trade.getId(), XmrAddressEntry.Context.OFFER_FUNDING).get().getSubaddressIndex();
}

View file

@ -18,7 +18,6 @@
package haveno.core.trade.protocol.tasks;
import haveno.common.ThreadUtils;
import haveno.common.taskrunner.TaskRunner;
import haveno.core.trade.Trade;
import haveno.core.trade.messages.DepositsConfirmedMessage;
@ -63,17 +62,7 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
// update multisig hex
if (sender.getUpdatedMultisigHex() == null) {
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
// try to import multisig hex (retry later)
if (!trade.isPayoutPublished()) {
ThreadUtils.submitToPool(() -> {
try {
trade.importMultisigHex();
} catch (Exception e) {
log.warn("Error importing multisig hex on deposits confirmed for trade " + trade.getId() + ": " + e.getMessage() + "\n", e);
}
});
}
trade.scheduleImportMultisigHex();
}
// persist

View file

@ -80,9 +80,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
return;
}
// save message for reprocessing
trade.getSeller().setPaymentReceivedMessage(message);
// set state
trade.getSeller().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
trade.getBuyer().setAccountAgeWitness(message.getBuyerAccountAgeWitness());

View file

@ -48,7 +48,6 @@ public class ProcessPaymentSentMessage extends TradeTask {
trade.getBuyer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
// update state from message
trade.getBuyer().setPaymentSentMessage(message);
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
trade.getSeller().setAccountAgeWitness(message.getSellerAccountAgeWitness());
String counterCurrencyTxId = message.getCounterCurrencyTxId();

View file

@ -70,6 +70,9 @@ public class TakerReserveTradeFunds extends TradeTask {
MoneroRpcConnection sourceConnection = trade.getXmrConnectionService().getConnection();
try {
reserveTx = model.getXmrWalletService().createReserveTx(penaltyFee, takerFee, sendAmount, securityDeposit, returnAddress, false, null);
} catch (IllegalStateException e) {
log.warn("Illegal state creating reserve tx, offerId={}, error={}", trade.getShortId(), i + 1, e.getMessage());
throw e;
} catch (Exception e) {
log.warn("Error creating reserve tx, tradeId={}, attempt={}/{}, error={}", trade.getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
trade.getXmrWalletService().handleWalletError(e, sourceConnection);

View file

@ -135,7 +135,7 @@ public class MoneroWalletRpcManager {
// stop process
String pid = walletRpc.getProcess() == null ? null : String.valueOf(walletRpc.getProcess().pid());
log.info("Stopping MoneroWalletRpc path={}, port={}, pid={}", path, port, pid);
log.info("Stopping MoneroWalletRpc path={}, port={}, pid={}, force={}", path, port, pid, force);
walletRpc.stopProcess(force);
}

View file

@ -1016,6 +1016,13 @@ public class XmrWalletService extends XmrWalletBase {
public synchronized void resetAddressEntriesForOpenOffer(String offerId) {
log.info("resetAddressEntriesForOpenOffer offerId={}", offerId);
// skip if failed trade is scheduled for processing // TODO: do not call this function in this case?
if (tradeManager.hasFailedScheduledTrade(offerId)) {
log.warn("Refusing to reset address entries because trade is scheduled for deletion with offerId={}", offerId);
return;
}
swapAddressEntryToAvailable(offerId, XmrAddressEntry.Context.OFFER_FUNDING);
// swap trade payout to available if applicable
@ -1164,7 +1171,7 @@ public class XmrWalletService extends XmrWalletBase {
public Stream<XmrAddressEntry> getAddressEntriesForAvailableBalanceStream() {
Stream<XmrAddressEntry> available = getFundedAvailableAddressEntries().stream();
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.ARBITRATOR).stream());
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOfferById(entry.getOfferId()).isPresent()));
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.OFFER_FUNDING).stream().filter(entry -> !tradeManager.getOpenOfferManager().getOpenOffer(entry.getOfferId()).isPresent()));
available = Stream.concat(available, getAddressEntries(XmrAddressEntry.Context.TRADE_PAYOUT).stream().filter(entry -> tradeManager.getTrade(entry.getOfferId()) == null || tradeManager.getTrade(entry.getOfferId()).isPayoutUnlocked()));
return available.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).compareTo(BigInteger.ZERO) > 0);
}

View file

@ -208,7 +208,7 @@ class GrpcOffersService extends OffersImplBase {
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, SECONDS));
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getPostOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
}}
)));
}

View file

@ -252,14 +252,14 @@ class GrpcTradesService extends TradesImplBase {
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 1, SECONDS));
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 1, SECONDS));
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 20 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getConfirmPaymentSentMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getCompleteTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 1, SECONDS));
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getConfirmPaymentSentMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getCompleteTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getWithdrawFundsMethod().getFullMethodName(), new GrpcCallRateMeter(3, MINUTES));
put(getGetChatMessagesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getGetChatMessagesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 30 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
}}
)));
}

View file

@ -60,6 +60,6 @@
</content_rating>
<releases>
<release version="1.0.18" date="2025-01-19"/>
<release version="1.0.19" date="2025-03-10"/>
</releases>
</component>

View file

@ -5,10 +5,10 @@
<!-- See: https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -->
<key>CFBundleVersion</key>
<string>1.0.18</string>
<string>1.0.19</string>
<key>CFBundleShortVersionString</key>
<string>1.0.18</string>
<string>1.0.19</string>
<key>CFBundleExecutable</key>
<string>Haveno</string>

View file

@ -420,7 +420,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
havenoSetup.setRejectedTxErrorMessageHandler(msg -> new Popup().width(850).warning(msg).show());
havenoSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig);
havenoSetup.setShowPopupIfInvalidXmrConfigHandler(this::showPopupIfInvalidXmrConfig);
havenoSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> {
// We copy the array as we will mutate it later
@ -536,7 +536,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
});
}
private void showPopupIfInvalidBtcConfig() {
private void showPopupIfInvalidXmrConfig() {
preferences.setMoneroNodesOptionOrdinal(0);
new Popup().warning(Res.get("settings.net.warn.invalidXmrConfig"))
.hideCloseButton()

View file

@ -225,8 +225,8 @@ public class LockedView extends ActivatableView<VBox, Void> {
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(offerId);
if (tradeOptional.isPresent()) {
return Optional.of(tradeOptional.get());
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {
return Optional.of(openOfferManager.getOpenOfferById(offerId).get());
} else if (openOfferManager.getOpenOffer(offerId).isPresent()) {
return Optional.of(openOfferManager.getOpenOffer(offerId).get());
} else {
return Optional.empty();
}

View file

@ -224,8 +224,8 @@ public class ReservedView extends ActivatableView<VBox, Void> {
Optional<Trade> tradeOptional = tradeManager.getOpenTrade(offerId);
if (tradeOptional.isPresent()) {
return Optional.of(tradeOptional.get());
} else if (openOfferManager.getOpenOfferById(offerId).isPresent()) {
return Optional.of(openOfferManager.getOpenOfferById(offerId).get());
} else if (openOfferManager.getOpenOffer(offerId).isPresent()) {
return Optional.of(openOfferManager.getOpenOffer(offerId).get());
} else {
return Optional.<Tradable>empty();
}

View file

@ -665,7 +665,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
createOfferRequested = false;
createOfferCanceled = true;
OpenOfferManager openOfferManager = HavenoUtils.openOfferManager;
Optional<OpenOffer> openOffer = openOfferManager.getOpenOfferById(offer.getId());
Optional<OpenOffer> openOffer = openOfferManager.getOpenOffer(offer.getId());
if (openOffer.isPresent()) {
openOfferManager.cancelOpenOffer(openOffer.get(), () -> {
UserThread.execute(() -> {

View file

@ -711,6 +711,6 @@ abstract class OfferBookViewModel extends ActivatableViewModel {
abstract String getCurrencyCodeFromPreferences(OfferDirection direction);
public OpenOffer getOpenOffer(Offer offer) {
return openOfferManager.getOpenOfferById(offer.getId()).orElse(null);
return openOfferManager.getOpenOffer(offer.getId()).orElse(null);
}
}

View file

@ -284,6 +284,7 @@ class TakeOfferDataModel extends OfferDataModel {
// handle error
if (errorMsg != null) {
new Popup().warning(errorMsg).show();
log.warn("Error taking offer " + offer.getId() + ": " + errorMsg);
errorMessageHandler.handleErrorMessage(errorMsg);
}
}

View file

@ -765,9 +765,7 @@ public abstract class Overlay<T extends Overlay<T>> {
FormBuilder.getIconForLabel(AwesomeIcon.COPY, copyIcon, "1.1em");
copyIcon.addEventHandler(MOUSE_CLICKED, mouseEvent -> {
if (message != null) {
String forClipboard = headLineLabel.getText() + System.lineSeparator() + message
+ System.lineSeparator() + (messageHyperlinks == null ? "" : messageHyperlinks.toString());
Utilities.copyToClipboard(forClipboard);
Utilities.copyToClipboard(getClipboardText());
Tooltip tp = new Tooltip(Res.get("shared.copiedToClipboard"));
Node node = (Node) mouseEvent.getSource();
UserThread.runAfter(() -> tp.hide(), 1);
@ -1083,6 +1081,11 @@ public abstract class Overlay<T extends Overlay<T>> {
return isDisplayed;
}
public String getClipboardText() {
return headLineLabel.getText() + System.lineSeparator() + message
+ System.lineSeparator() + (messageHyperlinks == null ? "" : messageHyperlinks.toString());
}
@Override
public String toString() {
return "Popup{" +

View file

@ -342,7 +342,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
BigInteger reservedAmount = isMyOffer ? offer.getReservedAmount() : null;
// get offer challenge
OpenOffer myOpenOffer = HavenoUtils.openOfferManager.getOpenOfferById(offer.getId()).orElse(null);
OpenOffer myOpenOffer = HavenoUtils.openOfferManager.getOpenOffer(offer.getId()).orElse(null);
String offerChallenge = myOpenOffer == null ? null : myOpenOffer.getChallenge();
rows = 3;

View file

@ -37,10 +37,10 @@ import java.io.ByteArrayInputStream;
public class QRCodeWindow extends Overlay<QRCodeWindow> {
private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class);
private final ImageView qrCodeImageView;
private final String bitcoinURI;
private final String moneroUri;
public QRCodeWindow(String bitcoinURI) {
this.bitcoinURI = bitcoinURI;
this.moneroUri = bitcoinURI;
final byte[] imageBytes = QRCode
.from(bitcoinURI)
.withSize(300, 300)
@ -70,7 +70,7 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
GridPane.setHalignment(qrCodeImageView, HPos.CENTER);
gridPane.getChildren().add(qrCodeImageView);
String request = bitcoinURI.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
String request = moneroUri.replace("%20", " ").replace("?", "\n?").replace("&", "\n&");
Label infoLabel = new AutoTooltipLabel(Res.get("qRCodeWindow.request", request));
infoLabel.setMouseTransparent(true);
infoLabel.setWrapText(true);
@ -87,4 +87,8 @@ public class QRCodeWindow extends Overlay<QRCodeWindow> {
applyStyles();
display();
}
public String getClipboardText() {
return moneroUri;
}
}

View file

@ -100,7 +100,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
private final ObjectProperty<BuyerState> buyerState = new SimpleObjectProperty<>();
private final ObjectProperty<SellerState> sellerState = new SimpleObjectProperty<>();
@Getter
private final ObjectProperty<MessageState> messageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
private final ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
private Subscription tradeStateSubscription;
private Subscription paymentAccountDecryptedSubscription;
private Subscription payoutStateSubscription;
@ -186,7 +186,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
if (messageStateSubscription != null) {
messageStateSubscription.unsubscribe();
messageStateProperty.set(MessageState.UNDEFINED);
paymentSentMessageStateProperty.set(MessageState.UNDEFINED);
}
if (selectedItem != null) {
@ -200,7 +200,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
payoutStateSubscription = EasyBind.subscribe(trade.payoutStateProperty(), state -> {
onPayoutStateChanged(state);
});
messageStateSubscription = EasyBind.subscribe(trade.getProcessModel().getPaymentSentMessageStateProperty(), this::onMessageStateChanged);
messageStateSubscription = EasyBind.subscribe(trade.getProcessModel().getPaymentSentMessageStatePropertySeller(), this::onPaymentSentMessageStateChanged);
}
}
}
@ -215,8 +215,8 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
});
}
private void onMessageStateChanged(MessageState messageState) {
messageStateProperty.set(messageState);
private void onPaymentSentMessageStateChanged(MessageState messageState) {
paymentSentMessageStateProperty.set(messageState);
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -52,7 +52,7 @@ public class BuyerStep3View extends TradeStepView {
public void activate() {
super.activate();
model.getMessageStateProperty().addListener(messageStateChangeListener);
model.getPaymentSentMessageStateProperty().addListener(messageStateChangeListener);
updateMessageStateInfo();
}
@ -60,7 +60,7 @@ public class BuyerStep3View extends TradeStepView {
public void deactivate() {
super.deactivate();
model.getMessageStateProperty().removeListener(messageStateChangeListener);
model.getPaymentSentMessageStateProperty().removeListener(messageStateChangeListener);
}
@ -87,7 +87,7 @@ public class BuyerStep3View extends TradeStepView {
}
private void updateMessageStateInfo() {
MessageState messageState = model.getMessageStateProperty().get();
MessageState messageState = model.getPaymentSentMessageStateProperty().get();
textFieldWithIcon.setText(Res.get("message.state." + messageState.name()));
Label iconLabel = textFieldWithIcon.getIconLabel();
switch (messageState) {

View file

@ -270,7 +270,7 @@ Then follow these instructions: https://github.com/haveno-dex/haveno/blob/master
<b>Set the mandatory minimum version for trading (optional)</b>
If applicable, update the mandatory minimum version for trading, by entering `ctrl + f` to open the Filter window, enter a private key with developer privileges, and enter the minimum version (e.g. 1.0.18) in the field labeled "Min. version required for trading".
If applicable, update the mandatory minimum version for trading, by entering `ctrl + f` to open the Filter window, enter a private key with developer privileges, and enter the minimum version (e.g. 1.0.19) in the field labeled "Min. version required for trading".
<b>Send update alert</b>

View file

@ -10,7 +10,7 @@ As per the project's authors, `netlayer` is _"essentially a wrapper around the o
easy use and convenient integration into Kotlin/Java projects"_.
Similarly, `tor-binary` is _"[the] Tor binary packaged in a way that can be used for java projects"_. The project
unpacks the tor browser binaries to extract and repackage the tor binaries themselves.
unpacks the Tor Browser binaries to extract and repackage the tor binaries themselves.
Therefore, upgrading tor in Haveno comes down to upgrading these two artefacts.
@ -22,8 +22,8 @@ Therefore, upgrading tor in Haveno comes down to upgrading these two artefacts.
- Find out which tor version Haveno currently uses
- Find out the current `netlayer` version (see `netlayerVersion` in `haveno/build.gradle`)
- Find that release on the project's [releases page][3]
- The release description says which tor version it includes
- Find that tag on the project's [Tags page][3]
- The tag description says which tor version it includes
- Find out the latest available tor release
- See the [official tor changelog][4]
@ -32,23 +32,24 @@ Therefore, upgrading tor in Haveno comes down to upgrading these two artefacts.
During this update, you will need to keep track of:
- the new tor browser version
- the new Tor Browser version
- the new tor binary version
Create a PR for the `master` branch of [tor-binary][2] with the following changes:
- Decide which tor browser version contains the desired tor binary version
- The official tor browser releases are here: https://dist.torproject.org/torbrowser/
- For the chosen tor browser version, get the list of SHA256 checksums and its signature
- For example, for tor browser 10.0.12:
- https://dist.torproject.org/torbrowser/10.0.12/sha256sums-signed-build.txt
- https://dist.torproject.org/torbrowser/10.0.12/sha256sums-signed-build.txt.asc
- Decide which Tor Browser version contains the desired tor binary version
- The latest official Tor Browser releases are here: https://dist.torproject.org/torbrowser/
- All official Tor Browser releases are here: https://archive.torproject.org/tor-package-archive/torbrowser/
- For the chosen Tor Browser version, get the list of SHA256 checksums and its signature
- For example, for Tor Browser 14.0.7:
- https://dist.torproject.org/torbrowser/14.0.7/sha256sums-signed-build.txt
- https://dist.torproject.org/torbrowser/14.0.7/sha256sums-signed-build.txt.asc
- Verify the signature of the checksums list (see [instructions][5])
- Update the `tor-binary` checksums
- For each file present in `tor-binary/tor-binary-resources/checksums`:
- Rename the file such that it reflects the new tor browser version, but preserves the naming scheme
- Rename the file such that it reflects the new Tor Browser version, but preserves the naming scheme
- Update the contents of the file with the corresponding SHA256 checksum from the list
- Update `torbrowser.version` to the new tor browser version in:
- Update `torbrowser.version` to the new Tor Browser version in:
- `tor-binary/build.xml`
- `tor-binary/pom.xml`
- Update `version` to the new tor binary version in:
@ -72,7 +73,7 @@ next.
### 3. Update `netlayer`
Create a PR for the `externaltor` branch of [netlayer][1] with the following changes:
Create a PR for the `master` branch of [netlayer][1] with the following changes:
- In `netlayer/pom.xml`:
- Update `tor-binary.version` to the `tor-binary` commit ID from above (e.g. `a4b868a`)
@ -82,13 +83,13 @@ Create a PR for the `externaltor` branch of [netlayer][1] with the following cha
- `netlayer/tor.external/pom.xml`
- `netlayer/tor.native/pom.xml`
Once the PR is merged, make a note of the commit ID in the `externaltor` branch (for example `32779ac`), as it will be
Once the PR is merged, make a note of the commit ID in the `master` branch (for example `32779ac`), as it will be
needed next.
Create a tag for the new artefact version, having the new tor binary version as description, for example:
```
# Create tag locally for new netlayer release, on the externaltor branch
# Create tag locally for new netlayer release, on the master branch
git tag -s 0.7.0 -m"tor 0.4.5.6"
# Push it to netlayer repo
@ -105,8 +106,6 @@ Create a Haveno PR with the following changes:
- See instructions in `haveno/gradle/witness/gradle-witness.gradle`
## Credits
Thanks to freimair, JesusMcCloud, mrosseel, sschuberth and cedricwalter for their work on the original
@ -115,8 +114,8 @@ Thanks to freimair, JesusMcCloud, mrosseel, sschuberth and cedricwalter for thei
[1]: https://github.com/bisq-network/netlayer "netlayer"
[2]: https://github.com/bisq-network/tor-binary "tor-binary"
[3]: https://github.com/bisq-network/netlayer/releases "netlayer releases"
[1]: https://github.com/haveno-dex/netlayer "netlayer"
[2]: https://github.com/haveno-dex/tor-binary "tor-binary"
[3]: https://github.com/haveno-dex/netlayer/tags "netlayer Tags"
[4]: https://gitweb.torproject.org/tor.git/plain/ChangeLog "tor changelog"
[5]: https://support.torproject.org/tbb/how-to-verify-signature/ "verify tor signature"

View file

@ -205,44 +205,44 @@
<sha256 value="d4ea711258c783e0accb8feaaa204f0414781551b0159fa17e5f1869200f96f7" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.netlayer" name="tor" version="700ec94f0f">
<artifact name="tor-700ec94f0f.jar">
<sha256 value="63eafc2bf43ae2556d5a24f23b5ddfe371c1ac01b7bc595d6fdb7eadcba37d52" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.netlayer" name="tor" version="d4f9d0ce24">
<artifact name="tor-d4f9d0ce24.jar">
<sha256 value="58c7df7cab675ec3db4f80b456282d49e8d983676af7f053c7514fc5dfb83cee" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.netlayer" name="tor.external" version="700ec94f0f">
<artifact name="tor.external-700ec94f0f.jar">
<sha256 value="01b506ec84697a08abfad2ab1928a8ba8bda36f588f05fbb14e5b9cacdbd0e3d" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.netlayer" name="tor.external" version="d4f9d0ce24">
<artifact name="tor.external-d4f9d0ce24.jar">
<sha256 value="1231429367e83f7c7536cf6febd4df2e95fec81a776f199791d5b9790aa1d25e" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.netlayer" name="tor.native" version="700ec94f0f">
<artifact name="tor.native-700ec94f0f.jar">
<sha256 value="51886b73f9c41d1d16ab7995af9846f67d6f7942def0c182b56a2a801ae301d3" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.netlayer" name="tor.native" version="d4f9d0ce24">
<artifact name="tor.native-d4f9d0ce24.jar">
<sha256 value="5d83e8fb429e0fe79df55b220731567b9290f84bd2f1ee03b2a194284290e052" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-geoip" version="6bdcb0303ae92f4b7bd7102706467f10fcac4d9e">
<artifact name="tor-binary-geoip-6bdcb0303ae92f4b7bd7102706467f10fcac4d9e.jar">
<sha256 value="7bd0fa5e818825d8f0c87a52cc0c468a06fd7850c825b9b36ba82d7a3d9f2fa5" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-geoip" version="462c44c157cbf0b7574b7ab14d0bf231df770a63">
<artifact name="tor-binary-geoip-462c44c157cbf0b7574b7ab14d0bf231df770a63.jar">
<sha256 value="af2c7a517d45c7640f11c28fa5987201ddcb4ea139ef4d56fbcff17336a83289" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-linux32" version="6bdcb0303ae92f4b7bd7102706467f10fcac4d9e">
<artifact name="tor-binary-linux32-6bdcb0303ae92f4b7bd7102706467f10fcac4d9e.jar">
<sha256 value="4b4b3d822d8ad88f874450385751d0b26b41e2724d0d9b703acd9e4b73b3ba5d" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-linux32" version="462c44c157cbf0b7574b7ab14d0bf231df770a63">
<artifact name="tor-binary-linux32-462c44c157cbf0b7574b7ab14d0bf231df770a63.jar">
<sha256 value="6ee4cb8f9cd33bb255fa0e9991e9be49338f574999270434a4499b4554e5e714" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-linux64" version="6bdcb0303ae92f4b7bd7102706467f10fcac4d9e">
<artifact name="tor-binary-linux64-6bdcb0303ae92f4b7bd7102706467f10fcac4d9e.jar">
<sha256 value="512b6d52217feed0efe84c1f43888fc8a8ba32a8998486c32e233a031dddbd94" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-linux64" version="462c44c157cbf0b7574b7ab14d0bf231df770a63">
<artifact name="tor-binary-linux64-462c44c157cbf0b7574b7ab14d0bf231df770a63.jar">
<sha256 value="7da86fc024976ab46b6e92f4f5c0046b7df1f800cd09f297232698b1a6b7f961" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-macos" version="6bdcb0303ae92f4b7bd7102706467f10fcac4d9e">
<artifact name="tor-binary-macos-6bdcb0303ae92f4b7bd7102706467f10fcac4d9e.jar">
<sha256 value="f68f9c6a3f56d084bd9426ff3834bcc90b07a4489357b04ef8151465a73c8783" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-macos" version="462c44c157cbf0b7574b7ab14d0bf231df770a63">
<artifact name="tor-binary-macos-462c44c157cbf0b7574b7ab14d0bf231df770a63.jar">
<sha256 value="f1cd51f9acf7562190fc1ea327325116fcd006f98e16eaef279554f20cca86c3" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-windows" version="6bdcb0303ae92f4b7bd7102706467f10fcac4d9e">
<artifact name="tor-binary-windows-6bdcb0303ae92f4b7bd7102706467f10fcac4d9e.jar">
<sha256 value="94f7090f34dc6f12cdd5e5a247f7f0c4aeb40693258fd5365db41a2b8a71b197" origin="Generated by Gradle"/>
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-windows" version="462c44c157cbf0b7574b7ab14d0bf231df770a63">
<artifact name="tor-binary-windows-462c44c157cbf0b7574b7ab14d0bf231df770a63.jar">
<sha256 value="39ae9a91803dae23fc5573f46f136b4ee973babfd72c3495963f1c430d20f162" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="com.github.johnrengelman" name="shadow" version="8.1.1">

View file

@ -1568,7 +1568,7 @@ message ProcessModel {
bytes payout_tx_signature = 4;
bool use_savings_wallet = 5;
int64 funds_needed_for_trade = 6;
string payment_sent_message_state = 7;
string payment_sent_message_state_seller = 7;
string payment_sent_message_state_arbitrator = 8;
bytes maker_signature = 9;
TradePeer maker = 10;
@ -1581,6 +1581,7 @@ message ProcessModel {
int64 seller_payout_amount_from_mediation = 17;
int64 trade_protocol_error_height = 18;
string trade_fee_address = 19;
bool import_multisig_hex_scheduled = 20;
}
message TradePeer {

View file

@ -41,7 +41,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SeedNodeMain extends ExecutableForAppWithP2p {
private static final long CHECK_CONNECTION_LOSS_SEC = 30;
private static final String VERSION = "1.0.18";
private static final String VERSION = "1.0.19";
private SeedNode seedNode;
private Timer checkConnectionLossTime;