mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-04-14 04:53:00 -04:00
Merge branch 'haveno-dex:master' into haveno-reto-dev
This commit is contained in:
commit
3bd0db513a
6
LICENSE
6
LICENSE
@ -1,7 +1,7 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Copyright (C) 2020 Haveno Dex
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
@ -644,7 +644,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@ -659,4 +659,4 @@ specific requirements.
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
@ -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 = 'd9c60be46d' // 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,
|
||||
|
@ -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.
|
||||
@ -72,6 +72,25 @@ public class Version {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int compare(String version1, String version2) {
|
||||
if (version1.equals(version2))
|
||||
return 0;
|
||||
else if (getMajorVersion(version1) > getMajorVersion(version2))
|
||||
return 1;
|
||||
else if (getMajorVersion(version1) < getMajorVersion(version2))
|
||||
return -1;
|
||||
else if (getMinorVersion(version1) > getMinorVersion(version2))
|
||||
return 1;
|
||||
else if (getMinorVersion(version1) < getMinorVersion(version2))
|
||||
return -1;
|
||||
else if (getPatchVersion(version1) > getPatchVersion(version2))
|
||||
return 1;
|
||||
else if (getPatchVersion(version1) < getPatchVersion(version2))
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getSubVersion(String version, int index) {
|
||||
final String[] split = version.split("\\.");
|
||||
checkArgument(split.length == 3, "Version number must be in semantic version format (contain 2 '.'). version=" + version);
|
||||
@ -91,8 +110,9 @@ public class Version {
|
||||
// For the switch to version 2, offers created with the old version will become invalid and have to be canceled.
|
||||
// For the switch to version 3, offers created with the old version can be migrated to version 3 just by opening
|
||||
// the Haveno app.
|
||||
// VERSION = 0.0.1 -> TRADE_PROTOCOL_VERSION = 1
|
||||
public static final int TRADE_PROTOCOL_VERSION = 1;
|
||||
// Version = 0.0.1 -> TRADE_PROTOCOL_VERSION = 1
|
||||
// Version = 1.0.19 -> TRADE_PROTOCOL_VERSION = 2
|
||||
public static final int TRADE_PROTOCOL_VERSION = 2;
|
||||
private static String p2pMessageVersion;
|
||||
|
||||
public static String getP2PMessageVersion() {
|
||||
|
@ -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)));
|
||||
@ -265,6 +265,7 @@ public class CoreOffersService {
|
||||
if (!seenKeyImages.add(keyImage)) {
|
||||
for (Offer offer2 : offers) {
|
||||
if (offer == offer2) continue;
|
||||
if (offer2.getOfferPayload().getReserveTxKeyImages() == null) continue;
|
||||
if (offer2.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) {
|
||||
log.warn("Key image {} belongs to multiple offers, seen in offer {} and {}", keyImage, offer.getId(), offer2.getId());
|
||||
duplicateFundedOffers.add(offer2);
|
||||
|
@ -178,6 +178,9 @@ public class DomainInitialisation {
|
||||
closedTradableManager.onAllServicesInitialized();
|
||||
failedTradesManager.onAllServicesInitialized();
|
||||
|
||||
filterManager.setFilterWarningHandler(filterWarningHandler);
|
||||
filterManager.onAllServicesInitialized();
|
||||
|
||||
openOfferManager.onAllServicesInitialized();
|
||||
|
||||
balances.onAllServicesInitialized();
|
||||
@ -199,10 +202,6 @@ public class DomainInitialisation {
|
||||
priceFeedService.setCurrencyCodeOnInit();
|
||||
priceFeedService.startRequestingPrices();
|
||||
|
||||
filterManager.setFilterWarningHandler(filterWarningHandler);
|
||||
filterManager.onAllServicesInitialized();
|
||||
|
||||
|
||||
mobileNotificationService.onAllServicesInitialized();
|
||||
myOfferTakenEvents.onAllServicesInitialized();
|
||||
tradeEvents.onAllServicesInitialized();
|
||||
|
@ -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"));
|
||||
|
@ -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,
|
||||
() -> {},
|
||||
() -> {});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -407,6 +407,10 @@ public class FilterManager {
|
||||
.anyMatch(e -> e.equals(address));
|
||||
}
|
||||
|
||||
public String getDisableTradeBelowVersion() {
|
||||
return getFilter() == null || getFilter().getDisableTradeBelowVersion() == null || getFilter().getDisableTradeBelowVersion().isEmpty() ? null : getFilter().getDisableTradeBelowVersion();
|
||||
}
|
||||
|
||||
public boolean requireUpdateToNewVersionForTrading() {
|
||||
if (getFilter() == null) {
|
||||
return false;
|
||||
|
@ -97,6 +97,7 @@ import haveno.network.p2p.peers.PeerManager;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -236,7 +237,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 +574,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 +687,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();
|
||||
@ -736,7 +737,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
doCancelOffer(openOffer, true);
|
||||
}
|
||||
|
||||
// remove open offer which thaws its key images
|
||||
// cancel open offer which thaws its key images
|
||||
private void doCancelOffer(@NotNull OpenOffer openOffer, boolean resetAddressEntries) {
|
||||
Offer offer = openOffer.getOffer();
|
||||
offer.setState(Offer.State.REMOVED);
|
||||
@ -750,7 +751,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 +814,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) {
|
||||
@ -888,6 +889,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
List<String> errorMessages = new ArrayList<String>();
|
||||
synchronized (processOffersLock) {
|
||||
List<OpenOffer> openOffers = getOpenOffers();
|
||||
removeOffersWithDuplicateKeyImages(openOffers);
|
||||
for (OpenOffer pendingOffer : openOffers) {
|
||||
if (pendingOffer.getState() != OpenOffer.State.PENDING) continue;
|
||||
if (skipOffersWithTooManyAttempts && pendingOffer.getNumProcessingAttempts() > NUM_ATTEMPTS_THRESHOLD) continue; // skip offers with too many attempts
|
||||
@ -919,6 +921,28 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
}, THREAD_ID);
|
||||
}
|
||||
|
||||
private void removeOffersWithDuplicateKeyImages(List<OpenOffer> openOffers) {
|
||||
|
||||
// collect offers with duplicate key images
|
||||
Set<String> keyImages = new HashSet<>();
|
||||
Set<OpenOffer> offersToRemove = new HashSet<>();
|
||||
for (OpenOffer openOffer : openOffers) {
|
||||
if (openOffer.getOffer().getOfferPayload().getReserveTxKeyImages() == null) continue;
|
||||
if (Collections.disjoint(keyImages, openOffer.getOffer().getOfferPayload().getReserveTxKeyImages())) {
|
||||
keyImages.addAll(openOffer.getOffer().getOfferPayload().getReserveTxKeyImages());
|
||||
} else {
|
||||
offersToRemove.add(openOffer);
|
||||
}
|
||||
}
|
||||
|
||||
// remove offers with duplicate key images
|
||||
for (OpenOffer offerToRemove : offersToRemove) {
|
||||
log.warn("Removing open offer which has duplicate key images with other open offers: {}", offerToRemove.getId());
|
||||
doCancelOffer(offerToRemove);
|
||||
openOffers.remove(offerToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private void processPendingOffer(List<OpenOffer> openOffers, OpenOffer openOffer, TransactionResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
|
||||
// skip if already processing
|
||||
@ -987,26 +1011,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 +1031,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 +1100,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 +1128,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 +1340,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);
|
||||
|
||||
@ -1355,6 +1396,40 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
return;
|
||||
}
|
||||
|
||||
// verify max length of extra info
|
||||
if (offer.getOfferPayload().getExtraInfo() != null && offer.getOfferPayload().getExtraInfo().length() > Restrictions.MAX_EXTRA_INFO_LENGTH) {
|
||||
errorMessage = "Extra info is too long for offer " + request.offerId + ". Max length is " + Restrictions.MAX_EXTRA_INFO_LENGTH + " but got " + offer.getOfferPayload().getExtraInfo().length();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify the trade protocol version
|
||||
if (request.getOfferPayload().getProtocolVersion() != Version.TRADE_PROTOCOL_VERSION) {
|
||||
errorMessage = "Unsupported protocol version: " + request.getOfferPayload().getProtocolVersion();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify the min version number
|
||||
if (filterManager.getDisableTradeBelowVersion() != null) {
|
||||
if (Version.compare(request.getOfferPayload().getVersionNr(), filterManager.getDisableTradeBelowVersion()) < 0) {
|
||||
errorMessage = "Offer version number is too low: " + request.getOfferPayload().getVersionNr() + " < " + filterManager.getDisableTradeBelowVersion();
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// verify the max version number
|
||||
if (Version.compare(request.getOfferPayload().getVersionNr(), Version.VERSION) > 0) {
|
||||
errorMessage = "Offer version number is too high: " + request.getOfferPayload().getVersionNr() + " > " + Version.VERSION;
|
||||
log.warn(errorMessage);
|
||||
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// verify maker and taker fees
|
||||
boolean hasBuyerAsTakerWithoutDeposit = offer.getDirection() == OfferDirection.SELL && offer.isPrivateOffer() && offer.getChallengeHash() != null && offer.getChallengeHash().length() > 0 && offer.getTakerFeePct() == 0;
|
||||
if (hasBuyerAsTakerWithoutDeposit) {
|
||||
@ -1557,6 +1632,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 +1658,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()) {
|
||||
@ -1801,27 +1884,20 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
originalOfferPayload.getChallengeHash(),
|
||||
updatedExtraDataMap,
|
||||
protocolVersion,
|
||||
originalOfferPayload.getArbitratorSigner(),
|
||||
originalOfferPayload.getArbitratorSignature(),
|
||||
originalOfferPayload.getReserveTxKeyImages(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
originalOfferPayload.getExtraInfo());
|
||||
|
||||
// Save states from original data to use for the updated
|
||||
Offer.State originalOfferState = originalOffer.getState();
|
||||
OpenOffer.State originalOpenOfferState = originalOpenOffer.getState();
|
||||
// cancel old offer
|
||||
log.info("Canceling outdated offer id={}", originalOffer.getId());
|
||||
doCancelOffer(originalOpenOffer, false);
|
||||
|
||||
// remove old offer
|
||||
originalOffer.setState(Offer.State.REMOVED);
|
||||
originalOpenOffer.setState(OpenOffer.State.CANCELED);
|
||||
removeOpenOffer(originalOpenOffer);
|
||||
|
||||
// Create new Offer
|
||||
// create new offer
|
||||
Offer updatedOffer = new Offer(updatedPayload);
|
||||
updatedOffer.setPriceFeedService(priceFeedService);
|
||||
updatedOffer.setState(originalOfferState);
|
||||
|
||||
OpenOffer updatedOpenOffer = new OpenOffer(updatedOffer, originalOpenOffer.getTriggerPrice());
|
||||
updatedOpenOffer.setState(originalOpenOfferState);
|
||||
addOpenOffer(updatedOpenOffer);
|
||||
requestPersistence();
|
||||
|
||||
@ -1961,6 +2037,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 +2060,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.");
|
||||
|
@ -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);
|
||||
|
@ -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) -> {
|
||||
|
@ -52,7 +52,8 @@ public class ProvidersRepository {
|
||||
private static final String DEFAULT_LOCAL_NODE = "http://localhost:8078/";
|
||||
private static final List<String> DEFAULT_NODES = Arrays.asList(
|
||||
"http://elaxlgigphpicy5q7pi5wkz2ko2vgjbq4576vic7febmx4xcxvk6deqd.onion/", // Haveno
|
||||
"http://lrrgpezvdrbpoqvkavzobmj7dr2otxc5x6wgktrw337bk6mxsvfp5yid.onion/" // Cake
|
||||
"http://lrrgpezvdrbpoqvkavzobmj7dr2otxc5x6wgktrw337bk6mxsvfp5yid.onion/", // Cake
|
||||
"http://2c6y3sqmknakl3fkuwh4tjhxb2q5isr53dnfcqs33vt3y7elujc6tyad.onion/" // boldsuck
|
||||
);
|
||||
|
||||
private final Config config;
|
||||
|
@ -399,13 +399,6 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
chatMessage.setSystemMessage(true);
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
|
||||
// export latest multisig hex
|
||||
try {
|
||||
trade.exportMultisigHex();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to export multisig hex", e);
|
||||
}
|
||||
|
||||
// create dispute opened message
|
||||
NodeAddress agentNodeAddress = getAgentNodeAddress(dispute);
|
||||
DisputeOpenedMessage disputeOpenedMessage = new DisputeOpenedMessage(dispute,
|
||||
@ -578,8 +571,9 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
trade.advanceState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
}
|
||||
|
||||
// update multisig hex
|
||||
if (message.getUpdatedMultisigHex() != null) sender.setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
// update opener's multisig hex
|
||||
TradePeer opener = sender == trade.getArbitrator() ? trade.getTradePeer() : sender;
|
||||
if (message.getOpenerUpdatedMultisigHex() != null) opener.setUpdatedMultisigHex(message.getOpenerUpdatedMultisigHex());
|
||||
|
||||
// add chat message with price info
|
||||
if (trade instanceof ArbitratorTrade) addPriceInfoMessage(dispute, 0);
|
||||
@ -605,7 +599,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||
if (trade.isArbitrator()) {
|
||||
TradePeer senderPeer = sender == trade.getMaker() ? trade.getTaker() : trade.getMaker();
|
||||
if (senderPeer != trade.getMaker() && senderPeer != trade.getTaker()) throw new RuntimeException("Sender peer is not maker or taker, address=" + senderPeer.getNodeAddress());
|
||||
sendDisputeOpenedMessageToPeer(dispute, contract, senderPeer.getPubKeyRing(), trade.getSelf().getUpdatedMultisigHex());
|
||||
sendDisputeOpenedMessageToPeer(dispute, contract, senderPeer.getPubKeyRing(), opener.getUpdatedMultisigHex());
|
||||
}
|
||||
tradeManager.requestPersistence();
|
||||
errorMessage = null;
|
||||
|
@ -62,7 +62,6 @@ import haveno.core.support.dispute.messages.DisputeClosedMessage;
|
||||
import haveno.core.support.dispute.messages.DisputeOpenedMessage;
|
||||
import haveno.core.support.messages.ChatMessage;
|
||||
import haveno.core.support.messages.SupportMessage;
|
||||
import haveno.core.trade.BuyerTrade;
|
||||
import haveno.core.trade.ClosedTradableManager;
|
||||
import haveno.core.trade.Contract;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
@ -464,14 +463,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||
// check daemon connection
|
||||
trade.verifyDaemonConnection();
|
||||
|
||||
// adapt from 1.0.6 to 1.0.7 which changes field usage
|
||||
// TODO: remove after future updates to allow old trades to clear
|
||||
if (trade.getPayoutTxHex() != null && trade.getBuyer().getPaymentSentMessage() != null && trade.getPayoutTxHex().equals(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex())) {
|
||||
log.warn("Nullifying payout tx hex after 1.0.7 update {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
if (trade instanceof BuyerTrade) trade.getSelf().setUnsignedPayoutTxHex(trade.getPayoutTxHex());
|
||||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// sign arbitrator-signed payout tx
|
||||
if (trade.getPayoutTxHex() == null) {
|
||||
try {
|
||||
|
@ -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);
|
||||
|
@ -34,7 +34,7 @@ import java.util.Optional;
|
||||
public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
private final Dispute dispute;
|
||||
private final NodeAddress senderNodeAddress;
|
||||
private final String updatedMultisigHex;
|
||||
private final String openerUpdatedMultisigHex;
|
||||
private final PaymentSentMessage paymentSentMessage;
|
||||
|
||||
public DisputeOpenedMessage(Dispute dispute,
|
||||
@ -67,7 +67,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
super(messageVersion, uid, supportType);
|
||||
this.dispute = dispute;
|
||||
this.senderNodeAddress = senderNodeAddress;
|
||||
this.updatedMultisigHex = updatedMultisigHex;
|
||||
this.openerUpdatedMultisigHex = updatedMultisigHex;
|
||||
this.paymentSentMessage = paymentSentMessage;
|
||||
}
|
||||
|
||||
@ -78,7 +78,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
.setDispute(dispute.toProtoMessage())
|
||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||
.setType(SupportType.toProtoMessage(supportType))
|
||||
.setUpdatedMultisigHex(updatedMultisigHex);
|
||||
.setOpenerUpdatedMultisigHex(openerUpdatedMultisigHex);
|
||||
Optional.ofNullable(paymentSentMessage).ifPresent(e -> builder.setPaymentSentMessage(paymentSentMessage.toProtoNetworkEnvelope().getPaymentSentMessage()));
|
||||
return getNetworkEnvelopeBuilder().setDisputeOpenedMessage(builder).build();
|
||||
}
|
||||
@ -91,7 +91,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
proto.getUid(),
|
||||
messageVersion,
|
||||
SupportType.fromProto(proto.getType()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()),
|
||||
ProtoUtil.stringOrNullFromProto(proto.getOpenerUpdatedMultisigHex()),
|
||||
proto.hasPaymentSentMessage() ? PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion) : null);
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ public final class DisputeOpenedMessage extends DisputeMessage {
|
||||
",\n DisputeOpenedMessage.uid='" + uid + '\'' +
|
||||
",\n messageVersion=" + messageVersion +
|
||||
",\n supportType=" + supportType +
|
||||
",\n updatedMultisigHex=" + updatedMultisigHex +
|
||||
",\n openerUpdatedMultisigHex=" + openerUpdatedMultisigHex +
|
||||
",\n paymentSentMessage=" + paymentSentMessage +
|
||||
"\n} " + super.toString();
|
||||
}
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
@ -194,7 +195,8 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
SELLER_SENT_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED);
|
||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||
BUYER_RECEIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED);
|
||||
|
||||
@NotNull
|
||||
public Phase getPhase() {
|
||||
@ -602,12 +604,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
// notified from TradeProtocol of ack messages
|
||||
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
// notified from TradeProtocol of ack messages
|
||||
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
for (TradeListener listener : new ArrayList<TradeListener>(tradeListeners)) { // copy array to allow listener invocation to unregister listener without concurrent modification exception
|
||||
listener.onAckMessage(ackMessage, sender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -617,8 +619,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
public void initialize(ProcessModelServiceProvider serviceProvider) {
|
||||
if (isInitialized) throw new IllegalStateException(getClass().getSimpleName() + " " + getId() + " is already initialized");
|
||||
|
||||
// done if payout unlocked and marked complete
|
||||
if (isPayoutUnlocked() && isCompleted()) {
|
||||
// skip initialization if trade is complete
|
||||
// starting in v1.0.19, seller resends payment received message until acked or stored in mailbox
|
||||
if (isFinished()) {
|
||||
clearAndShutDown();
|
||||
return;
|
||||
}
|
||||
@ -633,16 +636,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
ThreadUtils.execute(() -> onConnectionChanged(connection), getId());
|
||||
});
|
||||
|
||||
// reset buyer's payment sent state if no ack receive
|
||||
if (this instanceof BuyerTrade && getState().ordinal() >= Trade.State.BUYER_CONFIRMED_PAYMENT_SENT.ordinal() && getState().ordinal() < Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
setState(Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
}
|
||||
// reset states if no ack receive
|
||||
if (!isPayoutPublished()) {
|
||||
|
||||
// reset seller's payment received state if no ack receive
|
||||
if (this instanceof SellerTrade && getState().ordinal() >= Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT.ordinal() && getState().ordinal() < Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
// reset buyer's payment sent state if no ack receive
|
||||
if (this instanceof BuyerTrade && getState().ordinal() >= Trade.State.BUYER_CONFIRMED_PAYMENT_SENT.ordinal() && getState().ordinal() < Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
setState(Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||
}
|
||||
|
||||
// reset seller's payment received state if no ack receive
|
||||
if (this instanceof SellerTrade && getState().ordinal() >= Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT.ordinal() && getState().ordinal() < Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG.ordinal()) {
|
||||
log.warn("Resetting state of {} {} from {} to {} because no ack was received", getClass().getSimpleName(), getId(), getState(), Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
resetToPaymentSentState();
|
||||
}
|
||||
}
|
||||
|
||||
// handle trade state events
|
||||
@ -732,15 +739,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
xmrWalletService.addWalletListener(idlePayoutSyncer);
|
||||
}
|
||||
|
||||
// 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?
|
||||
// TODO: buyer's payment sent message state property became unsynced if shut down while awaiting ack from seller. fixed mismatch in v1.0.19, but can 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 != getSeller().getPaymentSentMessageStateProperty().get()) {
|
||||
log.warn("Updating unexpected payment sent message state for {} {}, expected={}, actual={}", getClass().getSimpleName(), getId(), expectedState, processModel.getPaymentSentMessageStatePropertySeller().get());
|
||||
getSeller().getPaymentSentMessageStateProperty().set(expectedState);
|
||||
}
|
||||
}
|
||||
|
||||
// handle confirmations
|
||||
walletHeight.addListener((observable, oldValue, newValue) -> {
|
||||
importMultisigHexIfScheduled();
|
||||
});
|
||||
|
||||
// trade is initialized
|
||||
isInitialized = true;
|
||||
|
||||
@ -765,11 +777,30 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
// start polling if deposit requested
|
||||
if (isDepositRequested()) tryInitPolling();
|
||||
// init syncing if deposit requested
|
||||
if (isDepositRequested()) {
|
||||
tryInitSyncing();
|
||||
}
|
||||
isFullyInitialized = true;
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return isPayoutUnlocked() && isCompleted() && !getProtocol().needsToResendPaymentReceivedMessages();
|
||||
}
|
||||
|
||||
public void resetToPaymentSentState() {
|
||||
setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
for (TradePeer peer : getAllPeers()) peer.setPaymentReceivedMessage(null);
|
||||
setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
public void reprocessApplicableMessages() {
|
||||
if (!isDepositRequested() || isPayoutUnlocked() || isCompleted()) return;
|
||||
getProtocol().maybeReprocessPaymentSentMessage(false);
|
||||
getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||
}
|
||||
|
||||
public void awaitInitialized() {
|
||||
while (!isFullyInitialized) HavenoUtils.waitFor(100); // TODO: use proper notification and refactor isInitialized, fullyInitialized, and arbitrator idling
|
||||
}
|
||||
@ -1077,6 +1108,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 +1140,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 +1192,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
|
||||
@ -1335,7 +1389,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
MoneroTxSet describedTxSet = wallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(payoutTxHex));
|
||||
if (describedTxSet.getTxs() == null || describedTxSet.getTxs().size() != 1) throw new IllegalArgumentException("Bad payout tx"); // TODO (woodser): test nack
|
||||
MoneroTxWallet payoutTx = describedTxSet.getTxs().get(0);
|
||||
if (payoutTxId == null) updatePayout(payoutTx); // update payout tx if not signed
|
||||
if (payoutTxId == null) updatePayout(payoutTx); // update payout tx if id currently unknown
|
||||
|
||||
// verify payout tx has exactly 2 destinations
|
||||
if (payoutTx.getOutgoingTransfer() == null || payoutTx.getOutgoingTransfer().getDestinations() == null || payoutTx.getOutgoingTransfer().getDestinations().size() != 2) throw new IllegalArgumentException("Payout tx does not have exactly two destinations");
|
||||
@ -1366,6 +1420,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
BigInteger expectedSellerPayout = sellerDepositAmount.subtract(tradeAmount).subtract(txCostSplit);
|
||||
if (!sellerPayoutDestination.getAmount().equals(expectedSellerPayout)) throw new IllegalArgumentException("Seller destination amount is not deposit amount - trade amount - 1/2 tx costs, " + sellerPayoutDestination.getAmount() + " vs " + expectedSellerPayout);
|
||||
|
||||
// update payout tx
|
||||
updatePayout(payoutTx);
|
||||
|
||||
// check connection
|
||||
boolean doSign = sign && getPayoutTxHex() == null;
|
||||
if (doSign || publish) verifyDaemonConnection();
|
||||
@ -1374,28 +1431,36 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
if (doSign) {
|
||||
|
||||
// sign tx
|
||||
String signedPayoutTxHex;
|
||||
try {
|
||||
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
|
||||
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
|
||||
setPayoutTxHex(result.getSignedMultisigTxHex());
|
||||
signedPayoutTxHex = result.getSignedMultisigTxHex();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
// verify miner fee is within tolerance unless outdated offer version
|
||||
if (getOffer().getOfferPayload().getProtocolVersion() >= 2) {
|
||||
|
||||
// verify fee is within tolerance by recreating payout tx
|
||||
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
|
||||
log.info("Creating fee estimate tx for {} {}", getClass().getSimpleName(), getId());
|
||||
saveWallet(); // save wallet before creating fee estimate tx
|
||||
MoneroTxWallet feeEstimateTx = createPayoutTx();
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee();
|
||||
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||
if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + payoutTx.getFee());
|
||||
log.info("Payout tx fee {} is within tolerance, diff %={}", payoutTx.getFee(), feeDiff);
|
||||
}
|
||||
|
||||
// set signed payout tx hex
|
||||
setPayoutTxHex(signedPayoutTxHex);
|
||||
|
||||
// describe result
|
||||
describedTxSet = wallet.describeMultisigTxSet(getPayoutTxHex());
|
||||
payoutTx = describedTxSet.getTxs().get(0);
|
||||
updatePayout(payoutTx);
|
||||
|
||||
// verify fee is within tolerance by recreating payout tx
|
||||
// TODO (monero-project): creating tx will require exchanging updated multisig hex if message needs reprocessed. provide weight with describe_transfer so fee can be estimated?
|
||||
log.info("Creating fee estimate tx for {} {}", getClass().getSimpleName(), getId());
|
||||
saveWallet(); // save wallet before creating fee estimate tx
|
||||
MoneroTxWallet feeEstimateTx = createPayoutTx();
|
||||
BigInteger feeEstimate = feeEstimateTx.getFee();
|
||||
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
|
||||
if (feeDiff > XmrWalletService.MINER_FEE_TOLERANCE) throw new IllegalArgumentException("Miner fee is not within " + (XmrWalletService.MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + payoutTx.getFee());
|
||||
log.info("Payout tx fee {} is within tolerance, diff %={}", payoutTx.getFee(), feeDiff);
|
||||
}
|
||||
|
||||
// save trade state
|
||||
@ -1506,7 +1571,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
peer.setUpdatedMultisigHex(null);
|
||||
peer.setDisputeClosedMessage(null);
|
||||
peer.setPaymentSentMessage(null);
|
||||
peer.setPaymentReceivedMessage(null);
|
||||
if (peer.isPaymentReceivedMessageReceived()) peer.setPaymentReceivedMessage(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1604,7 +1669,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 +1683,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 +1746,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()));
|
||||
}
|
||||
@ -1868,10 +1938,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
getSeller().setPayoutTxFee(splitTxFee);
|
||||
getBuyer().setPayoutAmount(getBuyer().getSecurityDeposit().subtract(getBuyer().getPayoutTxFee()).add(getAmount()));
|
||||
getSeller().setPayoutAmount(getSeller().getSecurityDeposit().subtract(getSeller().getPayoutTxFee()));
|
||||
} else if (getDisputeState().isClosed()) {
|
||||
} else {
|
||||
DisputeResult disputeResult = getDisputeResult();
|
||||
if (disputeResult == null) log.warn("Dispute result is not set for {} {}", getClass().getSimpleName(), getId());
|
||||
else {
|
||||
if (disputeResult != null) {
|
||||
BigInteger[] buyerSellerPayoutTxFees = ArbitrationManager.getBuyerSellerPayoutTxCost(disputeResult, payoutTx.getFee());
|
||||
getBuyer().setPayoutTxFee(buyerSellerPayoutTxFees[0]);
|
||||
getSeller().setPayoutTxFee(buyerSellerPayoutTxFees[1]);
|
||||
@ -2015,9 +2084,9 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
throw new IllegalArgumentException("Trade is not buyer, seller, or arbitrator");
|
||||
}
|
||||
|
||||
public MessageState getPaymentSentMessageState() {
|
||||
private MessageState getPaymentSentMessageState() {
|
||||
if (isPaymentReceived()) return MessageState.ACKNOWLEDGED;
|
||||
if (processModel.getPaymentSentMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return MessageState.ACKNOWLEDGED;
|
||||
if (getSeller().getPaymentSentMessageStateProperty().get() == MessageState.ACKNOWLEDGED) return MessageState.ACKNOWLEDGED;
|
||||
switch (state) {
|
||||
case BUYER_SENT_PAYMENT_SENT_MSG:
|
||||
return MessageState.SENT;
|
||||
@ -2156,7 +2225,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
|
||||
public boolean isPaymentSent() {
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_SENT.ordinal();
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_SENT.ordinal() && getState() != State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG;
|
||||
}
|
||||
|
||||
public boolean hasPaymentReceivedMessage() {
|
||||
@ -2174,7 +2243,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
|
||||
public boolean isPaymentReceived() {
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal() && getState() != State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG;
|
||||
}
|
||||
|
||||
public boolean isPayoutPublished() {
|
||||
@ -2345,7 +2414,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 +2435,6 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// lazy initialization
|
||||
private ObjectProperty<BigInteger> getAmountProperty() {
|
||||
if (tradeAmountProperty == null)
|
||||
@ -2410,11 +2479,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
|
||||
// sync and reprocess messages on new thread
|
||||
if (isInitialized && connection != null && !Boolean.FALSE.equals(xmrConnectionService.isConnected())) {
|
||||
ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||
ThreadUtils.execute(() -> tryInitSyncing(), getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
private void tryInitPolling() {
|
||||
|
||||
private void tryInitSyncing() {
|
||||
if (isShutDownStarted) return;
|
||||
|
||||
// set known deposit txs
|
||||
@ -2423,23 +2493,18 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
|
||||
// start polling
|
||||
if (!isIdling()) {
|
||||
tryInitPollingAux();
|
||||
doTryInitSyncing();
|
||||
} else {
|
||||
long startSyncingInMs = ThreadLocalRandom.current().nextLong(0, getPollPeriod()); // random time to start polling
|
||||
UserThread.runAfter(() -> ThreadUtils.execute(() -> {
|
||||
if (!isShutDownStarted) tryInitPollingAux();
|
||||
if (!isShutDownStarted) doTryInitSyncing();
|
||||
}, getId()), startSyncingInMs / 1000l);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryInitPollingAux() {
|
||||
private void doTryInitSyncing() {
|
||||
if (!wasWalletSynced) trySyncWallet(true);
|
||||
updatePollPeriod();
|
||||
|
||||
// reprocess pending payout messages
|
||||
this.getProtocol().maybeReprocessPaymentReceivedMessage(false);
|
||||
HavenoUtils.arbitrationManager.maybeReprocessDisputeClosedMessage(this, false);
|
||||
|
||||
startPolling();
|
||||
}
|
||||
|
||||
@ -2790,7 +2855,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
|
||||
if (!isShutDownStarted) wallet = getWallet();
|
||||
restartInProgress = false;
|
||||
pollWallet();
|
||||
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitPolling(), getId());
|
||||
if (!isShutDownStarted) ThreadUtils.execute(() -> tryInitSyncing(), getId());
|
||||
}
|
||||
|
||||
private void setStateDepositsSeen() {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -44,6 +44,7 @@ import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.filter.FilterManager;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.offer.OfferDirection;
|
||||
import haveno.core.offer.OpenOfferManager;
|
||||
import haveno.core.payment.PaymentAccount;
|
||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||
@ -73,6 +74,9 @@ import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
||||
@ -90,6 +94,7 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
transient private ProcessModelServiceProvider provider;
|
||||
transient private TradeManager tradeManager;
|
||||
transient private Offer offer;
|
||||
transient public Throwable error;
|
||||
|
||||
// Added in v1.4.0
|
||||
// MessageState of the last message sent from the seller to the buyer in the take offer process.
|
||||
@ -158,15 +163,14 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
@Getter
|
||||
@Setter
|
||||
private long tradeProtocolErrorHeight;
|
||||
|
||||
// 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.
|
||||
@Getter
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
private boolean importMultisigHexScheduled;
|
||||
private ObjectProperty<Boolean> paymentAccountDecryptedProperty = new SimpleObjectProperty<>(false);
|
||||
@Deprecated
|
||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertySeller = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Deprecated
|
||||
private ObjectProperty<MessageState> paymentSentMessageStatePropertyArbitrator = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
|
||||
public ProcessModel(String offerId, String accountId, PubKeyRing pubKeyRing) {
|
||||
this(offerId, accountId, pubKeyRing, new TradePeer(), new TradePeer(), new TradePeer());
|
||||
@ -188,6 +192,31 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
this.offer = offer;
|
||||
this.provider = provider;
|
||||
this.tradeManager = tradeManager;
|
||||
for (TradePeer peer : getTradePeers()) {
|
||||
peer.applyTransient(tradeManager);
|
||||
}
|
||||
|
||||
// migrate deprecated fields to new model for v1.0.19
|
||||
if (paymentSentMessageStatePropertySeller.get() != MessageState.UNDEFINED && getSeller().getPaymentSentMessageStateProperty().get() == MessageState.UNDEFINED) {
|
||||
getSeller().getPaymentSentMessageStateProperty().set(paymentSentMessageStatePropertySeller.get());
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
if (paymentSentMessageStatePropertyArbitrator.get() != MessageState.UNDEFINED && getArbitrator().getPaymentSentMessageStateProperty().get() == MessageState.UNDEFINED) {
|
||||
getArbitrator().getPaymentSentMessageStateProperty().set(paymentSentMessageStatePropertyArbitrator.get());
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
private List<TradePeer> getTradePeers() {
|
||||
return Arrays.asList(maker, taker, arbitrator);
|
||||
}
|
||||
|
||||
private TradePeer getBuyer() {
|
||||
return offer.getDirection() == OfferDirection.BUY ? maker : taker;
|
||||
}
|
||||
|
||||
private TradePeer getSeller() {
|
||||
return offer.getDirection() == OfferDirection.BUY ? taker : maker;
|
||||
}
|
||||
|
||||
|
||||
@ -203,11 +232,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 +261,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,14 +271,13 @@ 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);
|
||||
|
||||
// deprecated fields need to be read in order to migrate to new fields
|
||||
String paymentSentMessageStateSellerString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageStateSeller());
|
||||
MessageState paymentSentMessageStateSeller = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateSellerString);
|
||||
processModel.paymentSentMessageStatePropertySeller.set(paymentSentMessageStateSeller);
|
||||
String paymentSentMessageStateArbitratorString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageStateArbitrator());
|
||||
MessageState paymentSentMessageStateArbitrator = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateArbitratorString);
|
||||
processModel.setPaymentSentMessageStateArbitrator(paymentSentMessageStateArbitrator);
|
||||
|
||||
processModel.paymentSentMessageStatePropertyArbitrator.set(paymentSentMessageStateArbitrator);
|
||||
return processModel;
|
||||
}
|
||||
|
||||
@ -274,32 +304,8 @@ public class ProcessModel implements Model, PersistablePayload {
|
||||
return getP2PService().getAddress();
|
||||
}
|
||||
|
||||
void setPaymentSentAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentSentMessageState(messageState);
|
||||
}
|
||||
|
||||
void setPaymentSentAckMessageArbitrator(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentSentMessageStateArbitrator(messageState);
|
||||
}
|
||||
|
||||
public void setPaymentSentMessageState(MessageState paymentSentMessageStateProperty) {
|
||||
this.paymentSentMessageStateProperty.set(paymentSentMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaymentSentMessageStateArbitrator(MessageState paymentSentMessageStateProperty) {
|
||||
this.paymentSentMessageStatePropertyArbitrator.set(paymentSentMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
public boolean isPaymentReceivedMessagesReceived() {
|
||||
return getArbitrator().isPaymentReceivedMessageReceived() && getBuyer().isPaymentReceivedMessageReceived();
|
||||
}
|
||||
|
||||
void setDepositTxSentAckMessage(AckMessage ackMessage) {
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -53,6 +53,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SellerProtocol extends DisputeProtocol {
|
||||
|
||||
enum SellerEvent implements FluentProtocol.Event {
|
||||
STARTUP,
|
||||
DEPOSIT_TXS_CONFIRMED,
|
||||
@ -69,31 +70,37 @@ public class SellerProtocol extends DisputeProtocol {
|
||||
|
||||
// re-send payment received message if payout not published
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
if (!needsToResendPaymentReceivedMessages()) return;
|
||||
synchronized (trade.getLock()) {
|
||||
if (trade.isShutDownStarted() || trade.isPayoutPublished()) return;
|
||||
if (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !trade.isPayoutPublished()) {
|
||||
latchTrade();
|
||||
given(anyPhase(Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(SellerEvent.STARTUP))
|
||||
.setup(tasks(
|
||||
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
unlatchTrade();
|
||||
},
|
||||
(errorMessage) -> {
|
||||
log.warn("Error sending PaymentReceivedMessage on startup: " + errorMessage);
|
||||
unlatchTrade();
|
||||
})))
|
||||
.executeTasks();
|
||||
awaitTradeLatch();
|
||||
}
|
||||
if (!needsToResendPaymentReceivedMessages()) return;
|
||||
latchTrade();
|
||||
given(anyPhase(Trade.Phase.PAYMENT_RECEIVED)
|
||||
.with(SellerEvent.STARTUP))
|
||||
.setup(tasks(
|
||||
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||
.using(new TradeTaskRunner(trade,
|
||||
() -> {
|
||||
unlatchTrade();
|
||||
},
|
||||
(errorMessage) -> {
|
||||
log.warn("Error sending PaymentReceivedMessage on startup: " + errorMessage);
|
||||
unlatchTrade();
|
||||
})))
|
||||
.executeTasks();
|
||||
awaitTradeLatch();
|
||||
}
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
public boolean needsToResendPaymentReceivedMessages() {
|
||||
return !trade.isShutDownStarted() && trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && !trade.getProcessModel().isPaymentReceivedMessagesReceived() && resendPaymentReceivedMessagesEnabled();
|
||||
}
|
||||
|
||||
private boolean resendPaymentReceivedMessagesEnabled() {
|
||||
return trade.getOffer().getOfferPayload().getProtocolVersion() >= 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
||||
super.onTradeMessage(message, peer);
|
||||
@ -115,7 +122,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();
|
||||
@ -137,7 +144,7 @@ public class SellerProtocol extends DisputeProtocol {
|
||||
resultHandler.handleResult();
|
||||
}, (errorMessage) -> {
|
||||
log.warn("Error confirming payment received, reverting state to {}, error={}", Trade.State.BUYER_SENT_PAYMENT_SENT_MSG, errorMessage);
|
||||
trade.setState(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
trade.resetToPaymentSentState();
|
||||
handleTaskRunnerFault(event, errorMessage);
|
||||
})))
|
||||
.run(() -> trade.advanceState(Trade.State.SELLER_CONFIRMED_PAYMENT_RECEIPT))
|
||||
|
@ -24,12 +24,17 @@ import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.proto.ProtoUtil;
|
||||
import haveno.common.proto.persistable.PersistablePayload;
|
||||
import haveno.core.account.witness.AccountAgeWitness;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||
import haveno.core.proto.CoreProtoResolver;
|
||||
import haveno.core.support.dispute.messages.DisputeClosedMessage;
|
||||
import haveno.core.trade.TradeManager;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
import haveno.core.trade.messages.PaymentSentMessage;
|
||||
import haveno.network.p2p.AckMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -57,6 +62,7 @@ public final class TradePeer implements PersistablePayload {
|
||||
@Nullable
|
||||
transient private byte[] preparedDepositTx;
|
||||
transient private MoneroTxWallet depositTx;
|
||||
transient private TradeManager tradeManager;
|
||||
|
||||
// Persistable mutable
|
||||
@Nullable
|
||||
@ -96,7 +102,6 @@ public final class TradePeer implements PersistablePayload {
|
||||
@Getter
|
||||
private DisputeClosedMessage disputeClosedMessage;
|
||||
|
||||
|
||||
// added in v 0.6
|
||||
@Nullable
|
||||
private byte[] accountAgeWitnessNonce;
|
||||
@ -142,13 +147,32 @@ public final class TradePeer implements PersistablePayload {
|
||||
private long payoutAmount;
|
||||
@Nullable
|
||||
private String updatedMultisigHex;
|
||||
@Getter
|
||||
@Deprecated
|
||||
private boolean depositsConfirmedMessageAcked;
|
||||
|
||||
// We want to indicate the user the state of the message delivery of the payment
|
||||
// confirmation messages. We do an automatic re-send in case it was not ACKed yet.
|
||||
// To enable that even after restart we persist the state.
|
||||
@Setter
|
||||
boolean depositsConfirmedMessageAcked;
|
||||
private ObjectProperty<MessageState> depositsConfirmedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentSentMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
@Setter
|
||||
private ObjectProperty<MessageState> paymentReceivedMessageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||
|
||||
public TradePeer() {
|
||||
}
|
||||
|
||||
public void applyTransient(TradeManager tradeManager) {
|
||||
this.tradeManager = tradeManager;
|
||||
|
||||
// migrate deprecated fields to new model for v1.0.19
|
||||
if (depositsConfirmedMessageAcked && depositsConfirmedMessageStateProperty.get() == MessageState.UNDEFINED) {
|
||||
depositsConfirmedMessageStateProperty.set(MessageState.ACKNOWLEDGED);
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public BigInteger getDepositTxFee() {
|
||||
return BigInteger.valueOf(depositTxFee);
|
||||
}
|
||||
@ -181,6 +205,60 @@ public final class TradePeer implements PersistablePayload {
|
||||
this.payoutAmount = payoutAmount.longValueExact();
|
||||
}
|
||||
|
||||
void setDepositsConfirmedAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setDepositsConfirmedMessageState(messageState);
|
||||
}
|
||||
|
||||
void setPaymentSentAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentSentMessageState(messageState);
|
||||
}
|
||||
|
||||
void setPaymentReceivedAckMessage(AckMessage ackMessage) {
|
||||
MessageState messageState = ackMessage.isSuccess() ?
|
||||
MessageState.ACKNOWLEDGED :
|
||||
MessageState.FAILED;
|
||||
setPaymentReceivedMessageState(messageState);
|
||||
}
|
||||
|
||||
public void setDepositsConfirmedMessageState(MessageState depositsConfirmedMessageStateProperty) {
|
||||
this.depositsConfirmedMessageStateProperty.set(depositsConfirmedMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaymentSentMessageState(MessageState paymentSentMessageStateProperty) {
|
||||
this.paymentSentMessageStateProperty.set(paymentSentMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public void setPaymentReceivedMessageState(MessageState paymentReceivedMessageStateProperty) {
|
||||
this.paymentReceivedMessageStateProperty.set(paymentReceivedMessageStateProperty);
|
||||
if (tradeManager != null) {
|
||||
tradeManager.requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDepositsConfirmedMessageAcked() {
|
||||
return depositsConfirmedMessageStateProperty.get() == MessageState.ACKNOWLEDGED;
|
||||
}
|
||||
|
||||
public boolean isPaymentSentMessageAcked() {
|
||||
return paymentSentMessageStateProperty.get() == MessageState.ACKNOWLEDGED;
|
||||
}
|
||||
|
||||
public boolean isPaymentReceivedMessageReceived() {
|
||||
return paymentReceivedMessageStateProperty.get() == MessageState.ACKNOWLEDGED || paymentReceivedMessageStateProperty.get() == MessageState.STORED_IN_MAILBOX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
final protobuf.TradePeer.Builder builder = protobuf.TradePeer.newBuilder();
|
||||
@ -221,6 +299,9 @@ public final class TradePeer implements PersistablePayload {
|
||||
Optional.ofNullable(payoutTxFee).ifPresent(e -> builder.setPayoutTxFee(payoutTxFee));
|
||||
Optional.ofNullable(payoutAmount).ifPresent(e -> builder.setPayoutAmount(payoutAmount));
|
||||
builder.setDepositsConfirmedMessageAcked(depositsConfirmedMessageAcked);
|
||||
builder.setDepositsConfirmedMessageState(depositsConfirmedMessageStateProperty.get().name());
|
||||
builder.setPaymentSentMessageState(paymentSentMessageStateProperty.get().name());
|
||||
builder.setPaymentReceivedMessageState(paymentReceivedMessageStateProperty.get().name());
|
||||
|
||||
builder.setCurrentDate(currentDate);
|
||||
return builder.build();
|
||||
@ -270,6 +351,19 @@ public final class TradePeer implements PersistablePayload {
|
||||
tradePeer.setUnsignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getUnsignedPayoutTxHex()));
|
||||
tradePeer.setPayoutTxFee(BigInteger.valueOf(proto.getPayoutTxFee()));
|
||||
tradePeer.setPayoutAmount(BigInteger.valueOf(proto.getPayoutAmount()));
|
||||
|
||||
String depositsConfirmedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getDepositsConfirmedMessageState());
|
||||
MessageState depositsConfirmedMessageState = ProtoUtil.enumFromProto(MessageState.class, depositsConfirmedMessageStateString);
|
||||
tradePeer.setDepositsConfirmedMessageState(depositsConfirmedMessageState);
|
||||
|
||||
String paymentSentMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentSentMessageState());
|
||||
MessageState paymentSentMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentSentMessageStateString);
|
||||
tradePeer.setPaymentSentMessageState(paymentSentMessageState);
|
||||
|
||||
String paymentReceivedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentReceivedMessageState());
|
||||
MessageState paymentReceivedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentReceivedMessageStateString);
|
||||
tradePeer.setPaymentReceivedMessageState(paymentReceivedMessageState);
|
||||
|
||||
return tradePeer;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
@ -163,7 +165,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}
|
||||
} else if (networkEnvelope instanceof AckMessage) {
|
||||
onAckMessage((AckMessage) networkEnvelope, peer);
|
||||
trade.onAckMessage((AckMessage) networkEnvelope, peer); // notify trade listeners
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,11 +209,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
onMailboxMessage(tradeMessage, mailboxMessage.getSenderNodeAddress());
|
||||
} else if (mailboxMessage instanceof AckMessage) {
|
||||
AckMessage ackMessage = (AckMessage) mailboxMessage;
|
||||
if (!trade.isCompleted()) {
|
||||
// We only apply the msg if we have not already completed the trade
|
||||
onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress());
|
||||
}
|
||||
// In any case we remove the msg
|
||||
onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress());
|
||||
processModel.getP2PService().getMailboxMessageService().removeMailboxMsg(ackMessage);
|
||||
log.info("Remove {} from the P2P network.", ackMessage.getClass().getSimpleName());
|
||||
}
|
||||
@ -240,7 +237,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
protected void onInitialized() {
|
||||
|
||||
// listen for direct messages unless completed
|
||||
if (!trade.isCompleted()) processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||
if (!trade.isFinished()) processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||
|
||||
// initialize trade
|
||||
synchronized (trade.getLock()) {
|
||||
@ -250,6 +247,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService();
|
||||
if (!trade.isCompleted()) mailboxMessageService.addDecryptedMailboxListener(this);
|
||||
handleMailboxCollection(mailboxMessageService.getMyDecryptedMailboxMessages());
|
||||
|
||||
// reprocess applicable messages
|
||||
trade.reprocessApplicableMessages();
|
||||
}
|
||||
|
||||
// send deposits confirmed message if applicable
|
||||
@ -279,24 +279,46 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
public void maybeReprocessPaymentReceivedMessage(boolean reprocessOnError) {
|
||||
public boolean needsToResendPaymentReceivedMessages() {
|
||||
return false; // seller protocol overrides
|
||||
}
|
||||
|
||||
public void maybeReprocessPaymentSentMessage(boolean reprocessOnError) {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// skip if no need to reprocess
|
||||
if (trade.isSeller() || trade.getSeller().getPaymentReceivedMessage() == null || (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && trade.isPayoutPublished())) {
|
||||
if (trade.isShutDownStarted() || trade.isBuyer() || trade.getBuyer().getPaymentSentMessage() == null || trade.getState().ordinal() >= Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("Reprocessing payment received message for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
log.warn("Reprocessing PaymentSentMessage 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(() -> {
|
||||
if (trade.isShutDownStarted()) return;
|
||||
synchronized (trade.getLock()) {
|
||||
|
||||
// skip if no need to reprocess
|
||||
if (trade.isShutDownStarted() || trade.isSeller() || trade.getSeller().getPaymentReceivedMessage() == null || (trade.getState().ordinal() >= Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG.ordinal() && trade.isPayoutPublished())) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("Reprocessing PaymentReceivedMessage for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
handle(trade.getSeller().getPaymentReceivedMessage(), trade.getSeller().getPaymentReceivedMessage().getSenderNodeAddress(), reprocessOnError);
|
||||
}
|
||||
}, trade.getId());
|
||||
}
|
||||
|
||||
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 +355,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 +398,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 +444,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 +474,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,8 +503,26 @@ 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());
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
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.isShutDownStarted()) return;
|
||||
if (!(trade instanceof SellerTrade || trade instanceof ArbitratorTrade)) {
|
||||
log.warn("Ignoring PaymentSentMessage since not seller or arbitrator");
|
||||
return;
|
||||
@ -494,7 +534,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
// TODO A better fix would be to add a listener for the wallet sync state and process
|
||||
// the mailbox msg once wallet is ready and trade state set.
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||
if (trade.getPhase().ordinal() >= Trade.Phase.PAYMENT_SENT.ordinal()) {
|
||||
log.warn("Received another PaymentSentMessage which was already processed for {} {}, ACKing", trade.getClass().getSimpleName(), trade.getId());
|
||||
handleTaskRunnerSuccess(peer, message);
|
||||
@ -521,7 +561,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,15 +587,28 @@ 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());
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
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.isShutDownStarted()) return;
|
||||
ThreadUtils.execute(() -> {
|
||||
if (!(trade instanceof BuyerTrade || trade instanceof ArbitratorTrade)) {
|
||||
log.warn("Ignoring PaymentReceivedMessage since not buyer or arbitrator");
|
||||
return;
|
||||
}
|
||||
synchronized (trade.getLock()) {
|
||||
if (!trade.isInitialized() || trade.isShutDown()) return;
|
||||
if (!trade.isInitialized() || trade.isShutDownStarted()) return;
|
||||
latchTrade();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
processModel.setTradeMessage(message);
|
||||
@ -649,48 +714,84 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||
|
||||
private void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
|
||||
|
||||
// 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);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
||||
processModel.setPaymentSentAckMessageArbitrator(ackMessage);
|
||||
} 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);
|
||||
return; // log error and ignore nack if not seller
|
||||
}
|
||||
// ignore if trade is completely finished
|
||||
if (trade.isFinished()) return;
|
||||
|
||||
// get trade peer
|
||||
TradePeer peer = trade.getTradePeer(sender);
|
||||
if (peer == null) {
|
||||
if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getArbitrator().getNodeAddress()))) peer = trade.getArbitrator();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getMaker().getNodeAddress()))) peer = trade.getMaker();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getTaker().getNodeAddress()))) peer = trade.getTaker();
|
||||
}
|
||||
if (peer == null) {
|
||||
if (ackMessage.isSuccess()) log.warn("Received AckMessage from unknown peer for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
else log.warn("Received AckMessage with error state from unknown peer for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (ackMessage.isSuccess()) {
|
||||
log.info("Received AckMessage for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
// update sender's node address
|
||||
if (!peer.getNodeAddress().equals(sender)) {
|
||||
log.info("Updating peer's node address from {} to {} using ACK message to {}", peer.getNodeAddress(), sender, ackMessage.getSourceMsgClassName());
|
||||
peer.setNodeAddress(sender);
|
||||
}
|
||||
|
||||
// handle ack for DepositsConfirmedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositsConfirmedMessage.class.getSimpleName())) {
|
||||
TradePeer peer = trade.getTradePeer(sender);
|
||||
if (peer == null) {
|
||||
|
||||
// get the applicable peer based on the sourceUid
|
||||
if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getArbitrator().getNodeAddress()))) peer = trade.getArbitrator();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getMaker().getNodeAddress()))) peer = trade.getMaker();
|
||||
else if (ackMessage.getSourceUid().equals(HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, trade.getTaker().getNodeAddress()))) peer = trade.getTaker();
|
||||
}
|
||||
if (peer == null) log.warn("Received AckMesage for DepositsConfirmedMessage for unknown peer: " + sender);
|
||||
else peer.setDepositsConfirmedMessageAcked(true);
|
||||
}
|
||||
} else {
|
||||
log.warn("Received AckMessage with error state for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
|
||||
// set trade state on deposit request nack
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
||||
// set trade state on deposit request nack
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositRequest.class.getSimpleName())) {
|
||||
if (!ackMessage.isSuccess()) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
||||
|
||||
// handle ack for DepositsConfirmedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(DepositsConfirmedMessage.class.getSimpleName())) {
|
||||
peer.setDepositsConfirmedAckMessage(ackMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
trade.getSeller().setPaymentSentAckMessage(ackMessage);
|
||||
if (ackMessage.isSuccess()) trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
else trade.setState(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
||||
trade.getArbitrator().setPaymentSentAckMessage(ackMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else {
|
||||
log.warn("Received AckMessage from unexpected peer for {}, sender={}, trade={} {}, messageUid={}, success={}, errorMsg={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.isSuccess(), ackMessage.getErrorMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// handle ack for PaymentReceivedMessage, which automatically re-sends if not ACKed in a certain time
|
||||
if (ackMessage.getSourceMsgClassName().equals(PaymentReceivedMessage.class.getSimpleName())) {
|
||||
if (trade.getTradePeer(sender) == trade.getBuyer()) {
|
||||
trade.getBuyer().setPaymentReceivedAckMessage(ackMessage);
|
||||
if (ackMessage.isSuccess()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_RECEIVED_PAYMENT_RECEIVED_MSG);
|
||||
else trade.setState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else if (trade.getTradePeer(sender) == trade.getArbitrator()) {
|
||||
trade.getArbitrator().setPaymentReceivedAckMessage(ackMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else {
|
||||
log.warn("Received AckMessage from unexpected peer for {}, sender={}, trade={} {}, messageUid={}, success={}, errorMsg={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.isSuccess(), ackMessage.getErrorMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// generic handling
|
||||
if (ackMessage.isSuccess()) {
|
||||
log.info("Received AckMessage for {}, sender={}, trade={} {}, messageUid={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid());
|
||||
} else {
|
||||
log.warn("Received AckMessage with error state for {}, sender={}, trade={} {}, messageUid={}, errorMessage={}", ackMessage.getSourceMsgClassName(), sender, trade.getClass().getSimpleName(), trade.getId(), ackMessage.getSourceUid(), ackMessage.getErrorMessage());
|
||||
handleError(ackMessage.getErrorMessage());
|
||||
}
|
||||
|
||||
// notify trade listeners
|
||||
trade.onAckMessage(ackMessage, sender);
|
||||
}
|
||||
|
||||
protected void sendAckMessage(NodeAddress peer, TradeMessage message, boolean result, @Nullable String errorMessage) {
|
||||
|
@ -44,7 +44,6 @@ import java.util.UUID;
|
||||
@Slf4j
|
||||
public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
|
||||
private Throwable error;
|
||||
private boolean depositResponsesSent;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
@ -68,7 +67,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
processDepositRequest();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
this.error = t;
|
||||
trade.getProcessModel().error = t;
|
||||
log.error("Error processing deposit request for trade {}: {}\n", trade.getId(), t.getMessage(), t);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
failed(t);
|
||||
@ -188,7 +187,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
trade.stateProperty().addListener((obs, oldState, newState) -> {
|
||||
if (oldState == newState) return;
|
||||
if (newState == Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED) {
|
||||
sendDepositResponsesOnce(error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : error.getMessage());
|
||||
sendDepositResponsesOnce(trade.getProcessModel().error == null ? "Arbitrator failed to publish deposit txs within timeout for trade " + trade.getId() : trade.getProcessModel().error.getMessage());
|
||||
} else if (newState.ordinal() >= Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS.ordinal()) {
|
||||
sendDepositResponsesOnce(null);
|
||||
}
|
||||
@ -230,7 +229,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
}
|
||||
|
||||
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
|
||||
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), error);
|
||||
log.info("Sending deposit response to trader={}; offerId={}, error={}", nodeAddress, trade.getId(), trade.getProcessModel().error);
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
|
@ -142,26 +142,26 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.SENT);
|
||||
tryToSendAgainLater();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
getReceiver().setPaymentSentMessageState(MessageState.FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@ -170,7 +170,7 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
timer.stop();
|
||||
}
|
||||
if (listener != null) {
|
||||
processModel.getPaymentSentMessageStateProperty().removeListener(listener);
|
||||
trade.getSeller().getPaymentReceivedMessageStateProperty().removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +185,6 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
@ -194,8 +193,8 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
|
||||
if (resendCounter == 0) {
|
||||
listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue);
|
||||
processModel.getPaymentSentMessageStateProperty().addListener(listener);
|
||||
onMessageStateChange(processModel.getPaymentSentMessageStateProperty().get());
|
||||
getReceiver().getPaymentSentMessageStateProperty().addListener(listener);
|
||||
onMessageStateChange(getReceiver().getPaymentSentMessageStateProperty().get());
|
||||
}
|
||||
|
||||
// first re-send is after 2 minutes, then increase the delay exponentially
|
||||
@ -212,12 +211,12 @@ public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask
|
||||
}
|
||||
|
||||
private void onMessageStateChange(MessageState newValue) {
|
||||
if (newValue == MessageState.ACKNOWLEDGED) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
if (isAckedByReceiver()) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean isAckedByReceiver();
|
||||
protected boolean isAckedByReceiver() {
|
||||
return getReceiver().isPaymentSentMessageAcked();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
@ -39,26 +38,7 @@ public class BuyerSendPaymentSentMessageToArbitrator extends BuyerSendPaymentSen
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
super.setStateSent();
|
||||
complete(); // don't wait for message to arbitrator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAckedByReceiver() {
|
||||
return trade.getProcessModel().getPaymentSentMessageStatePropertyArbitrator().get() == MessageState.ACKNOWLEDGED;
|
||||
}
|
||||
}
|
||||
|
@ -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.messages.TradeMessage;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
@ -40,25 +39,25 @@ public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMes
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.SENT);
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
super.setStateSent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.ARRIVED);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
|
||||
super.setStateArrived();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG);
|
||||
super.setStateStoredInMailbox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.getProcessModel().setPaymentSentMessageState(MessageState.FAILED);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
super.setStateFault();
|
||||
}
|
||||
|
||||
@ -69,9 +68,4 @@ public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMes
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAckedByReceiver() {
|
||||
return trade.getState().ordinal() >= Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG.ordinal();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
@ -128,14 +125,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
|
||||
private void processPayoutTx(PaymentReceivedMessage message) {
|
||||
|
||||
// adapt from 1.0.6 to 1.0.7 which changes field usage
|
||||
// TODO: remove after future updates to allow old trades to clear
|
||||
if (trade.getPayoutTxHex() != null && trade.getBuyer().getPaymentSentMessage() != null && trade.getPayoutTxHex().equals(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex())) {
|
||||
log.warn("Nullifying payout tx hex after 1.0.7 update {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
if (trade instanceof BuyerTrade) trade.getSelf().setUnsignedPayoutTxHex(trade.getPayoutTxHex());
|
||||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// update wallet
|
||||
trade.importMultisigHex();
|
||||
trade.syncAndPollWallet();
|
||||
|
@ -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();
|
||||
|
@ -43,13 +43,6 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
||||
// handle first time preparation
|
||||
if (trade.getArbitrator().getPaymentReceivedMessage() == null) {
|
||||
|
||||
// adapt from 1.0.6 to 1.0.7 which changes field usage
|
||||
// TODO: remove after future updates to allow old trades to clear
|
||||
if (trade.getPayoutTxHex() != null && trade.getPayoutTxHex().equals(trade.getBuyer().getPaymentSentMessage().getPayoutTxHex())) {
|
||||
log.warn("Nullifying payout tx hex after 1.0.7 update {} {}", trade.getClass().getSimpleName(), trade.getShortId());
|
||||
trade.setPayoutTxHex(null);
|
||||
}
|
||||
|
||||
// synchronize on lock for wallet operations
|
||||
synchronized (trade.getWalletLock()) {
|
||||
synchronized (HavenoUtils.getWalletFunctionLock()) {
|
||||
|
@ -35,11 +35,15 @@
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.account.sign.SignedWitness;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
@ -47,15 +51,23 @@ import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.core.util.JsonUtil;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
|
||||
SignedWitness signedWitness = null;
|
||||
private SignedWitness signedWitness = null;
|
||||
private ChangeListener<MessageState> listener;
|
||||
private Timer timer;
|
||||
private static final int MAX_RESEND_ATTEMPTS = 20;
|
||||
private int delayInMin = 10;
|
||||
private int resendCounter = 0;
|
||||
|
||||
public SellerSendPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
@ -77,6 +89,13 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if already received
|
||||
if (isReceived()) {
|
||||
if (!isCompleted()) complete();
|
||||
return;
|
||||
}
|
||||
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
@ -134,29 +153,85 @@ public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessag
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} sent: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.SENT);
|
||||
tryToSendAgainLater();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.advanceState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
log.error("{} failed: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.advanceState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} stored in mailbox: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.advanceState(Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} arrived: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
getReceiver().setPaymentReceivedMessageState(MessageState.ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
if (listener != null) {
|
||||
trade.getBuyer().getPaymentReceivedMessageStateProperty().removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToSendAgainLater() {
|
||||
|
||||
// skip if already received
|
||||
if (isReceived()) return;
|
||||
|
||||
if (resendCounter >= MAX_RESEND_ATTEMPTS) {
|
||||
cleanup();
|
||||
log.warn("We never received an ACK message when sending the PaymentReceivedMessage to the peer. We stop trying to send the message.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||
|
||||
if (resendCounter == 0) {
|
||||
listener = (observable, oldValue, newValue) -> onMessageStateChange(newValue);
|
||||
getReceiver().getPaymentReceivedMessageStateProperty().addListener(listener);
|
||||
onMessageStateChange(getReceiver().getPaymentReceivedMessageStateProperty().get());
|
||||
}
|
||||
|
||||
// first re-send is after 2 minutes, then increase the delay exponentially
|
||||
if (resendCounter == 0) {
|
||||
int shortDelay = 2;
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", shortDelay);
|
||||
timer = UserThread.runAfter(this::run, shortDelay, TimeUnit.MINUTES);
|
||||
} else {
|
||||
log.info("We will send the message again to the peer after a delay of {} min.", delayInMin);
|
||||
timer = UserThread.runAfter(this::run, delayInMin, TimeUnit.MINUTES);
|
||||
delayInMin = (int) ((double) delayInMin * 1.5);
|
||||
}
|
||||
resendCounter++;
|
||||
}
|
||||
|
||||
private void onMessageStateChange(MessageState newValue) {
|
||||
if (isReceived()) {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isReceived() {
|
||||
return getReceiver().isPaymentReceivedMessageReceived();
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,30 @@ public class SellerSendPaymentReceivedMessageToBuyer extends SellerSendPaymentRe
|
||||
return trade.getBuyer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateSent();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.advanceState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateFault();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.advanceState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateStoredInMailbox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.advanceState(Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
||||
super.setStateArrived();
|
||||
}
|
||||
|
||||
// continue execution on fault so payment received message is sent to arbitrator
|
||||
@Override
|
||||
protected void onFault(String errorMessage, TradeMessage message) {
|
||||
|
@ -23,6 +23,7 @@ import haveno.common.Timer;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||
@ -52,8 +53,8 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if already acked by receiver
|
||||
if (isAckedByReceiver()) {
|
||||
// skip if already acked or payout published
|
||||
if (isAckedByReceiver() || trade.isPayoutPublished()) {
|
||||
if (!isCompleted()) complete();
|
||||
return;
|
||||
}
|
||||
@ -64,11 +65,17 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract NodeAddress getReceiverNodeAddress();
|
||||
protected abstract TradePeer getReceiver();
|
||||
|
||||
@Override
|
||||
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return getReceiver().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return getReceiver().getPubKeyRing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||
@ -97,23 +104,24 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.SENT);
|
||||
tryToSendAgainLater();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
// no additional handling
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.ARRIVED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
// no additional handling
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.STORED_IN_MAILBOX);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
// no additional handling
|
||||
getReceiver().setDepositsConfirmedMessageState(MessageState.FAILED);
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
@ -151,7 +159,6 @@ public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTas
|
||||
}
|
||||
|
||||
private boolean isAckedByReceiver() {
|
||||
TradePeer peer = trade.getTradePeer(getReceiverNodeAddress());
|
||||
return peer.isDepositsConfirmedMessageAcked();
|
||||
return getReceiver().isDepositsConfirmedMessageAcked();
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -34,12 +33,7 @@ public class SendDepositsConfirmedMessageToArbitrator extends SendDepositsConfir
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getArbitrator().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getArbitrator().getPubKeyRing();
|
||||
protected TradePeer getReceiver() {
|
||||
return trade.getArbitrator();
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -34,12 +33,7 @@ public class SendDepositsConfirmedMessageToBuyer extends SendDepositsConfirmedMe
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getBuyer().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getBuyer().getPubKeyRing();
|
||||
protected TradePeer getReceiver() {
|
||||
return trade.getBuyer();
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,9 @@
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
@ -34,12 +33,7 @@ public class SendDepositsConfirmedMessageToSeller extends SendDepositsConfirmedM
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getSeller().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getSeller().getPubKeyRing();
|
||||
protected TradePeer getReceiver() {
|
||||
return trade.getSeller();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ public class Restrictions {
|
||||
public static final double MAX_SECURITY_DEPOSIT_PCT = 0.5;
|
||||
public static BigInteger MIN_TRADE_AMOUNT = HavenoUtils.xmrToAtomicUnits(0.1);
|
||||
public static BigInteger MIN_SECURITY_DEPOSIT = HavenoUtils.xmrToAtomicUnits(0.1);
|
||||
public static int MAX_EXTRA_INFO_LENGTH = 1500;
|
||||
|
||||
// At mediation we require a min. payout to the losing party to keep incentive for the trader to accept the
|
||||
// mediated payout. For Refund agent cases we do not have that restriction.
|
||||
|
@ -767,7 +767,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
BigInteger minerFeeEstimate = getFeeEstimate(tx.getWeight());
|
||||
double minerFeeDiff = tx.getFee().subtract(minerFeeEstimate).abs().doubleValue() / minerFeeEstimate.doubleValue();
|
||||
if (minerFeeDiff > MINER_FEE_TOLERANCE) throw new RuntimeException("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + minerFeeEstimate + " but was " + tx.getFee() + ", diff%=" + minerFeeDiff);
|
||||
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
|
||||
log.info("Trade miner fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
|
||||
|
||||
// verify proof to fee address
|
||||
BigInteger actualTradeFee = BigInteger.ZERO;
|
||||
@ -785,7 +785,7 @@ public class XmrWalletService extends XmrWalletBase {
|
||||
// verify trade fee amount
|
||||
if (!actualTradeFee.equals(tradeFeeAmount)) {
|
||||
if (equalsWithinFractionError(actualTradeFee, tradeFeeAmount)) {
|
||||
log.warn("Trade tx fee amount is within fraction error, expected " + tradeFeeAmount + " but was " + actualTradeFee);
|
||||
log.warn("Trade fee amount is within fraction error, expected " + tradeFeeAmount + " but was " + actualTradeFee);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid trade fee amount, expected " + tradeFeeAmount + " but was " + actualTradeFee);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
@ -495,6 +495,7 @@ createOffer.triggerPrice.tooltip=As protection against drastic price movements y
|
||||
deactivates the offer if the market price reaches that value.
|
||||
createOffer.triggerPrice.invalid.tooLow=Value must be higher than {0}
|
||||
createOffer.triggerPrice.invalid.tooHigh=Value must be lower than {0}
|
||||
createOffer.extraInfo.invalid.tooLong=Must not exceed {0} characters.
|
||||
|
||||
# new entries
|
||||
createOffer.placeOfferButton=Review: Place offer to {0} monero
|
||||
|
@ -203,12 +203,12 @@ class GrpcOffersService extends OffersImplBase {
|
||||
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
|
||||
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
|
||||
new HashMap<>() {{
|
||||
put(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, SECONDS));
|
||||
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 10 : 3, SECONDS));
|
||||
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(getGetOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, SECONDS));
|
||||
put(getGetMyOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, SECONDS));
|
||||
put(getGetOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, SECONDS));
|
||||
put(getGetMyOffersMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getPostOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getCancelOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
}}
|
||||
)));
|
||||
}
|
||||
|
@ -251,15 +251,15 @@ class GrpcTradesService extends TradesImplBase {
|
||||
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
|
||||
.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(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 1, SECONDS));
|
||||
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 1, SECONDS));
|
||||
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getConfirmPaymentSentMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 3, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getCompleteTradeMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 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() ? 75 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(Config.baseCurrencyNetwork().isTestnet() ? 75 : 4, Config.baseCurrencyNetwork().isTestnet() ? SECONDS : MINUTES));
|
||||
}}
|
||||
)));
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.desktop.components;
|
||||
|
||||
|
||||
import com.jfoenix.controls.JFXTextArea;
|
||||
import haveno.core.util.validation.InputValidator;
|
||||
import haveno.desktop.util.validation.JFXInputValidator;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
/**
|
||||
* TextArea with validation support.
|
||||
* If validator is set it supports on focus out validation with that validator. If a more sophisticated validation is
|
||||
* needed the validationResultProperty can be used for applying validation result done by external validation.
|
||||
* In case the isValid property in validationResultProperty get set to false we display a red border and an error
|
||||
* message within the errorMessageDisplay placed on the right of the text area.
|
||||
* The errorMessageDisplay gets closed when the ValidatingTextArea instance gets removed from the scene graph or when
|
||||
* hideErrorMessageDisplay() is called.
|
||||
* There can be only 1 errorMessageDisplays at a time we use static field for it.
|
||||
* The position is derived from the position of the textArea itself or if set from the layoutReference node.
|
||||
*/
|
||||
//TODO There are some rare situation where it behaves buggy. Needs further investigation and improvements.
|
||||
public class InputTextArea extends JFXTextArea {
|
||||
|
||||
private final ObjectProperty<InputValidator.ValidationResult> validationResult = new SimpleObjectProperty<>
|
||||
(new InputValidator.ValidationResult(true));
|
||||
|
||||
private final JFXInputValidator jfxValidationWrapper = new JFXInputValidator();
|
||||
|
||||
private InputValidator validator;
|
||||
private String errorMessage = null;
|
||||
|
||||
|
||||
public InputValidator getValidator() {
|
||||
return validator;
|
||||
}
|
||||
|
||||
public void setValidator(InputValidator validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public InputTextArea() {
|
||||
super();
|
||||
|
||||
getValidators().add(jfxValidationWrapper);
|
||||
|
||||
validationResult.addListener((ov, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
jfxValidationWrapper.resetValidation();
|
||||
if (!newValue.isValid) {
|
||||
if (!newValue.errorMessageEquals(oldValue)) { // avoid blinking
|
||||
validate(); // ensure that the new error message replaces the old one
|
||||
}
|
||||
if (this.errorMessage != null) {
|
||||
jfxValidationWrapper.applyErrorMessage(this.errorMessage);
|
||||
} else {
|
||||
jfxValidationWrapper.applyErrorMessage(newValue);
|
||||
}
|
||||
}
|
||||
validate();
|
||||
}
|
||||
});
|
||||
|
||||
textProperty().addListener((o, oldValue, newValue) -> {
|
||||
refreshValidation();
|
||||
});
|
||||
|
||||
focusedProperty().addListener((o, oldValue, newValue) -> {
|
||||
if (validator != null) {
|
||||
if (!oldValue && newValue) {
|
||||
this.validationResult.set(new InputValidator.ValidationResult(true));
|
||||
} else {
|
||||
this.validationResult.set(validator.validate(getText()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public methods
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void resetValidation() {
|
||||
jfxValidationWrapper.resetValidation();
|
||||
|
||||
String input = getText();
|
||||
if (input.isEmpty()) {
|
||||
validationResult.set(new InputValidator.ValidationResult(true));
|
||||
} else {
|
||||
validationResult.set(validator.validate(input));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshValidation() {
|
||||
if (validator != null) {
|
||||
this.validationResult.set(validator.validate(getText()));
|
||||
}
|
||||
}
|
||||
|
||||
public void setInvalid(String message) {
|
||||
validationResult.set(new InputValidator.ValidationResult(false, message));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public ObjectProperty<InputValidator.ValidationResult> validationResultProperty() {
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new JFXTextAreaSkinHavenoStyle(this);
|
||||
}
|
||||
}
|
@ -501,15 +501,15 @@ tree-table-view:focused {
|
||||
-jfx-default-color: -bs-color-primary;
|
||||
}
|
||||
|
||||
.jfx-date-picker .jfx-text-field {
|
||||
.jfx-date-picker .jfx-text-field .jfx-text-area {
|
||||
-fx-padding: 0.333333em 0em 0.333333em 0em;
|
||||
}
|
||||
|
||||
.jfx-date-picker .jfx-text-field > .input-line {
|
||||
.jfx-date-picker .jfx-text-field .jfx-text-area > .input-line {
|
||||
-fx-translate-x: 0em;
|
||||
}
|
||||
|
||||
.jfx-date-picker .jfx-text-field > .input-focused-line {
|
||||
.jfx-date-picker .jfx-text-field .jfx-text-area > .input-focused-line {
|
||||
-fx-translate-x: 0em;
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,6 @@
|
||||
-fx-image: url("../../images/remove.png");
|
||||
}
|
||||
|
||||
#image-edit {
|
||||
-fx-image: url("../../images/edit.png");
|
||||
}
|
||||
|
||||
#image-buy-white {
|
||||
-fx-image: url("../../images/buy_white.png");
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ import haveno.desktop.components.AutoTooltipLabel;
|
||||
import haveno.desktop.components.BalanceTextField;
|
||||
import haveno.desktop.components.BusyAnimation;
|
||||
import haveno.desktop.components.FundsTextField;
|
||||
import haveno.desktop.components.HavenoTextArea;
|
||||
import haveno.desktop.components.InfoInputTextField;
|
||||
import haveno.desktop.components.InputTextArea;
|
||||
import haveno.desktop.components.InputTextField;
|
||||
import haveno.desktop.components.TitledGroupBg;
|
||||
import haveno.desktop.main.MainView;
|
||||
@ -76,7 +76,6 @@ import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.Tooltip;
|
||||
@ -140,7 +139,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
private BalanceTextField balanceTextField;
|
||||
private ToggleButton reserveExactAmountSlider;
|
||||
private ToggleButton buyerAsTakerWithoutDepositSlider;
|
||||
protected TextArea extraInfoTextArea;
|
||||
protected InputTextArea extraInfoTextArea;
|
||||
private FundsTextField totalToPayTextField;
|
||||
private Label amountDescriptionLabel, priceCurrencyLabel, priceDescriptionLabel, volumeDescriptionLabel,
|
||||
waitingForFundsLabel, marketBasedPriceLabel, percentagePriceDescriptionLabel, tradeFeeDescriptionLabel,
|
||||
@ -211,7 +210,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
|
||||
createListeners();
|
||||
|
||||
balanceTextField.setFormatter(model.getBtcFormatter());
|
||||
balanceTextField.setFormatter(model.getXmrFormatter());
|
||||
|
||||
paymentAccountsComboBox.setConverter(GUIUtil.getPaymentAccountsComboBoxStringConverter());
|
||||
paymentAccountsComboBox.setButtonCell(GUIUtil.getComboBoxButtonCell(Res.get("shared.chooseTradingAccount"),
|
||||
@ -592,6 +591,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
triggerPriceInputTextField.validationResultProperty().bind(model.triggerPriceValidationResult);
|
||||
volumeTextField.validationResultProperty().bind(model.volumeValidationResult);
|
||||
securityDepositInputTextField.validationResultProperty().bind(model.securityDepositValidationResult);
|
||||
extraInfoTextArea.validationResultProperty().bind(model.extraInfoValidationResult);
|
||||
|
||||
// funding
|
||||
fundingHBox.visibleProperty().bind(model.getDataModel().getIsXmrWalletFunded().not().and(model.showPayFundsScreenDisplayed));
|
||||
@ -713,7 +713,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
triggerPriceInputTextField.setText(model.triggerPrice.get());
|
||||
};
|
||||
extraInfoFocusedListener = (observable, oldValue, newValue) -> {
|
||||
model.onFocusOutExtraInfoTextField(oldValue, newValue);
|
||||
model.onFocusOutExtraInfoTextArea(oldValue, newValue);
|
||||
extraInfoTextArea.setText(model.extraInfo.get());
|
||||
};
|
||||
|
||||
@ -1097,7 +1097,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
Res.get("payment.shared.optionalExtra"), 25 + heightAdjustment);
|
||||
GridPane.setColumnSpan(extraInfoTitledGroupBg, 3);
|
||||
|
||||
extraInfoTextArea = new HavenoTextArea();
|
||||
extraInfoTextArea = new InputTextArea();
|
||||
extraInfoTextArea.setPromptText(Res.get("payment.shared.extraInfo.prompt.offer"));
|
||||
extraInfoTextArea.getStyleClass().add("text-area");
|
||||
extraInfoTextArea.setWrapText(true);
|
||||
@ -1109,7 +1109,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel<?>> exten
|
||||
GridPane.setColumnSpan(extraInfoTextArea, GridPane.REMAINING);
|
||||
GridPane.setColumnIndex(extraInfoTextArea, 0);
|
||||
GridPane.setHalignment(extraInfoTextArea, HPos.LEFT);
|
||||
GridPane.setMargin(extraInfoTextArea, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 0, 0));
|
||||
GridPane.setMargin(extraInfoTextArea, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE, 0, 10, 0));
|
||||
gridPane.getChildren().add(extraInfoTextArea);
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
private final AccountAgeWitnessService accountAgeWitnessService;
|
||||
private final Navigation navigation;
|
||||
private final Preferences preferences;
|
||||
protected final CoinFormatter btcFormatter;
|
||||
protected final CoinFormatter xmrFormatter;
|
||||
private final FiatVolumeValidator fiatVolumeValidator;
|
||||
private final AmountValidator4Decimals amountValidator4Decimals;
|
||||
private final AmountValidator8Decimals amountValidator8Decimals;
|
||||
@ -160,6 +160,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
final ObjectProperty<InputValidator.ValidationResult> triggerPriceValidationResult = new SimpleObjectProperty<>(new InputValidator.ValidationResult(true));
|
||||
final ObjectProperty<InputValidator.ValidationResult> volumeValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> securityDepositValidationResult = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<InputValidator.ValidationResult> extraInfoValidationResult = new SimpleObjectProperty<>();
|
||||
|
||||
private ChangeListener<String> amountStringListener;
|
||||
private ChangeListener<String> minAmountStringListener;
|
||||
@ -195,26 +196,26 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
FiatVolumeValidator fiatVolumeValidator,
|
||||
AmountValidator4Decimals amountValidator4Decimals,
|
||||
AmountValidator8Decimals amountValidator8Decimals,
|
||||
XmrValidator btcValidator,
|
||||
XmrValidator xmrValidator,
|
||||
SecurityDepositValidator securityDepositValidator,
|
||||
PriceFeedService priceFeedService,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
Navigation navigation,
|
||||
Preferences preferences,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter xmrFormatter,
|
||||
OfferUtil offerUtil) {
|
||||
super(dataModel);
|
||||
|
||||
this.fiatVolumeValidator = fiatVolumeValidator;
|
||||
this.amountValidator4Decimals = amountValidator4Decimals;
|
||||
this.amountValidator8Decimals = amountValidator8Decimals;
|
||||
this.xmrValidator = btcValidator;
|
||||
this.xmrValidator = xmrValidator;
|
||||
this.securityDepositValidator = securityDepositValidator;
|
||||
this.priceFeedService = priceFeedService;
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.navigation = navigation;
|
||||
this.preferences = preferences;
|
||||
this.btcFormatter = btcFormatter;
|
||||
this.xmrFormatter = xmrFormatter;
|
||||
this.offerUtil = offerUtil;
|
||||
|
||||
paymentLabel = Res.get("createOffer.fundsBox.paymentLabel", dataModel.shortOfferId);
|
||||
@ -500,11 +501,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
};
|
||||
|
||||
extraInfoStringListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue != null) {
|
||||
extraInfo.set(newValue);
|
||||
} else {
|
||||
extraInfo.set("");
|
||||
}
|
||||
onExtraInfoTextAreaChanged();
|
||||
};
|
||||
|
||||
isWalletFundedListener = (ov, oldValue, newValue) -> updateButtonDisableState();
|
||||
@ -531,7 +528,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
tradeFee.set(HavenoUtils.formatXmr(makerFee));
|
||||
tradeFeeInXmrWithFiat.set(OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
|
||||
dataModel.getMaxMakerFee(),
|
||||
btcFormatter));
|
||||
xmrFormatter));
|
||||
}
|
||||
|
||||
|
||||
@ -665,7 +662,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(() -> {
|
||||
@ -836,8 +833,16 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
}
|
||||
}
|
||||
|
||||
public void onFocusOutExtraInfoTextField(boolean oldValue, boolean newValue) {
|
||||
public void onFocusOutExtraInfoTextArea(boolean oldValue, boolean newValue) {
|
||||
if (oldValue && !newValue) {
|
||||
onExtraInfoTextAreaChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void onExtraInfoTextAreaChanged() {
|
||||
extraInfoValidationResult.set(getExtraInfoValidationResult());
|
||||
updateButtonDisableState();
|
||||
if (extraInfoValidationResult.get().isValid) {
|
||||
dataModel.setExtraInfo(extraInfo.get());
|
||||
}
|
||||
}
|
||||
@ -1045,8 +1050,8 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
.show();
|
||||
}
|
||||
|
||||
CoinFormatter getBtcFormatter() {
|
||||
return btcFormatter;
|
||||
CoinFormatter getXmrFormatter() {
|
||||
return xmrFormatter;
|
||||
}
|
||||
|
||||
public boolean isShownAsBuyOffer() {
|
||||
@ -1064,7 +1069,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
public String getTradeAmount() {
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
|
||||
dataModel.getAmount().get(),
|
||||
btcFormatter);
|
||||
xmrFormatter);
|
||||
}
|
||||
|
||||
public String getSecurityDepositLabel() {
|
||||
@ -1084,7 +1089,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
|
||||
dataModel.getSecurityDeposit(),
|
||||
dataModel.getAmount().get(),
|
||||
btcFormatter
|
||||
xmrFormatter
|
||||
);
|
||||
}
|
||||
|
||||
@ -1097,7 +1102,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalentAndPercentage(offerUtil,
|
||||
dataModel.getMaxMakerFee(),
|
||||
dataModel.getAmount().get(),
|
||||
btcFormatter);
|
||||
xmrFormatter);
|
||||
}
|
||||
|
||||
public String getMakerFeePercentage() {
|
||||
@ -1108,7 +1113,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
public String getTotalToPayInfo() {
|
||||
return OfferViewModelUtil.getTradeFeeWithFiatEquivalent(offerUtil,
|
||||
dataModel.totalToPay.get(),
|
||||
btcFormatter);
|
||||
xmrFormatter);
|
||||
}
|
||||
|
||||
public String getFundsStructure() {
|
||||
@ -1181,7 +1186,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
|
||||
private void setAmountToModel() {
|
||||
if (amount.get() != null && !amount.get().isEmpty()) {
|
||||
BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), btcFormatter));
|
||||
BigInteger amount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.amount.get(), xmrFormatter));
|
||||
|
||||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
Price price = dataModel.getPrice().get();
|
||||
@ -1202,7 +1207,7 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
|
||||
private void setMinAmountToModel() {
|
||||
if (minAmount.get() != null && !minAmount.get().isEmpty()) {
|
||||
BigInteger minAmount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.minAmount.get(), btcFormatter));
|
||||
BigInteger minAmount = HavenoUtils.coinToAtomicUnits(DisplayUtils.parseToCoinWith4Decimals(this.minAmount.get(), xmrFormatter));
|
||||
|
||||
Price price = dataModel.getPrice().get();
|
||||
long maxTradeLimit = dataModel.getMaxTradeLimit();
|
||||
@ -1343,10 +1348,20 @@ public abstract class MutableOfferViewModel<M extends MutableOfferDataModel> ext
|
||||
inputDataValid = inputDataValid && securityDepositValidator.validate(securityDeposit.get()).isValid;
|
||||
}
|
||||
|
||||
inputDataValid = inputDataValid && getExtraInfoValidationResult().isValid;
|
||||
|
||||
isNextButtonDisabled.set(!inputDataValid);
|
||||
isPlaceOfferButtonDisabled.set(createOfferRequested || !inputDataValid || !dataModel.getIsXmrWalletFunded().get());
|
||||
}
|
||||
|
||||
private ValidationResult getExtraInfoValidationResult() {
|
||||
if (extraInfo.get() != null && !extraInfo.get().isEmpty() && extraInfo.get().length() > Restrictions.MAX_EXTRA_INFO_LENGTH) {
|
||||
return new InputValidator.ValidationResult(false, Res.get("createOffer.extraInfo.invalid.tooLong", Restrictions.MAX_EXTRA_INFO_LENGTH));
|
||||
} else {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMarketPriceToManual() {
|
||||
final String currencyCode = dataModel.getTradeCurrencyCode().get();
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
|
@ -20,6 +20,7 @@ package haveno.desktop.main.offer.offerbook;
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIconView;
|
||||
import haveno.common.UserThread;
|
||||
import haveno.common.app.DevEnv;
|
||||
import haveno.common.util.Tuple3;
|
||||
@ -1082,7 +1083,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
||||
button.setPrefWidth(10000);
|
||||
}
|
||||
|
||||
final ImageView iconView2 = new ImageView();
|
||||
MaterialDesignIconView iconView2 = new MaterialDesignIconView(MaterialDesignIcon.PENCIL);
|
||||
final AutoTooltipButton button2 = new AutoTooltipButton();
|
||||
|
||||
{
|
||||
@ -1135,7 +1136,7 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
|
||||
title = Res.get("shared.remove");
|
||||
button.setOnAction(e -> onRemoveOpenOffer(offer));
|
||||
|
||||
iconView2.setId("image-edit");
|
||||
iconView2.setSize("16px");
|
||||
button2.updateText(Res.get("shared.edit"));
|
||||
button2.setOnAction(e -> onEditOpenOffer(offer));
|
||||
button2.setManaged(true);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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{" +
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -553,13 +553,6 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||
disputeManager = arbitrationManager;
|
||||
Dispute dispute = disputesService.createDisputeForTrade(trade, offer, pubKeyRingProvider.get(), isMaker, isSupportTicket);
|
||||
|
||||
// export latest multisig hex
|
||||
try {
|
||||
trade.exportMultisigHex();
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to export multisig hex", e);
|
||||
}
|
||||
|
||||
// send dispute opened message
|
||||
sendDisputeOpenedMessage(dispute, disputeManager);
|
||||
tradeManager.requestPersistence();
|
||||
|
@ -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.getSeller().getPaymentSentMessageStateProperty(), 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);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -416,15 +416,20 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||
|
||||
// payment received
|
||||
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
||||
if (trade instanceof BuyerTrade) buyerState.set(BuyerState.STEP4);
|
||||
else if (trade instanceof SellerTrade) sellerState.set(trade.isPayoutPublished() ? SellerState.STEP4 : SellerState.STEP3);
|
||||
if (trade instanceof BuyerTrade) {
|
||||
buyerState.set(BuyerState.UNDEFINED); // TODO: resetting screen to populate summary information which can be missing before payout message processed
|
||||
buyerState.set(BuyerState.STEP4);
|
||||
} else if (trade instanceof SellerTrade) {
|
||||
sellerState.set(trade.isPayoutPublished() ? SellerState.STEP4 : SellerState.STEP3);
|
||||
}
|
||||
break;
|
||||
|
||||
// seller step 3
|
||||
// seller step 3 or 4 if published
|
||||
case SELLER_CONFIRMED_PAYMENT_RECEIPT:
|
||||
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
||||
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||
case BUYER_RECEIVED_PAYMENT_RECEIVED_MSG:
|
||||
sellerState.set(trade.isPayoutPublished() ? SellerState.STEP4 : SellerState.STEP3);
|
||||
break;
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -79,7 +79,7 @@ public class BuyerStep4View extends TradeStepView {
|
||||
TitledGroupBg completedTradeLabel = new TitledGroupBg();
|
||||
if (trade.getDisputeState().isMediated()) {
|
||||
completedTradeLabel.setText(Res.get("portfolio.pending.step5_buyer.groupTitle.mediated"));
|
||||
} else if (trade.getDisputeState().isArbitrated() && trade.getDisputes().get(0).getDisputeResultProperty().get() != null) {
|
||||
} else if (trade.getDisputeState().isArbitrated() && trade.getDisputeResult() != null) {
|
||||
completedTradeLabel.setText(Res.get("portfolio.pending.step5_buyer.groupTitle.arbitrated"));
|
||||
} else {
|
||||
completedTradeLabel.setText(Res.get("portfolio.pending.step5_buyer.groupTitle"));
|
||||
@ -90,8 +90,8 @@ public class BuyerStep4View extends TradeStepView {
|
||||
gridPane.getChildren().add(hBox2);
|
||||
GridPane.setRowSpan(hBox2, 5);
|
||||
|
||||
if (trade.getDisputeState().isNotDisputed()) {
|
||||
addCompactTopLabelTextField(gridPane, gridRow, getBtcTradeAmountLabel(), model.getTradeVolume(), Layout.TWICE_FIRST_ROW_DISTANCE);
|
||||
if (trade.isPaymentReceived()) {
|
||||
addCompactTopLabelTextField(gridPane, gridRow, getXmrTradeAmountLabel(), model.getTradeVolume(), Layout.TWICE_FIRST_ROW_DISTANCE);
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, getTraditionalTradeAmountLabel(), model.getFiatVolume());
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.refunded"), model.getSecurityDeposit());
|
||||
addCompactTopLabelTextField(gridPane, ++gridRow, Res.get("portfolio.pending.step5_buyer.tradeFee"), model.getTradeFee());
|
||||
@ -149,7 +149,7 @@ public class BuyerStep4View extends TradeStepView {
|
||||
}
|
||||
}
|
||||
|
||||
protected String getBtcTradeAmountLabel() {
|
||||
protected String getXmrTradeAmountLabel() {
|
||||
return Res.get("portfolio.pending.step5_buyer.bought");
|
||||
}
|
||||
|
||||
|
@ -135,6 +135,7 @@ public class SellerStep3View extends TradeStepView {
|
||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||
break;
|
||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||
case BUYER_RECEIVED_PAYMENT_RECEIVED_MSG:
|
||||
busyAnimation.stop();
|
||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||
break;
|
||||
|
@ -32,7 +32,7 @@ public class SellerStep4View extends BuyerStep4View {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBtcTradeAmountLabel() {
|
||||
protected String getXmrTradeAmountLabel() {
|
||||
return Res.get("portfolio.pending.step5_seller.sold");
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -1,10 +1,16 @@
|
||||
## Importing Haveno into development environment
|
||||
# Importing Haveno dev environment
|
||||
|
||||
This document describes how to import Haveno into an integrated development environment (IDE).
|
||||
|
||||
## Importing Haveno into Eclipse IDE
|
||||
First [install and run a Haveno test network](installing.md), then use the following instructions to import Haveno into an IDE.
|
||||
|
||||
These steps describe how to import Haveno into Eclipse IDE for development. You can also develop using [IntelliJ IDEA](#importing-haveno-into-intellij-idea) or VSCode if you prefer.
|
||||
## Visual Studio Code (recommended)
|
||||
|
||||
1. Download and open Visual Studio Code: https://code.visualstudio.com/.
|
||||
2. File > Add folder to Workspace...
|
||||
3. Browse to the `haveno` git project.
|
||||
|
||||
## Eclipse IDE
|
||||
|
||||
> Note: Use default values unless specified otherwise.
|
||||
|
||||
@ -26,7 +32,7 @@ These steps describe how to import Haveno into Eclipse IDE for development. You
|
||||
|
||||
You are now ready to make, run, and test changes to the Haveno project!
|
||||
|
||||
## Importing Haveno into IntelliJ IDEA
|
||||
## IntelliJ IDEA
|
||||
|
||||
> Note: These instructions are outdated and for Haveno.
|
||||
|
||||
|
@ -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"
|
||||
|
@ -205,44 +205,49 @@
|
||||
<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="d9c60be46d">
|
||||
<artifact name="tor-d9c60be46d.jar">
|
||||
<sha256 value="e35f3bf160f96a99255ba3c408f096e65c0092658c5fac182561e081d97c4638" 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="d9c60be46d">
|
||||
<artifact name="tor.external-d9c60be46d.jar">
|
||||
<sha256 value="2cb926847283dea14acb732aa8df8042c59a5a803b1a80e171abbdeea44a7eb0" 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="d9c60be46d">
|
||||
<artifact name="tor.native-d9c60be46d.jar">
|
||||
<sha256 value="2cc31cd9aed3d3368a0fdeaa529d7fa83f732d170f331ec471a37a45c30c80cb" 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="2c02f6b133da79134312b964f2d0f9630c7dfa67">
|
||||
<artifact name="tor-binary-geoip-2c02f6b133da79134312b964f2d0f9630c7dfa67.jar">
|
||||
<sha256 value="98ba8e66de5198d5a558db0ec932faccb52c4eff9c50deefb6427c7f083be665" 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="2c02f6b133da79134312b964f2d0f9630c7dfa67">
|
||||
<artifact name="tor-binary-linux32-2c02f6b133da79134312b964f2d0f9630c7dfa67.jar">
|
||||
<sha256 value="f6a63ed5bdbc9417107c8425bb241ff841a0a8076492fbbcc11921e46bd360a0" 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="2c02f6b133da79134312b964f2d0f9630c7dfa67">
|
||||
<artifact name="tor-binary-linux64-2c02f6b133da79134312b964f2d0f9630c7dfa67.jar">
|
||||
<sha256 value="ef055a43e73f3bc4fffb8fafcafe513566d8e019c9518fe2d1bd81bbcad9d537" 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-linuxaarch64" version="2c02f6b133da79134312b964f2d0f9630c7dfa67">
|
||||
<artifact name="tor-binary-linuxaarch64-2c02f6b133da79134312b964f2d0f9630c7dfa67.jar">
|
||||
<sha256 value="652b95aad1aac64583c6d8e3b573853b6a6877b363b3040dc232eb0b14e78946" 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-macos" version="2c02f6b133da79134312b964f2d0f9630c7dfa67">
|
||||
<artifact name="tor-binary-macos-2c02f6b133da79134312b964f2d0f9630c7dfa67.jar">
|
||||
<sha256 value="a6815e420956ce1faf3ddeaea87ef08666f4744cd0cfd5912b414943b41d5eba" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.haveno-dex.tor-binary" name="tor-binary-windows" version="2c02f6b133da79134312b964f2d0f9630c7dfa67">
|
||||
<artifact name="tor-binary-windows-2c02f6b133da79134312b964f2d0f9630c7dfa67.jar">
|
||||
<sha256 value="9144e233fe350164a9ee0bdd42ea9b16db2d2ea486e5ec634ff040fd5934a609" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.johnrengelman" name="shadow" version="8.1.1">
|
||||
|
@ -95,7 +95,9 @@ public class Socks5ProxyProvider {
|
||||
String[] tokens = socks5ProxyAddress.split(":");
|
||||
if (tokens.length == 2) {
|
||||
try {
|
||||
return new Socks5Proxy(tokens[0], Integer.valueOf(tokens[1]));
|
||||
Socks5Proxy proxy = new Socks5Proxy(tokens[0], Integer.valueOf(tokens[1]));
|
||||
proxy.resolveAddrLocally(false);
|
||||
return proxy;
|
||||
} catch (UnknownHostException e) {
|
||||
log.error(ExceptionUtils.getStackTrace(e));
|
||||
}
|
||||
|
@ -385,7 +385,7 @@ message DisputeOpenedMessage {
|
||||
NodeAddress sender_node_address = 2;
|
||||
string uid = 3;
|
||||
SupportType type = 4;
|
||||
string updated_multisig_hex = 5;
|
||||
string opener_updated_multisig_hex = 5;
|
||||
PaymentSentMessage payment_sent_message = 6;
|
||||
}
|
||||
|
||||
@ -1465,6 +1465,7 @@ message Trade {
|
||||
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 24;
|
||||
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 25;
|
||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 26;
|
||||
BUYER_RECEIVED_PAYMENT_RECEIVED_MSG = 27;
|
||||
}
|
||||
|
||||
enum Phase {
|
||||
@ -1568,8 +1569,8 @@ 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_arbitrator = 8;
|
||||
string payment_sent_message_state_seller = 7 [deprecated = true];
|
||||
string payment_sent_message_state_arbitrator = 8 [deprecated = true];
|
||||
bytes maker_signature = 9;
|
||||
TradePeer maker = 10;
|
||||
TradePeer taker = 11;
|
||||
@ -1581,6 +1582,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 {
|
||||
@ -1612,7 +1614,7 @@ message TradePeer {
|
||||
string made_multisig_hex = 31;
|
||||
string exchanged_multisig_hex = 32;
|
||||
string updated_multisig_hex = 33;
|
||||
bool deposits_confirmed_message_acked = 34;
|
||||
bool deposits_confirmed_message_acked = 34 [deprecated = true];
|
||||
string deposit_tx_hash = 35;
|
||||
string deposit_tx_hex = 36;
|
||||
string deposit_tx_key = 37;
|
||||
@ -1621,6 +1623,9 @@ message TradePeer {
|
||||
string unsigned_payout_tx_hex = 40;
|
||||
int64 payout_tx_fee = 41;
|
||||
int64 payout_amount = 42;
|
||||
string deposits_confirmed_message_state = 43;
|
||||
string payment_sent_message_state = 44;
|
||||
string payment_received_message_state = 45;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
401
scripts/install_whonix_qubes/INSTALL.md
Normal file
401
scripts/install_whonix_qubes/INSTALL.md
Normal file
@ -0,0 +1,401 @@
|
||||
# Haveno on Qubes/Whonix
|
||||
|
||||
## **Conventions:**
|
||||
|
||||
+ \# – Requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
|
||||
|
||||
+ $ or % – Requires given linux commands to be executed as a regular non-privileged user
|
||||
|
||||
+ \<VAR> – Used to indicate user supplied variable
|
||||
|
||||
---
|
||||
|
||||
## **Installation - Scripted & Manual (GUI + CLI):**
|
||||
### *Acquire release files:*
|
||||
#### In `dispXXXX` AppVM:
|
||||
##### Clone repository
|
||||
```shell
|
||||
% git clone --depth=1 https://github.com/haveno-dex/haveno
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Create TemplateVM, NetVM & AppVM:**
|
||||
#### Scripted
|
||||
##### In `dispXXXX` AppVM:
|
||||
###### Prepare files for transfer to `dom0`
|
||||
```shell
|
||||
% tar -C haveno/scripts/install_qubes/scripts/0-dom0 -zcvf /tmp/haveno.tgz .
|
||||
```
|
||||
|
||||
##### In `dom0`:
|
||||
###### Copy files to `dom0`
|
||||
```shell
|
||||
$ mkdir -p /tmp/haveno && qvm-run -p dispXXXX 'cat /tmp/haveno.tgz' > /tmp/haveno.tgz && tar -C /tmp/haveno -zxfv /tmp/haveno.tgz
|
||||
$ bash /tmp/haveno/0.0-dom0.sh && bash /tmp/haveno/0.1-dom0.sh && bash /tmp/haveno/0.2-dom0.sh
|
||||
```
|
||||
|
||||
#### GUI
|
||||
##### TemplateVM
|
||||
###### Via `Qubes Manager`:
|
||||
|
||||
+ Locate & highlight whonix-workstation-17 (TemplateVM)
|
||||
|
||||
+ Right-Click "whonix-workstation-17" and select "Clone qube" from Drop-Down
|
||||
|
||||
+ Enter "haveno-template" in "Name"
|
||||
|
||||
+ Click OK Button
|
||||
|
||||
##### NetVM
|
||||
###### Via `Qubes Manager`:
|
||||
|
||||
+ Click "New qube" Button
|
||||
|
||||
+ Enter "sys-haveno" for "Name and label"
|
||||
|
||||
+ Click the Button Beside "Name and label" and Select "orange"
|
||||
|
||||
+ Select "whonix-gateway-17" from "Template" Drop-Down
|
||||
|
||||
+ Select "sys-firewall" from "Networking" Drop-Down
|
||||
|
||||
+ Tick "Launch settings after creation" Radio-Box
|
||||
|
||||
+ Click OK
|
||||
|
||||
+ Click "Advanced" Tab
|
||||
|
||||
+ Enter "512" for "Initial memory"
|
||||
|
||||
<p style="text-align: center;"><em>(Within reason, can adjust to personal preference)</em></p>
|
||||
|
||||
+ Enter "512" for "Max memory"
|
||||
|
||||
<p style="text-align: center;"><em>(Within reason, can adjust to personal preference)</em></p>
|
||||
|
||||
+ Tick "Provides network" Radio-Box
|
||||
|
||||
+ Click "Apply" Button
|
||||
|
||||
+ Click "OK" Button
|
||||
|
||||
##### AppVM
|
||||
###### Via `Qubes Manager`:
|
||||
|
||||
+ Click "New qube" Button
|
||||
|
||||
+ Enter "haveno" for "Name and label"
|
||||
|
||||
+ Click the Button Beside "Name and label" and Select "orange"
|
||||
|
||||
+ Select "haveno-template" from "Template" Drop-Down
|
||||
|
||||
+ Select "sys-haveno" from "Networking" Drop-Down
|
||||
|
||||
+ Tick "Launch settings after creation" Radio-Box
|
||||
|
||||
+ Click OK
|
||||
|
||||
+ Click "Advanced" Tab
|
||||
|
||||
+ Enter "2048" for "Initial memory"
|
||||
|
||||
<p style="text-align: center;"><em>(Within reason, can adjust to personal preference)</em></p>
|
||||
|
||||
+ Enter "4096" for "Max memory"
|
||||
|
||||
<p style="text-align: center;"><em>(Within reason, can adjust to personal preference)</em></p>
|
||||
|
||||
+ Click "Apply" Button
|
||||
|
||||
+ Click "OK" Button
|
||||
|
||||
|
||||
#### CLI
|
||||
##### TemplateVM
|
||||
###### In `dom0`:
|
||||
```shell
|
||||
$ qvm-clone whonix-workstation-17 haveno-template
|
||||
```
|
||||
|
||||
##### NetVM
|
||||
##### In `dom0`:
|
||||
```shell
|
||||
$ qvm-create --template whonix-gateway-17 --class AppVM --label=orange --property memory=512 --property maxmem=512 --property netvm=sys-firewall sys-haveno && qvm-prefs --set sys-haveno provides_network True
|
||||
```
|
||||
|
||||
#### AppVM
|
||||
##### In `dom0`:
|
||||
```shell
|
||||
$ qvm-create --template haveno-template --class AppVM --label=orange --property memory=2048 --property maxmem=4096 --property netvm=sys-haveno haveno
|
||||
$ printf 'haveno-Haveno.desktop' | qvm-appmenus --set-whitelist – haveno
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Build TemplateVM, NetVM & AppVM:**
|
||||
#### *TemplateVM Using Precompiled Package via `git` Repository (Scripted)*
|
||||
##### In `dispXXXX` AppVM:
|
||||
```shell
|
||||
% qvm-copy haveno/scripts/install_qubes/scripts/1-TemplateVM/1.0-haveno-templatevm.sh
|
||||
```
|
||||
|
||||
+ Select "haveno-template" for "Target" of Pop-Up
|
||||
|
||||
+ Click OK
|
||||
|
||||
##### In `haveno-template` TemplateVM:
|
||||
```shell
|
||||
% sudo bash QubesIncoming/dispXXXX/1.0-haveno-templatevm.sh "<PACKAGE_ARCHIVE_URL>" "<PACKAGE_PGP_FINGERPRINT>"
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Example:</p>
|
||||
|
||||
```shell
|
||||
% sudo bash QubesIncoming/dispXXXX/1.0-haveno-templatevm.sh "https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/haveno-linux-deb.zip" "ABAF11C65A2970B130ABE3C479BE3E4300411886"
|
||||
```
|
||||
|
||||
#### *TemplateVM Using Precompiled Package From `git` Repository (CLI)*
|
||||
##### In `haveno-template` TemplateVM:
|
||||
###### Download & Import Project PGP Key
|
||||
<p style="text-align: center;">For Whonix On Qubes OS:</p>
|
||||
|
||||
```shell
|
||||
# export https_proxy=http://127.0.0.1:8082
|
||||
# export KEY_SEARCH="<PACKAGE_PGP_FINGERPRINT>"
|
||||
# curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_SEARCH" | gpg --import
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Example:</p>
|
||||
|
||||
```shell
|
||||
# export https_proxy=http://127.0.0.1:8082
|
||||
# export KEY_SEARCH="ABAF11C65A2970B130ABE3C479BE3E4300411886"
|
||||
# curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_SEARCH" | gpg --import
|
||||
```
|
||||
|
||||
<p style="text-align: center;">For Whonix On Anything Other Than Qubes OS:</p>
|
||||
|
||||
```shell
|
||||
# export KEY_SEARCH="<PACKAGE_PGP_FINGERPRINT>"
|
||||
# curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_SEARCH" | gpg --import
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Example:</p>
|
||||
|
||||
```shell
|
||||
# export KEY_SEARCH="ABAF11C65A2970B130ABE3C479BE3E4300411886"
|
||||
# curl -sL "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$KEY_SEARCH" | gpg --import
|
||||
```
|
||||
|
||||
|
||||
###### Download Release Files
|
||||
<p style="text-align: center;">For Whonix On Qubes OS:</p>
|
||||
|
||||
```shell
|
||||
# export https_proxy=http://127.0.0.1:8082
|
||||
# curl -sSLo /tmp/hashes.txt https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/1.0.18-hashes.txt
|
||||
# curl -sSLo /tmp/hashes.txt.sig https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/1.0.18-hashes.txt.sig
|
||||
# curl -sSLo /tmp/haveno.zip https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/haveno_amd64_deb-latest.zip
|
||||
# curl -sSLo /tmp/haveno.zip.sig https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/haveno_amd64_deb-latest.zip.sig
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Note:</p>
|
||||
<p style="text-align: center;"><em>Above are dummy URLS which MUST be replaced with actual working URLs</em></p>
|
||||
|
||||
<p style="text-align: center;">For Whonix On Anything Other Than Qubes OS:</p>
|
||||
|
||||
```shell
|
||||
# curl -sSLo /tmp/hashes.txt https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/1.0.18-hashes.txt
|
||||
# curl -sSLo /tmp/hashes.txt.sig https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/1.0.18-hashes.txt.sig
|
||||
# curl -sSLo /tmp/haveno.zip https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/haveno_amd64_deb-latest.zip
|
||||
# curl -sSLo /tmp/haveno.zip.sig https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/haveno_amd64_deb-latest.zip.sig
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Note:</p>
|
||||
<p style="text-align: center;"><em>Above are dummy URLS which MUST be replaced with actual working URLs</em></p>
|
||||
|
||||
###### Verify Release Files
|
||||
```shell
|
||||
# if gpg --digest-algo SHA256 --verify /tmp/hashes.txt.sig >/dev/null 2>&1; then printf $'SHASUM file has a VALID signature!\n'; else printf $'SHASUMS failed signature check\n' && sleep 5 && exit 1; fi
|
||||
```
|
||||
|
||||
###### Verify Hash, Unpack & Install Package
|
||||
```shell
|
||||
# if [[ $(cat /tmp/hashes.txt) =~ $(sha512sum /tmp/haveno*.zip | awk '{ print $1 }') ]] ; then printf $'SHA Hash IS valid!\n' && mkdir -p /usr/share/desktop-directories && cd /tmp && unzip /tmp/haveno*.zip && apt install -y /tmp/haveno*.deb; else printf $'WARNING: Bad Hash!\n' && exit; fi
|
||||
```
|
||||
|
||||
###### Verify Jar
|
||||
```shell
|
||||
# if [[ $(cat /tmp/desktop*.SHA-256) =~ $(sha256sum /opt/haveno/lib/app/desktop*.jar | awk '{ print $1 }') ]] ; then printf $'SHA Hash IS valid!\n' && printf 'Happy trading!\n'; else printf $'WARNING: Bad Hash!\n' && exit; fi
|
||||
```
|
||||
|
||||
#### *TemplateVM Building From Source via `git` Repository (Scripted)*
|
||||
##### In `dispXXXX` AppVM:
|
||||
```shell
|
||||
% bash haveno/scripts/install_qubes/scripts/1-TemplateVM/1.0-haveno-templatevm.sh "<JDK_PACKAGE_URL>" "<JDK_SHA_HASH>" "<SOURCE_URL>"
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Example:</p>
|
||||
|
||||
```shell
|
||||
% bash haveno/scripts/install_qubes/scripts/1-TemplateVM/1.0-haveno-templatevm.sh "https://download.bell-sw.com/java/21.0.6+10/bellsoft-jdk21.0.6+10-linux-amd64.deb" "a5e3fd9f5323de5fc188180c91e0caa777863b5b" "https://github.com/haveno-dex/haveno"
|
||||
```
|
||||
+ Upon Successful Compilation & Packaging, A `Filecopy` Confirmation Will Be Presented
|
||||
|
||||
+ Select "haveno-template" for "Target" of Pop-Up
|
||||
|
||||
+ Click OK
|
||||
|
||||
##### In `haveno-template` TemplateVM:
|
||||
```shell
|
||||
% sudo apt install -y ./QubesIncoming/dispXXXX/haveno.deb
|
||||
```
|
||||
|
||||
#### *NetVM (Scripted)*
|
||||
##### In `dispXXXX` AppVM:
|
||||
```shell
|
||||
$ qvm-copy haveno/scripts/install_qubes/scripts/2-NetVM/2.0-haveno-netvm.sh
|
||||
```
|
||||
|
||||
+ Select "sys-haveno" for "Target" Within Pop-Up
|
||||
|
||||
+ Click "OK" Button
|
||||
|
||||
##### In `sys-haveno` NetVM:
|
||||
(Allow bootstrap process to complete)
|
||||
```shell
|
||||
% sudo zsh QubesIncoming/dispXXXX/2.0-haveno-netvm.sh
|
||||
```
|
||||
|
||||
#### *NetVM (CLI)*
|
||||
##### In `sys-haveno` NetVM:
|
||||
###### Add `onion-grater` Profile
|
||||
```shell
|
||||
# onion-grater-add 40_haveno
|
||||
```
|
||||
|
||||
###### Restart `onion-grater` Service
|
||||
```shell
|
||||
# systemctl restart onion-grater.service
|
||||
# poweroff
|
||||
```
|
||||
|
||||
#### *AppVM (Scripted)*
|
||||
##### In `dispXXXX` AppVM:
|
||||
```shell
|
||||
$ qvm-copy haveno/scripts/install_qubes/scripts/3-AppVM/3.0-haveno-appvm.sh
|
||||
```
|
||||
|
||||
+ Select "haveno" for "Target" of Pop-Up
|
||||
|
||||
+ Click OK
|
||||
|
||||
##### In `haveno` AppVM:
|
||||
```shell
|
||||
% sudo zsh QubesIncoming/dispXXXX/3.0-haveno-appvm.sh
|
||||
```
|
||||
|
||||
#### *AppVM (CLI)*
|
||||
##### In `haveno` AppVM:
|
||||
###### Adjust `sdwdate` Configuration
|
||||
```shell
|
||||
# mkdir /usr/local/etc/sdwdate-gui.d
|
||||
# printf "gateway=sys-haveno\n" > /usr/local/etc/sdwdate-gui.d/50_user.conf
|
||||
# systemctl restart sdwdate
|
||||
```
|
||||
|
||||
###### Prepare Firewall Settings via `/rw/config/rc.local`
|
||||
```shell
|
||||
# printf "\n# Prepare Local FW Settings\nmkdir -p /usr/local/etc/whonix_firewall.d\n" >> /rw/config/rc.local
|
||||
# printf "\n# Poke FW\nprintf \"EXTERNAL_OPEN_PORTS+=\\\\\" 9999 \\\\\"\\\n\" | tee /usr/local/etc/whonix_firewall.d/50_user.conf\n" >> /rw/config/rc.local
|
||||
# printf "\n# Restart FW\nwhonix_firewall\n\n" >> /rw/config/rc.local
|
||||
```
|
||||
|
||||
###### View & Verify Change
|
||||
```shell
|
||||
# tail /rw/config/rc.local
|
||||
```
|
||||
|
||||
<p style="text-align: center;"><b>Confirm output contains:</b></p>
|
||||
|
||||
> # Poke FW
|
||||
> printf "EXTERNAL_OPEN_PORTS+=\" 9999 \"\n" | tee /usr/local/etc/whonix_firewall.d/50_user.conf
|
||||
>
|
||||
> # Restart FW
|
||||
> whonix_firewall
|
||||
|
||||
###### Restart `whonix_firewall`
|
||||
```shell
|
||||
# whonix_firewall
|
||||
```
|
||||
|
||||
###### Create `haveno-Haveno.desktop`
|
||||
```shell
|
||||
# mkdir -p /home/$(ls /home)/\.local/share/applications
|
||||
# sed 's|/opt/haveno/bin/Haveno|/opt/haveno/bin/Haveno --torControlPort=9051 --socks5ProxyXmrAddress=127.0.0.1:9050 --useTorForXmr=on|g' /opt/haveno/lib/haveno-Haveno.desktop > /home/$(ls /home)/.local/share/applications/haveno-Haveno.desktop
|
||||
# chown -R $(ls /home):$(ls /home) /home/$(ls /home)/.local/share/applications
|
||||
```
|
||||
|
||||
###### View & Verify Change
|
||||
```shell
|
||||
# tail /home/$(ls /home)/.local/share/applications/haveno-Haveno.desktop
|
||||
```
|
||||
|
||||
<p style="text-align: center;"><b>Confirm output contains:</b></p>
|
||||
|
||||
> [Desktop Entry]
|
||||
> Name=Haveno
|
||||
> Comment=Haveno
|
||||
> Exec=/opt/haveno/bin/Haveno --torControlPort=9051 --socks5ProxyXmrAddress=127.0.0.1:9050 --useTorForXmr=on
|
||||
> Icon=/opt/haveno/lib/Haveno.png
|
||||
> Terminal=false
|
||||
> Type=Application
|
||||
> Categories=Network
|
||||
> MimeType=
|
||||
|
||||
###### Poweroff
|
||||
```shell
|
||||
# poweroff
|
||||
```
|
||||
|
||||
### **Remove TemplateVM, NetVM & AppVM:**
|
||||
#### Scripted
|
||||
##### In `dom0`:
|
||||
```shell
|
||||
$ bash /tmp/haveno/0.3-dom0.sh
|
||||
```
|
||||
|
||||
#### GUI
|
||||
##### Via `Qubes Manager`:
|
||||
|
||||
+ Highlight "haveno" (AppVM)
|
||||
|
||||
+ Click "Delete qube"
|
||||
|
||||
+ Enter "haveno"
|
||||
|
||||
+ Click "OK" Button
|
||||
|
||||
+ Highlight "haveno-template" (TemplateVM)
|
||||
|
||||
+ Click "Delete qube"
|
||||
|
||||
+ Enter "haveno-template"
|
||||
|
||||
+ Click "OK" Button
|
||||
|
||||
+ Highlight "sys-haveno" (NetVM)
|
||||
|
||||
+ Click "Delete qube"
|
||||
|
||||
+ Enter "sys-haveno"
|
||||
|
||||
+ Click "OK" Button
|
||||
|
||||
#### CLI
|
||||
##### In `dom0`:
|
||||
```shell
|
||||
$ qvm-shutdown --force --quiet haveno haveno-template sys-haveno && qvm-remove --force --quiet haveno haveno-template sys-haveno
|
||||
```
|
75
scripts/install_whonix_qubes/README.md
Normal file
75
scripts/install_whonix_qubes/README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Install Haveno on Qubes/Whonix
|
||||
|
||||
|
||||
After you already have [`Qubes`](https://www.qubes-os.org/downloads) or [`Whonix`](https://www.whonix.org/wiki/Download) installed:
|
||||
|
||||
1. Download [scripts](https://github.com/haveno-dex/haveno/tree/master/scripts/install_whonix_qubes/scripts).
|
||||
2. Move script(s) to their respective destination (`0.*-dom0.sh` -> `dom0`, `1.0-haveno-templatevm.sh` -> `haveno-template`, etc.).
|
||||
3. Consecutively execute the following commands in their respective destinations.
|
||||
|
||||
---
|
||||
|
||||
## **Create VMs**
|
||||
[`Qubes`](https://www.qubes-os.org/downloads)
|
||||
### **In `dom0`:**
|
||||
|
||||
```shell
|
||||
$ bash 0.0-dom0.sh && bash 0.1-dom0.sh && bash 0.2-dom0.sh
|
||||
```
|
||||
|
||||
[`Whonix`](https://www.whonix.org/wiki/Download) On Anything Other Than [`Qubes`](https://www.qubes-os.org/downloads)
|
||||
|
||||
- Clone `Whonix Workstation` To VM Named `haveno-template`
|
||||
- Clone `Whonix Gateway` To VM Named `sys-haveno`
|
||||
- Create New Linked VM Clone Based On `haveno-template` Named `haveno`
|
||||
|
||||
|
||||
## **Build TemplateVM**
|
||||
### *Via Binary Archive*
|
||||
#### **In `haveno-template` `TemplateVM`:**
|
||||
|
||||
```shell
|
||||
% sudo bash QubesIncoming/dispXXXX/1.0-haveno-templatevm.sh "<PACKAGE_ARCHIVE_URL>" "<PACKAGE_PGP_FINGERPRINT>"
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Example:</p>
|
||||
|
||||
```shell
|
||||
% sudo bash 1.0-haveno-templatevm.sh "https://github.com/havenoexample/haveno-example/releases/download/v1.0.18/haveno-linux-deb.zip" "ABAF11C65A2970B130ABE3C479BE3E4300411886"
|
||||
```
|
||||
|
||||
### *Via Source*
|
||||
#### **In `dispXXXX` `AppVM`:**
|
||||
```shell
|
||||
% bash 1.0-haveno-templatevm.sh "<JDK_PACKAGE_URL>" "<JDK_SHA_HASH>" "<SOURCE_URL>"
|
||||
```
|
||||
|
||||
<p style="text-align: center;">Example:</p>
|
||||
|
||||
```shell
|
||||
% bash 1.0-haveno-templatevm.sh "https://download.bell-sw.com/java/21.0.6+10/bellsoft-jdk21.0.6+10-linux-amd64.deb" "a5e3fd9f5323de5fc188180c91e0caa777863b5b" "https://github.com/haveno-dex/haveno"
|
||||
```
|
||||
|
||||
#### **In `haveno-template` `TemplateVM`:**
|
||||
|
||||
```shell
|
||||
% sudo apt install -y haveno.deb
|
||||
```
|
||||
|
||||
## **Build NetVM**
|
||||
### **In `sys-haveno` `NetVM`:**
|
||||
|
||||
```shell
|
||||
% sudo zsh 3.0-haveno-appvm.sh
|
||||
```
|
||||
|
||||
## **Build AppVM**
|
||||
### **In `haveno` `AppVM`:**
|
||||
|
||||
```shell
|
||||
% sudo zsh 3.0-haveno-appvm.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Complete Documentation Can Be Found [Here](https://github.com/haveno-dex/haveno/blob/master/scripts/install_whonix_qubes/INSTALL.md).
|
6
scripts/install_whonix_qubes/scripts/0-dom0/0.0-dom0.sh
Normal file
6
scripts/install_whonix_qubes/scripts/0-dom0/0.0-dom0.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
## ./haveno-on-qubes/scripts/0.0-dom0.sh
|
||||
|
||||
## Create Haveno TemplateVM:
|
||||
qvm-clone whonix-workstation-17 haveno-template
|
||||
|
6
scripts/install_whonix_qubes/scripts/0-dom0/0.1-dom0.sh
Normal file
6
scripts/install_whonix_qubes/scripts/0-dom0/0.1-dom0.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
## ./haveno-on-qubes/scripts/0.1-dom0.sh
|
||||
|
||||
## Create Haveno NetVM:
|
||||
qvm-create --template whonix-gateway-17 --class AppVM --label=orange --property memory=512 --property maxmem=512 --property netvm=sys-firewall sys-haveno && qvm-prefs --set sys-haveno provides_network True
|
||||
|
7
scripts/install_whonix_qubes/scripts/0-dom0/0.2-dom0.sh
Normal file
7
scripts/install_whonix_qubes/scripts/0-dom0/0.2-dom0.sh
Normal file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
## ./haveno-on-qubes/scripts/0.2-dom0.sh
|
||||
|
||||
## Create Haveno AppVM:
|
||||
qvm-create --template haveno-template --class AppVM --label=orange --property memory=2048 --property maxmem=4096 --property netvm=sys-haveno haveno
|
||||
printf 'haveno-Haveno.desktop' | qvm-appmenus --set-whitelist - haveno
|
||||
|
6
scripts/install_whonix_qubes/scripts/0-dom0/0.3-dom0.sh
Normal file
6
scripts/install_whonix_qubes/scripts/0-dom0/0.3-dom0.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
## ./haveno-on-qubes/scripts/0.3-dom0.sh
|
||||
|
||||
## Remove Haveno GuestVMs
|
||||
qvm-shutdown --force --quiet haveno haveno-template sys-haveno && qvm-remove --force --quiet haveno haveno-template sys-haveno
|
||||
|
@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
## ./haveno-on-qubes/scripts/1.1-haveno-templatevm_maker.sh
|
||||
|
||||
|
||||
function remote {
|
||||
if [[ -z $PRECOMPILED_URL || -z $FINGERPRINT ]]; then
|
||||
printf "\nNo arguments provided!\n\nThis script requires two arguments to be provided:\nBinary URL & PGP Fingerprint\n\nPlease review documentation and try again.\n\nExiting now ...\n"
|
||||
exit 1
|
||||
fi
|
||||
## Update & Upgrade
|
||||
apt update && apt upgrade -y
|
||||
|
||||
|
||||
## Install wget
|
||||
apt install -y wget
|
||||
|
||||
|
||||
## Function to print messages in blue:
|
||||
echo_blue() {
|
||||
echo -e "\033[1;34m$1\033[0m"
|
||||
}
|
||||
|
||||
|
||||
# Function to print error messages in red:
|
||||
echo_red() {
|
||||
echo -e "\033[0;31m$1\033[0m"
|
||||
}
|
||||
|
||||
|
||||
## Sweep for old release files
|
||||
rm *.asc desktop-*-SNAPSHOT-all.jar.SHA-256 haveno*
|
||||
|
||||
|
||||
## Define URL & PGP Fingerprint etc. vars:
|
||||
user_url=$PRECOMPILED_URL
|
||||
base_url=$(printf ${user_url} | awk -F'/' -v OFS='/' '{$NF=""}1')
|
||||
expected_fingerprint=$FINGERPRINT
|
||||
binary_filename=$(awk -F'/' '{ print $NF }' <<< "$user_url")
|
||||
package_filename="haveno.deb"
|
||||
signature_filename="${binary_filename}.sig"
|
||||
key_filename="$(printf "$expected_fingerprint" | tr -d ' ' | sed -E 's/.*(................)/\1/' )".asc
|
||||
wget_flags="--tries=10 --timeout=10 --waitretry=5 --retry-connrefused --show-progress"
|
||||
|
||||
|
||||
## Debug:
|
||||
printf "\nUser URL=$user_url\n"
|
||||
printf "\nBase URL=$base_url\n"
|
||||
printf "\nFingerprint=$expected_fingerprint\n"
|
||||
printf "\nBinary Name=$binary_filename\n"
|
||||
printf "\nPackage Name=$package_filename\n"
|
||||
printf "\nSig Filename=$signature_filename\n"
|
||||
printf "\nKey Filename=$key_filename\n"
|
||||
|
||||
|
||||
## Configure for tinyproxy:
|
||||
export https_proxy=http://127.0.0.1:8082
|
||||
|
||||
|
||||
## Download Haveno binary:
|
||||
echo_blue "Downloading Haveno from URL provided ..."
|
||||
wget "${wget_flags}" -cq "${user_url}" || { echo_red "Failed to download Haveno binary."; exit 1; }
|
||||
|
||||
|
||||
## Download Haveno signature file:
|
||||
echo_blue "Downloading Haveno signature ..."
|
||||
wget "${wget_flags}" -cq "${base_url}""${signature_filename}" || { echo_red "Failed to download Haveno signature."; exit 1; }
|
||||
|
||||
|
||||
## Download the GPG key:
|
||||
echo_blue "Downloading signing GPG key ..."
|
||||
wget "${wget_flags}" -cqO "${key_filename}" "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x$(echo "$expected_fingerprint" | tr -d ' ')" || { echo_red "Failed to download GPG key."; exit 1; }
|
||||
|
||||
|
||||
## Import the GPG key:
|
||||
echo_blue "Importing the GPG key ..."
|
||||
gpg --import "${key_filename}" || { echo_red "Failed to import GPG key."; exit 1; }
|
||||
|
||||
|
||||
## Extract imported fingerprints:
|
||||
imported_fingerprints=$(gpg --with-colons --fingerprint | grep -A 1 'pub' | grep 'fpr' | cut -d: -f10 | tr -d '\n')
|
||||
|
||||
|
||||
## Remove spaces from the expected fingerprint for comparison:
|
||||
formatted_expected_fingerprint=$(echo "${expected_fingerprint}" | tr -d ' ')
|
||||
|
||||
|
||||
## Check if the expected fingerprint is in the list of imported fingerprints:
|
||||
if [[ ! "${imported_fingerprints}" =~ "${formatted_expected_fingerprint}" ]]; then
|
||||
echo_red "The imported GPG key fingerprint does not match the expected fingerprint."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
## Verify the downloaded binary with the signature:
|
||||
echo_blue "Verifying the signature of the downloaded file ..."
|
||||
if gpg --digest-algo SHA256 --verify "${signature_filename}" >/dev/null 2>&1; then
|
||||
7z x "${binary_filename}" && mv haveno*.deb "${package_filename}";
|
||||
else echo_red "Verification failed!" && sleep 5
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
|
||||
echo_blue "Haveno binaries have been successfully verified."
|
||||
|
||||
|
||||
# Install Haveno:
|
||||
echo_blue "Installing Haveno ..."
|
||||
apt install -y ./"${package_filename}" || { echo_red "Failed to install Haveno."; exit 1; }
|
||||
|
||||
## Finalize
|
||||
echo_blue "Haveno TemplateVM installation and configuration complete."
|
||||
echo_blue "\nHappy Trading\!\n"
|
||||
printf "%s \n" "Press [ENTER] to complete ..."
|
||||
read ans
|
||||
#exit
|
||||
poweroff
|
||||
}
|
||||
|
||||
|
||||
function build {
|
||||
if [[ -z $JAVA_URL || -z $JAVA_SHA1 || -z $SOURCE_URL ]]; then
|
||||
printf "\nNo arguments provided!\n\nThis script requires three argument to be provided:\n\nURL for Java 21 JDK Debian Package\n\nSHA1 Hash for Java 21 JDK Debian Package\n\nURL for Remote Git Source Repository\n\nPlease review documentation and try again.\n\nExiting now ...\n"
|
||||
exit 1
|
||||
fi
|
||||
# Dependancies
|
||||
sudo apt install -y make git expect fakeroot binutils
|
||||
|
||||
# Java
|
||||
curl -fsSLo jdk21.deb ${JAVA_URL}
|
||||
if [[ $(shasum ./jdk21.deb | awk '{ print $1 }') == ${JAVA_SHA1} ]] ; then printf $'SHA Hash IS valid!\n'; else printf $'WARNING: Bad Hash!\n' && exit; fi
|
||||
sudo apt install -y ./jdk21.deb
|
||||
|
||||
# Build
|
||||
git clone --depth=1 $SOURCE_URL
|
||||
GIT_DIR=$(awk -F'/' '{ print $NF }' <<< "$SOURCE_URL")
|
||||
cd ${GIT_DIR}
|
||||
git checkout master
|
||||
sed -i 's|XMR_STAGENET|XMR_MAINNET|g' desktop/package/package.gradle
|
||||
./gradlew clean build --refresh-keys --refresh-dependencies
|
||||
|
||||
# Package
|
||||
# Expect
|
||||
cat <<DONE >> /tmp/haveno_package_deb.exp
|
||||
set send_slow {1 .1}
|
||||
proc send {ignore arg} {
|
||||
sleep 1.1
|
||||
exp_send -s -- \$arg
|
||||
}
|
||||
set timeout -1
|
||||
spawn ./gradlew packageInstallers --console=plain
|
||||
match_max 100000
|
||||
expect -exact ""
|
||||
send -- "y\r"
|
||||
expect -exact ""
|
||||
send -- "y\r"
|
||||
expect -exact ""
|
||||
send -- "y\r"
|
||||
expect -exact "app-image"
|
||||
send -- \x03
|
||||
expect eof
|
||||
DONE
|
||||
|
||||
# Package
|
||||
expect -f /tmp/haveno_package_deb.exp && find ./ -name '*.deb' -exec qvm-copy {} \;
|
||||
printf "\nHappy Trading!\n"
|
||||
|
||||
}
|
||||
|
||||
if ! [[ $# -eq 2 || $# -eq 3 ]] ; then
|
||||
printf "\nFor this script to function, user supplied arguments are required.\n\n"
|
||||
printf "\nPlease review documentation and try again.\n\n"
|
||||
fi
|
||||
|
||||
if [[ $# -eq 2 ]] ; then
|
||||
PRECOMPILED_URL=$1
|
||||
FINGERPRINT=$2
|
||||
remote
|
||||
fi
|
||||
|
||||
if [[ $# -eq 3 ]] ; then
|
||||
JAVA_URL=$1
|
||||
JAVA_SHA1=$2
|
||||
SOURCE_URL=$3
|
||||
build
|
||||
fi
|
@ -0,0 +1,30 @@
|
||||
#!/bin/zsh
|
||||
## ./haveno-on-qubes/scripts/2.0-haveno-netvm_taker.sh
|
||||
|
||||
## Function to print messages in blue:
|
||||
echo_blue() {
|
||||
echo -e "\033[1;34m$1\033[0m"
|
||||
}
|
||||
|
||||
|
||||
# Function to print error messages in red:
|
||||
echo_red() {
|
||||
echo -e "\033[0;31m$1\033[0m"
|
||||
}
|
||||
|
||||
|
||||
## onion-grater
|
||||
# Add onion-grater Profile
|
||||
echo_blue "\nAdding onion-grater Profile ..."
|
||||
onion-grater-add 40_haveno
|
||||
|
||||
|
||||
# Restart onion-grater
|
||||
echo_blue "\nRestarting onion-grater Service ..."
|
||||
systemctl restart onion-grater.service
|
||||
echo_blue "Haveno NetVM configuration complete."
|
||||
printf "%s \n" "Press [ENTER] to complete ..."
|
||||
read ans
|
||||
#exit
|
||||
poweroff
|
||||
|
@ -0,0 +1,61 @@
|
||||
#!/bin/zsh
|
||||
## ./haveno-on-qubes/scripts/3.0-haveno-appvm_taker.sh
|
||||
|
||||
## Function to print messages in blue:
|
||||
echo_blue() {
|
||||
echo -e "\033[1;34m$1\033[0m"
|
||||
}
|
||||
|
||||
|
||||
# Function to print error messages in red:
|
||||
echo_red() {
|
||||
echo -e "\033[0;31m$1\033[0m"
|
||||
}
|
||||
|
||||
|
||||
## Adjust sdwdate Configuration
|
||||
mkdir -p /usr/local/etc/sdwdate-gui.d
|
||||
printf "gateway=sys-haveno\n" > /usr/local/etc/sdwdate-gui.d/50_user.conf
|
||||
systemctl restart sdwdate
|
||||
|
||||
|
||||
## Prepare Firewall Settings
|
||||
echo_blue "\nConfiguring FW ..."
|
||||
printf "\n# Prepare Local FW Settings\nmkdir -p /usr/local/etc/whonix_firewall.d\n" >> /rw/config/rc.local
|
||||
printf "\n# Poke FW\nprintf \"EXTERNAL_OPEN_PORTS+=\\\\\" 9999 \\\\\"\\\n\" | tee /usr/local/etc/whonix_firewall.d/50_user.conf\n" >> /rw/config/rc.local
|
||||
printf "\n# Restart FW\nwhonix_firewall\n\n" >> /rw/config/rc.local
|
||||
|
||||
|
||||
## View & Verify Change
|
||||
echo_blue "\nReview the following output and be certain in matches documentation!\n"
|
||||
tail /rw/config/rc.local
|
||||
printf "%s \n" "Press [ENTER] to continue ..."
|
||||
read ans
|
||||
:
|
||||
|
||||
|
||||
## Restart FW
|
||||
echo_blue "\nRestarting Whonix FW ..."
|
||||
whonix_firewall
|
||||
|
||||
|
||||
### Create Desktop Launcher:
|
||||
echo_blue "Creating desktop launcher ..."
|
||||
mkdir -p /home/$(ls /home)/\.local/share/applications
|
||||
sed 's|/opt/haveno/bin/Haveno|/opt/haveno/bin/Haveno --torControlPort=9051 --torControlUseSafeCookieAuth --torControlCookieFile=/var/run/tor/control.authcookie --socks5ProxyXmrAddress=127.0.0.1:9050 --useTorForXmr=on|g' /opt/haveno/lib/haveno-Haveno.desktop > /home/$(ls /home)/.local/share/applications/haveno-Haveno.desktop
|
||||
chown -R $(ls /home):$(ls /home) /home/$(ls /home)/.local/share/applications/haveno-Haveno.desktop
|
||||
|
||||
|
||||
## View & Verify Change
|
||||
echo_blue "\nReview the following output and be certain in matches documentation!\n"
|
||||
tail /home/$(ls /home)/.local/share/applications/haveno-Haveno.desktop
|
||||
printf "%s \n" "Press [ENTER] to continue ..."
|
||||
read ans
|
||||
:
|
||||
|
||||
echo_blue "Haveno AppVM configuration complete."
|
||||
echo_blue "Refresh applications via Qubes Manager GUI now."
|
||||
printf "%s \n" "Press [ENTER] to complete ..."
|
||||
read ans
|
||||
#exit
|
||||
poweroff
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user