Merge branch 'haveno-dex:master' into federation-test

This commit is contained in:
preland 2024-06-24 13:38:59 -05:00 committed by GitHub
commit 327ce45a4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 3744 additions and 125 deletions

View File

@ -49,7 +49,7 @@ configure(subprojects) {
gsonVersion = '2.8.5' gsonVersion = '2.8.5'
guavaVersion = '32.1.1-jre' guavaVersion = '32.1.1-jre'
guiceVersion = '7.0.0' guiceVersion = '7.0.0'
moneroJavaVersion = '0.8.29' moneroJavaVersion = '0.8.31'
httpclient5Version = '5.0' httpclient5Version = '5.0'
hamcrestVersion = '2.2' hamcrestVersion = '2.2'
httpclientVersion = '4.5.12' httpclientVersion = '4.5.12'
@ -452,14 +452,14 @@ configure(project(':core')) {
doLast { doLast {
// get monero binaries download url // get monero binaries download url
Map moneroBinaries = [ Map moneroBinaries = [
'linux-x86_64' : 'https://github.com/haveno-dex/monero/releases/download/release2/monero-bins-haveno-linux.tar.gz', 'linux-x86_64' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-linux-x86_64.tar.gz',
'linux-x86_64-sha256' : '3537fe2006997a1065748d27e9513ac3e0c942ab56a97a6e43065ddfd1820394', 'linux-x86_64-sha256' : '591e63c1e3249e0cfbba74f0302022160f64f049d06abff95417ad3ecb588581',
'linux-aarch64' : 'https://github.com/haveno-dex/monero/releases/download/release2/monero-bins-haveno-linux-aarch64.tar.gz', 'linux-aarch64' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-linux-aarch64.tar.gz',
'linux-aarch64-sha256' : '6ff81c61780fe08defbd6576bd93c6711cf5ad3e79be0e3bc2184ff11cc6a472', 'linux-aarch64-sha256' : 'fb0a91d07dbbc30646af8007205dbd11c59fb1d124a3b2d703511d8ee2739acc',
'mac' : 'https://github.com/haveno-dex/monero/releases/download/release2/monero-bins-haveno-mac.tar.gz', 'mac' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-mac.tar.gz',
'mac-sha256' : 'c7cafe1000a5839f02d02ed2edce5b1df3a06b5c77f4d91eaba106d948347730', 'mac-sha256' : '9eb01951976767372a3d10180c092af937afe6494928ea73e311476be5c0eba3',
'windows' : 'https://github.com/haveno-dex/monero/releases/download/release2/monero-bins-haveno-windows.zip', 'windows' : 'https://github.com/haveno-dex/monero/releases/download/release3/monero-bins-haveno-windows.zip',
'windows-sha256' : '9b900faefa75f354870646989484978d1fb11add392ffd05eb5abe7e514e395a' 'windows-sha256' : '49b84fab3a1f69564068fecff105b6079b843d99792409dffca4a66eb279288f'
] ]
String osKey String osKey
@ -532,6 +532,7 @@ configure(project(':core')) {
ext.downloadAndVerifyDependencies = { String archiveURL, String archiveSHA256, File destinationArchiveFile -> ext.downloadAndVerifyDependencies = { String archiveURL, String archiveSHA256, File destinationArchiveFile ->
ext.dependencyDownloadedAndVerified = false ext.dependencyDownloadedAndVerified = false
// if archive exists, check to see if its already up to date // if archive exists, check to see if its already up to date
if (destinationArchiveFile.exists()) { if (destinationArchiveFile.exists()) {
println "Verifying existing archive ${destinationArchiveFile}" println "Verifying existing archive ${destinationArchiveFile}"
@ -545,14 +546,15 @@ configure(project(':core')) {
} }
} }
// download archives
println "Downloading ${archiveURL}" println "Downloading ${archiveURL}"
ant.get(src: archiveURL, dest: destinationArchiveFile) ant.get(src: archiveURL, dest: destinationArchiveFile)
println 'Download saved to ' + destinationArchiveFile println 'Download saved to ' + destinationArchiveFile
// verify checksum
println 'Verifying checksum for downloaded binary ...' println 'Verifying checksum for downloaded binary ...'
ant.archiveHash = archiveSHA256 ant.archiveHash = archiveSHA256
// use a different verifyProperty name from existing verification or it will always fail ant.checksum(file: destinationArchiveFile, algorithm: 'SHA-256', property: '${archiveHash}', verifyProperty: 'downloadedHashMatches') // use a different verifyProperty name from existing verification or it will always fail
ant.checksum(file: destinationArchiveFile, algorithm: 'SHA-256', property: '${archiveHash}', verifyProperty: 'downloadedHashMatches')
if (ant.properties['downloadedHashMatches'] != 'true') { if (ant.properties['downloadedHashMatches'] != 'true') {
ant.fail('Checksum mismatch: Downloaded archive has a different checksum than expected') ant.fail('Checksum mismatch: Downloaded archive has a different checksum than expected')
} }
@ -603,7 +605,7 @@ configure(project(':desktop')) {
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.github.johnrengelman.shadow'
apply from: 'package/package.gradle' apply from: 'package/package.gradle'
version = '1.0.7-SNAPSHOT' version = '1.0.8-SNAPSHOT'
jar.manifest.attributes( jar.manifest.attributes(
"Implementation-Title": project.name, "Implementation-Title": project.name,

View File

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

View File

@ -24,6 +24,7 @@ import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
@ -38,13 +39,14 @@ public class ZipUtils {
* @param dir The directory to create the zip from. * @param dir The directory to create the zip from.
* @param out The stream to write to. * @param out The stream to write to.
*/ */
public static void zipDirToStream(File dir, OutputStream out, int bufferSize) throws Exception { public static void zipDirToStream(File dir, OutputStream out, int bufferSize, Collection<File> excludedFiles) throws Exception {
// Get all files in directory and subdirectories. // Get all files in directory and subdirectories.
ArrayList<String> fileList = new ArrayList<>(); List<File> fileList = new ArrayList<>();
getFilesRecursive(dir, fileList); getFilesRecursive(dir, fileList, excludedFiles);
try (ZipOutputStream zos = new ZipOutputStream(out)) { try (ZipOutputStream zos = new ZipOutputStream(out)) {
for (String filePath : fileList) { for (File file : fileList) {
String filePath = file.getAbsolutePath();
log.info("Compressing: " + filePath); log.info("Compressing: " + filePath);
// Creates a zip entry. // Creates a zip entry.
@ -73,14 +75,15 @@ public class ZipUtils {
/** /**
* Get files list from the directory recursive to the subdirectory. * Get files list from the directory recursive to the subdirectory.
*/ */
public static void getFilesRecursive(File directory, List<String> fileList) { public static void getFilesRecursive(File directory, List<File> fileList, Collection<File> excludedFiles) {
File[] files = directory.listFiles(); File[] files = directory.listFiles();
if (files != null && files.length > 0) { if (files != null && files.length > 0) {
for (File file : files) { for (File file : files) {
if (excludedFiles != null && excludedFiles.contains(file)) continue;
if (file.isFile()) { if (file.isFile()) {
fileList.add(file.getAbsolutePath()); fileList.add(file);
} else { } else {
getFilesRecursive(file, fileList); getFilesRecursive(file, fileList, excludedFiles);
} }
} }
} }

View File

@ -27,11 +27,13 @@ import haveno.common.crypto.KeyStorage;
import haveno.common.file.FileUtil; import haveno.common.file.FileUtil;
import haveno.common.persistence.PersistenceManager; import haveno.common.persistence.PersistenceManager;
import haveno.common.util.ZipUtils; import haveno.common.util.ZipUtils;
import haveno.core.xmr.wallet.XmrWalletService;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import lombok.Getter; import lombok.Getter;
@ -139,6 +141,7 @@ public class CoreAccountService {
} }
} }
// TODO: share common code with BackupView to backup
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) { public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
if (!accountExists()) throw new IllegalStateException("Cannot backup non existing account"); if (!accountExists()) throw new IllegalStateException("Cannot backup non existing account");
@ -149,9 +152,16 @@ public class CoreAccountService {
PipedInputStream in = new PipedInputStream(bufferSize); // pipe the serialized account object to stream which will be read by the consumer PipedInputStream in = new PipedInputStream(bufferSize); // pipe the serialized account object to stream which will be read by the consumer
PipedOutputStream out = new PipedOutputStream(in); PipedOutputStream out = new PipedOutputStream(in);
log.info("Zipping directory " + dataDir); log.info("Zipping directory " + dataDir);
// exclude monero binaries from backup so they're reinstalled with permissions
List<File> excludedFiles = Arrays.asList(
new File(XmrWalletService.MONERO_WALLET_RPC_PATH),
new File(XmrLocalNode.MONEROD_PATH)
);
new Thread(() -> { new Thread(() -> {
try { try {
ZipUtils.zipDirToStream(dataDir, out, bufferSize); ZipUtils.zipDirToStream(dataDir, out, bufferSize, excludedFiles);
} catch (Exception ex) { } catch (Exception ex) {
error.accept(ex); error.accept(ex);
} }

View File

@ -253,12 +253,12 @@ public final class XmrConnectionService {
connectionList.setAutoSwitch(autoSwitch); connectionList.setAutoSwitch(autoSwitch);
} }
public boolean isConnectionLocal() { public boolean isConnectionLocalHost() {
return isConnectionLocal(getConnection()); return isConnectionLocalHost(getConnection());
} }
public boolean isConnectionTor() { public boolean isProxyApplied() {
return useTorProxy(getConnection()); return isProxyApplied(getConnection());
} }
public long getRefreshPeriodMs() { public long getRefreshPeriodMs() {
@ -328,26 +328,26 @@ public final class XmrConnectionService {
downloadListener.doneDownload(); downloadListener.doneDownload();
} }
private boolean isConnectionLocal(MoneroRpcConnection connection) { private boolean isConnectionLocalHost(MoneroRpcConnection connection) {
return connection != null && HavenoUtils.isLocalHost(connection.getUri()); return connection != null && HavenoUtils.isLocalHost(connection.getUri());
} }
private long getDefaultRefreshPeriodMs() { private long getDefaultRefreshPeriodMs() {
MoneroRpcConnection connection = getConnection(); MoneroRpcConnection connection = getConnection();
if (connection == null) return XmrLocalNode.REFRESH_PERIOD_LOCAL_MS; if (connection == null) return XmrLocalNode.REFRESH_PERIOD_LOCAL_MS;
if (isConnectionLocal(connection)) { if (isConnectionLocalHost(connection)) {
if (lastInfo != null && (lastInfo.isBusySyncing() || (lastInfo.getHeightWithoutBootstrap() != null && lastInfo.getHeightWithoutBootstrap() > 0 && lastInfo.getHeightWithoutBootstrap() < lastInfo.getHeight()))) return REFRESH_PERIOD_HTTP_MS; // refresh slower if syncing or bootstrapped if (lastInfo != null && (lastInfo.isBusySyncing() || (lastInfo.getHeightWithoutBootstrap() != null && lastInfo.getHeightWithoutBootstrap() > 0 && lastInfo.getHeightWithoutBootstrap() < lastInfo.getHeight()))) return REFRESH_PERIOD_HTTP_MS; // refresh slower if syncing or bootstrapped
else return XmrLocalNode.REFRESH_PERIOD_LOCAL_MS; // TODO: announce faster refresh after done syncing else return XmrLocalNode.REFRESH_PERIOD_LOCAL_MS; // TODO: announce faster refresh after done syncing
} else if (useTorProxy(connection)) { } else if (isProxyApplied(connection)) {
return REFRESH_PERIOD_ONION_MS; return REFRESH_PERIOD_ONION_MS;
} else { } else {
return REFRESH_PERIOD_HTTP_MS; return REFRESH_PERIOD_HTTP_MS;
} }
} }
private boolean useTorProxy(MoneroRpcConnection connection) { private boolean isProxyApplied(MoneroRpcConnection connection) {
if (connection == null) return false; if (connection == null) return false;
return connection.isOnion() || (preferences.getUseTorForXmr().isUseTorForXmr() && !HavenoUtils.isLocalHost(connection.getUri())); return connection.isOnion() || (preferences.getUseTorForXmr().isUseTorForXmr() && !HavenoUtils.isPrivateIp(connection.getUri()));
} }
private void initialize() { private void initialize() {
@ -475,7 +475,7 @@ public final class XmrConnectionService {
// set connection proxies // set connection proxies
log.info("TOR proxy URI: " + getProxyUri()); log.info("TOR proxy URI: " + getProxyUri());
for (MoneroRpcConnection connection : connectionManager.getConnections()) { for (MoneroRpcConnection connection : connectionManager.getConnections()) {
if (useTorProxy(connection)) connection.setProxyUri(getProxyUri()); if (isProxyApplied(connection)) connection.setProxyUri(getProxyUri());
} }
// restore auto switch // restore auto switch
@ -495,7 +495,7 @@ public final class XmrConnectionService {
// set connection from startup argument if given // set connection from startup argument if given
connectionManager.setAutoSwitch(false); connectionManager.setAutoSwitch(false);
MoneroRpcConnection connection = new MoneroRpcConnection(config.xmrNode, config.xmrNodeUsername, config.xmrNodePassword).setPriority(1); MoneroRpcConnection connection = new MoneroRpcConnection(config.xmrNode, config.xmrNodeUsername, config.xmrNodePassword).setPriority(1);
if (useTorProxy(connection)) connection.setProxyUri(getProxyUri()); if (isProxyApplied(connection)) connection.setProxyUri(getProxyUri());
connectionManager.setConnection(connection); connectionManager.setConnection(connection);
// start local node if applicable // start local node if applicable
@ -530,7 +530,7 @@ public final class XmrConnectionService {
} }
private void onConnectionChanged(MoneroRpcConnection currentConnection) { private void onConnectionChanged(MoneroRpcConnection currentConnection) {
if (isShutDownStarted) return; if (isShutDownStarted || !accountService.isAccountOpen()) return;
if (currentConnection == null) { if (currentConnection == null) {
log.warn("Setting daemon connection to null"); log.warn("Setting daemon connection to null");
Thread.dumpStack(); Thread.dumpStack();
@ -557,6 +557,7 @@ public final class XmrConnectionService {
// update polling // update polling
doPollDaemon(); doPollDaemon();
if (currentConnection != getConnection()) return; // polling can change connection
UserThread.runAfter(() -> updatePolling(), getRefreshPeriodMs() / 1000); UserThread.runAfter(() -> updatePolling(), getRefreshPeriodMs() / 1000);
// notify listeners in parallel // notify listeners in parallel
@ -607,13 +608,22 @@ public final class XmrConnectionService {
try { try {
lastInfo = daemon.getInfo(); lastInfo = daemon.getInfo();
} catch (Exception e) { } catch (Exception e) {
try {
// skip handling if shutting down
if (isShutDownStarted) return;
// fallback to provided nodes if custom connection fails on startup
if (lastInfo == null && "".equals(config.xmrNode) && preferences.getMoneroNodesOption() == XmrNodes.MoneroNodesOption.CUSTOM) {
log.warn("Failed to fetch daemon info from custom node on startup, falling back to provided nodes: " + e.getMessage());
preferences.setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.PROVIDED.ordinal());
initializeConnections();
return;
}
// switch to best connection
log.warn("Failed to fetch daemon info, trying to switch to best connection: " + e.getMessage()); log.warn("Failed to fetch daemon info, trying to switch to best connection: " + e.getMessage());
switchToBestConnection(); switchToBestConnection();
lastInfo = daemon.getInfo(); lastInfo = daemon.getInfo(); // caught internally if still fails
} catch (Exception e2) {
throw e2; // caught internally
}
} }
// connected to daemon // connected to daemon

View File

@ -394,7 +394,7 @@ public class HavenoSetup {
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
log.error(e.toString()); log.warn("Failed to install Monero binaries: " + e.toString());
} }
} }

View File

@ -268,9 +268,9 @@ public class WalletAppSetup {
private String getXmrDaemonNetworkAsString() { private String getXmrDaemonNetworkAsString() {
String postFix; String postFix;
if (xmrConnectionService.isConnectionLocal()) if (xmrConnectionService.isConnectionLocalHost())
postFix = " " + Res.get("mainView.footer.localhostMoneroNode"); postFix = " " + Res.get("mainView.footer.localhostMoneroNode");
else if (xmrConnectionService.isConnectionTor()) else if (xmrConnectionService.isProxyApplied())
postFix = " " + Res.get("mainView.footer.usingTor"); postFix = " " + Res.get("mainView.footer.usingTor");
else else
postFix = ""; postFix = "";
@ -279,7 +279,7 @@ public class WalletAppSetup {
private String getXmrWalletNetworkAsString() { private String getXmrWalletNetworkAsString() {
String postFix; String postFix;
if (xmrConnectionService.isConnectionLocal()) if (xmrConnectionService.isConnectionLocalHost())
postFix = " " + Res.get("mainView.footer.localhostMoneroNode"); postFix = " " + Res.get("mainView.footer.localhostMoneroNode");
else if (xmrWalletService.isProxyApplied()) else if (xmrWalletService.isProxyApplied())
postFix = " " + Res.get("mainView.footer.usingTor"); postFix = " " + Res.get("mainView.footer.usingTor");

View File

@ -44,14 +44,14 @@ public class LanguageUtil {
"fa", // Persian "fa", // Persian
"it", // Italian "it", // Italian
"cs", // Czech "cs", // Czech
"pl" // Polish "pl", // Polish
"tr" // Turkish
/* /*
// not translated yet // not translated yet
"el", // Greek "el", // Greek
"sr-Latn-RS", // Serbian [Latin] (Serbia) "sr-Latn-RS", // Serbian [Latin] (Serbia)
"hu", // Hungarian "hu", // Hungarian
"ro", // Romanian "ro", // Romanian
"tr" // Turkish
"iw", // Hebrew "iw", // Hebrew
"hi", // Hindi "hi", // Hindi
"ko", // Korean "ko", // Korean

View File

@ -115,6 +115,7 @@ public class OfferBookService {
p2PService.addHashSetChangedListener(new HashMapChangedListener() { p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override @Override
public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) { public void onAdded(Collection<ProtectedStorageEntry> protectedStorageEntries) {
UserThread.execute(() -> {
protectedStorageEntries.forEach(protectedStorageEntry -> { protectedStorageEntries.forEach(protectedStorageEntry -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) { if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload(); OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
@ -128,10 +129,12 @@ public class OfferBookService {
} }
} }
}); });
});
} }
@Override @Override
public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) { public void onRemoved(Collection<ProtectedStorageEntry> protectedStorageEntries) {
UserThread.execute(() -> {
protectedStorageEntries.forEach(protectedStorageEntry -> { protectedStorageEntries.forEach(protectedStorageEntry -> {
if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) { if (protectedStorageEntry.getProtectedStoragePayload() instanceof OfferPayload) {
OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload(); OfferPayload offerPayload = (OfferPayload) protectedStorageEntry.getProtectedStoragePayload();
@ -145,6 +148,7 @@ public class OfferBookService {
} }
} }
}); });
});
} }
}); });
@ -278,9 +282,11 @@ public class OfferBookService {
keyImagePoller.addListener(new XmrKeyImageListener() { keyImagePoller.addListener(new XmrKeyImageListener() {
@Override @Override
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) { public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses) {
UserThread.execute(() -> {
for (String keyImage : spentStatuses.keySet()) { for (String keyImage : spentStatuses.keySet()) {
updateAffectedOffers(keyImage); updateAffectedOffers(keyImage);
} }
});
} }
}); });
@ -293,7 +299,7 @@ public class OfferBookService {
} }
private long getKeyImageRefreshPeriodMs() { private long getKeyImageRefreshPeriodMs() {
return xmrConnectionService.isConnectionLocal() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE; return xmrConnectionService.isConnectionLocalHost() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE;
} }
private void updateAffectedOffers(String keyImage) { private void updateAffectedOffers(String keyImage) {
@ -301,12 +307,8 @@ public class OfferBookService {
if (offer.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) { if (offer.getOfferPayload().getReserveTxKeyImages().contains(keyImage)) {
synchronized (offerBookChangedListeners) { synchronized (offerBookChangedListeners) {
offerBookChangedListeners.forEach(listener -> { offerBookChangedListeners.forEach(listener -> {
// notify off thread to avoid deadlocking
new Thread(() -> {
listener.onRemoved(offer); listener.onRemoved(offer);
listener.onAdded(offer); listener.onAdded(offer);
}).start();
}); });
} }
} }

View File

@ -288,7 +288,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
private long getKeyImageRefreshPeriodMs() { private long getKeyImageRefreshPeriodMs() {
return xmrConnectionService.isConnectionLocal() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE; return xmrConnectionService.isConnectionLocalHost() ? KEY_IMAGE_REFRESH_PERIOD_MS_LOCAL : KEY_IMAGE_REFRESH_PERIOD_MS_REMOTE;
} }
public void onAllServicesInitialized() { public void onAllServicesInitialized() {

View File

@ -274,15 +274,15 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
} }
dispute.setDisputeResult(disputeResult); dispute.setDisputeResult(disputeResult);
// sync and save wallet
if (!trade.isPayoutPublished()) trade.syncAndPollWallet();
// update multisig hex // update multisig hex
if (disputeClosedMessage.getUpdatedMultisigHex() != null) trade.getArbitrator().setUpdatedMultisigHex(disputeClosedMessage.getUpdatedMultisigHex()); if (disputeClosedMessage.getUpdatedMultisigHex() != null) trade.getArbitrator().setUpdatedMultisigHex(disputeClosedMessage.getUpdatedMultisigHex());
if (trade.walletExists()) trade.importMultisigHex(); if (trade.walletExists()) trade.importMultisigHex();
// sync and save wallet
if (!trade.isPayoutPublished()) trade.syncAndPollWallet();
// attempt to sign and publish dispute payout tx if given and not already published // attempt to sign and publish dispute payout tx if given and not already published
if (disputeClosedMessage.getUnsignedPayoutTxHex() != null && !trade.isPayoutPublished()) { if (!trade.isPayoutPublished() && disputeClosedMessage.getUnsignedPayoutTxHex() != null) {
// wait to sign and publish payout tx if defer flag set // wait to sign and publish payout tx if defer flag set
if (disputeClosedMessage.isDeferPublishPayout()) { if (disputeClosedMessage.isDeferPublishPayout()) {

View File

@ -41,6 +41,14 @@ public class ClosedTradableUtil {
public static Map<String, Long> getTotalVolumeByCurrency(List<Tradable> tradableList) { public static Map<String, Long> getTotalVolumeByCurrency(List<Tradable> tradableList) {
Map<String, Long> map = new HashMap<>(); Map<String, Long> map = new HashMap<>();
tradableList.stream() tradableList.stream()
.filter(tradable -> {
if (tradable instanceof Trade) {
Trade trade = castToTrade(tradable);
return trade.isCompleted(); // TODO: does not consider if trade was reverted by arbitrator
} else {
return false;
}
})
.flatMap(tradable -> tradable.getOptionalVolume().stream()) .flatMap(tradable -> tradable.getOptionalVolume().stream())
.forEach(volume -> { .forEach(volume -> {
String currencyCode = volume.getCurrencyCode(); String currencyCode = volume.getCurrencyCode();

View File

@ -40,6 +40,7 @@ import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress; import haveno.network.p2p.NodeAddress;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI; import java.net.URI;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@ -418,12 +419,32 @@ public class HavenoUtils {
/** /**
* Check if the given URI is on local host. * Check if the given URI is on local host.
*/ */
public static boolean isLocalHost(String uri) { public static boolean isLocalHost(String uriString) {
try { try {
String host = new URI(uri).getHost(); String host = new URI(uriString).getHost();
return LOOPBACK_HOST.equals(host) || LOCALHOST.equals(host); return LOOPBACK_HOST.equals(host) || LOCALHOST.equals(host);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); return false;
}
}
/**
* Check if the given URI is local or a private IP address.
*/
public static boolean isPrivateIp(String uriString) {
if (isLocalHost(uriString)) return true;
try {
// get the host
URI uri = new URI(uriString);
String host = uri.getHost();
// check if private IP address
if (host == null) return false;
InetAddress inetAddress = InetAddress.getByName(host);
return inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress() || inetAddress.isSiteLocalAddress();
} catch (Exception e) {
return false;
} }
} }

View File

@ -92,6 +92,7 @@ import javafx.collections.ObservableList;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroError;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.common.TaskLooper; import monero.common.TaskLooper;
import monero.daemon.MoneroDaemon; import monero.daemon.MoneroDaemon;
@ -886,6 +887,8 @@ public abstract class Trade implements Tradable, Model {
try { try {
doImportMultisigHex(); doImportMultisigHex();
break; break;
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to import multisig hex, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage()); log.warn("Failed to import multisig hex, attempt={}/{}, tradeId={}, error={}", i + 1, TradeProtocol.MAX_ATTEMPTS, getShortId(), e.getMessage());
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e; if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
@ -909,12 +912,44 @@ public abstract class Trade implements Tradable, Model {
log.info("Importing multisig hexes for {} {}, count={}", getClass().getSimpleName(), getShortId(), multisigHexes.size()); log.info("Importing multisig hexes for {} {}, count={}", getClass().getSimpleName(), getShortId(), multisigHexes.size());
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
if (!multisigHexes.isEmpty()) { if (!multisigHexes.isEmpty()) {
try {
wallet.importMultisigHex(multisigHexes.toArray(new String[0])); wallet.importMultisigHex(multisigHexes.toArray(new String[0]));
} catch (MoneroError e) {
// import multisig hex individually if one is invalid
if (isInvalidImportError(e.getMessage())) {
log.warn("Peer has invalid multisig hex for {} {}, importing individually", getClass().getSimpleName(), getShortId());
boolean imported = false;
Exception lastError = null;
for (TradePeer peer : getOtherPeers()) {
if (peer.getUpdatedMultisigHex() == null) continue;
try {
wallet.importMultisigHex(peer.getUpdatedMultisigHex());
imported = true;
} catch (MoneroError e2) {
lastError = e2;
if (isInvalidImportError(e2.getMessage())) {
log.warn("{} has invalid multisig hex for {} {}, error={}, multisigHex={}", getPeerRole(peer), getClass().getSimpleName(), getShortId(), e2.getMessage(), peer.getUpdatedMultisigHex());
} else {
throw e2;
}
}
}
if (!imported) throw new IllegalArgumentException("Could not import any multisig hexes for " + getClass().getSimpleName() + " " + getShortId(), lastError);
} else {
throw e;
}
}
requestSaveWallet(); requestSaveWallet();
} }
log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size()); log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size());
} }
// TODO: checking error strings isn't robust, but the library doesn't provide a way to check if multisig hex is invalid. throw IllegalArgumentException from library on invalid multisig hex?
private boolean isInvalidImportError(String errMsg) {
return errMsg.contains("Failed to parse hex") || errMsg.contains("Multisig info is for a different account");
}
public void changeWalletPassword(String oldPassword, String newPassword) { public void changeWalletPassword(String oldPassword, String newPassword) {
synchronized (walletLock) { synchronized (walletLock) {
getWallet().changePassword(oldPassword, newPassword); getWallet().changePassword(oldPassword, newPassword);
@ -1228,10 +1263,14 @@ public abstract class Trade implements Tradable, Model {
if (sign) { if (sign) {
// sign tx // sign tx
try {
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex); MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null"); if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
payoutTxHex = result.getSignedMultisigTxHex(); payoutTxHex = result.getSignedMultisigTxHex();
setPayoutTxHex(payoutTxHex); setPayoutTxHex(payoutTxHex);
} catch (Exception e) {
throw new IllegalStateException(e);
}
// describe result // describe result
describedTxSet = wallet.describeMultisigTxSet(payoutTxHex); describedTxSet = wallet.describeMultisigTxSet(payoutTxHex);
@ -1239,6 +1278,7 @@ public abstract class Trade implements Tradable, Model {
// verify fee is within tolerance by recreating payout tx // 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? // 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());
MoneroTxWallet feeEstimateTx = createPayoutTx(); MoneroTxWallet feeEstimateTx = createPayoutTx();
BigInteger feeEstimate = feeEstimateTx.getFee(); BigInteger feeEstimate = feeEstimateTx.getFee();
double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal? double feeDiff = payoutTx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
@ -2175,7 +2215,7 @@ public abstract class Trade implements Tradable, Model {
return tradeAmountTransferred(); return tradeAmountTransferred();
} }
private boolean tradeAmountTransferred() { public boolean tradeAmountTransferred() {
return isPaymentReceived() || (getDisputeResult() != null && getDisputeResult().getWinner() == DisputeResult.Winner.SELLER); return isPaymentReceived() || (getDisputeResult() != null && getDisputeResult().getWinner() == DisputeResult.Winner.SELLER);
} }

View File

@ -265,11 +265,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
cssThemeProperty.set(prefPayload.getCssTheme()); cssThemeProperty.set(prefPayload.getCssTheme());
// if no valid Bitcoin block explorer is set, select the 1st valid Bitcoin block explorer // if no valid Monero block explorer is set, select the 1st valid Monero block explorer
ArrayList<BlockChainExplorer> btcExplorers = getBlockChainExplorers(); ArrayList<BlockChainExplorer> xmrExplorers = getBlockChainExplorers();
if (getBlockChainExplorer() == null || if (getBlockChainExplorer() == null ||
getBlockChainExplorer().name.length() == 0) { getBlockChainExplorer().name.length() == 0) {
setBlockChainExplorer(btcExplorers.get(0)); setBlockChainExplorer(xmrExplorers.get(0));
} }
tradeCurrenciesAsObservable.addAll(prefPayload.getTraditionalCurrencies()); tradeCurrenciesAsObservable.addAll(prefPayload.getTraditionalCurrencies());
tradeCurrenciesAsObservable.addAll(prefPayload.getCryptoCurrencies()); tradeCurrenciesAsObservable.addAll(prefPayload.getCryptoCurrencies());
@ -281,8 +281,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) { if (xmrNodesFromOptions != null && !xmrNodesFromOptions.isEmpty()) {
if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) { if (getMoneroNodes() != null && !getMoneroNodes().equals(xmrNodesFromOptions)) {
log.warn("The Bitcoin node(s) from the program argument and the one(s) persisted in the UI are different. " + log.warn("The Monero node(s) from the program argument and the one(s) persisted in the UI are different. " +
"The Bitcoin node(s) {} from the program argument will be used.", xmrNodesFromOptions); "The Monero node(s) {} from the program argument will be used.", xmrNodesFromOptions);
} }
setMoneroNodes(xmrNodesFromOptions); setMoneroNodes(xmrNodesFromOptions);
setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.CUSTOM.ordinal()); setMoneroNodesOptionOrdinal(XmrNodes.MoneroNodesOption.CUSTOM.ordinal());
@ -574,8 +574,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence(); requestPersistence();
} }
public void setMoneroNodes(String bitcoinNodes) { public void setMoneroNodes(String moneroNodes) {
prefPayload.setMoneroNodes(bitcoinNodes); prefPayload.setMoneroNodes(moneroNodes);
requestPersistence(); requestPersistence();
} }
@ -665,8 +665,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence(); requestPersistence();
} }
public void setMoneroNodesOptionOrdinal(int bitcoinNodesOptionOrdinal) { public void setMoneroNodesOptionOrdinal(int moneroNodesOptionOrdinal) {
prefPayload.setMoneroNodesOptionOrdinal(bitcoinNodesOptionOrdinal); prefPayload.setMoneroNodesOptionOrdinal(moneroNodesOptionOrdinal);
requestPersistence(); requestPersistence();
} }
@ -904,7 +904,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setSortMarketCurrenciesNumerically(boolean sortMarketCurrenciesNumerically); void setSortMarketCurrenciesNumerically(boolean sortMarketCurrenciesNumerically);
void setMoneroNodes(String bitcoinNodes); void setMoneroNodes(String moneroNodes);
void setUseCustomWithdrawalTxFee(boolean useCustomWithdrawalTxFee); void setUseCustomWithdrawalTxFee(boolean useCustomWithdrawalTxFee);
@ -938,7 +938,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setUseTorForXmrOrdinal(int useTorForXmrOrdinal); void setUseTorForXmrOrdinal(int useTorForXmrOrdinal);
void setMoneroNodesOptionOrdinal(int bitcoinNodesOption); void setMoneroNodesOptionOrdinal(int moneroNodesOption);
void setReferralId(String referralId); void setReferralId(String referralId);

View File

@ -113,7 +113,7 @@ public class XmrWalletService {
// monero configuration // monero configuration
public static final int NUM_BLOCKS_UNLOCK = 10; public static final int NUM_BLOCKS_UNLOCK = 10;
public static final String MONERO_BINS_DIR = Config.baseCurrencyNetwork().isTestnet() ? System.getProperty("user.dir") + File.separator + ".localnet" : Config.appDataDir().getAbsolutePath(); // .localnet contains monero binaries and wallet files public static final String MONERO_BINS_DIR = Config.appDataDir().getAbsolutePath();
public static final String MONERO_WALLET_RPC_NAME = Utilities.isWindows() ? "monero-wallet-rpc.exe" : "monero-wallet-rpc"; public static final String MONERO_WALLET_RPC_NAME = Utilities.isWindows() ? "monero-wallet-rpc.exe" : "monero-wallet-rpc";
public static final String MONERO_WALLET_RPC_PATH = MONERO_BINS_DIR + File.separator + MONERO_WALLET_RPC_NAME; public static final String MONERO_WALLET_RPC_PATH = MONERO_BINS_DIR + File.separator + MONERO_WALLET_RPC_NAME;
public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee public static final double MINER_FEE_TOLERANCE = 0.25; // miner fee must be within percent of estimated fee
@ -322,7 +322,7 @@ public class XmrWalletService {
} }
public boolean isProxyApplied(boolean wasWalletSynced) { public boolean isProxyApplied(boolean wasWalletSynced) {
return preferences.isProxyApplied(wasWalletSynced); return preferences.isProxyApplied(wasWalletSynced) && xmrConnectionService.isProxyApplied();
} }
public String getWalletPassword() { public String getWalletPassword() {
@ -1545,6 +1545,7 @@ public class XmrWalletService {
// open wallet // open wallet
config.setNetworkType(getMoneroNetworkType()); config.setNetworkType(getMoneroNetworkType());
config.setServer(connection); config.setServer(connection);
log.info("Opening full wallet " + config.getPath() + " with monerod=" + connection.getUri());
walletFull = MoneroWalletFull.openWallet(config); walletFull = MoneroWalletFull.openWallet(config);
if (walletFull.getDaemonConnection() != null) walletFull.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE); if (walletFull.getDaemonConnection() != null) walletFull.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);
log.info("Done opening full wallet " + config.getPath()); log.info("Done opening full wallet " + config.getPath());
@ -1604,7 +1605,7 @@ public class XmrWalletService {
if (!applyProxyUri) connection.setProxyUri(null); if (!applyProxyUri) connection.setProxyUri(null);
// open wallet // open wallet
log.info("Opening RPC wallet " + config.getPath() + " connected to daemon " + connection.getUri()); log.info("Opening RPC wallet " + config.getPath() + " with monerod=" + connection.getUri());
config.setServer(connection); config.setServer(connection);
walletRpc.openWallet(config); walletRpc.openWallet(config);
if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE); if (walletRpc.getDaemonConnection() != null) walletRpc.getDaemonConnection().setPrintStackTrace(PRINT_RPC_STACK_TRACE);

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1200,6 +1200,19 @@ textfield */
-fx-border-color: -bs-background-gray; -fx-border-color: -bs-background-gray;
} }
.text-area-no-border {
-fx-border-color: -fx-control-inner-background;
}
.text-area-no-border .content {
-fx-background-color: -fx-control-inner-background;
}
.text-area-no-border:focused {
-fx-focus-color: -fx-control-inner-background;
-fx-faint-focus-color: -fx-control-inner-background;
}
/******************************************************************************* /*******************************************************************************
* * * *
* Tab pane * * Tab pane *

View File

@ -19,6 +19,8 @@ package haveno.desktop.main.market.offerbook;
import com.google.common.math.LongMath; import com.google.common.math.LongMath;
import com.google.inject.Inject; import com.google.inject.Inject;
import haveno.common.UserThread;
import haveno.core.account.witness.AccountAgeWitnessService; import haveno.core.account.witness.AccountAgeWitnessService;
import haveno.core.locale.CurrencyUtil; import haveno.core.locale.CurrencyUtil;
import haveno.core.locale.GlobalSettings; import haveno.core.locale.GlobalSettings;
@ -135,10 +137,12 @@ class OfferBookChartViewModel extends ActivatableViewModel {
currenciesUpdatedListener = (observable, oldValue, newValue) -> { currenciesUpdatedListener = (observable, oldValue, newValue) -> {
if (!isAnyPriceAbsent()) { if (!isAnyPriceAbsent()) {
UserThread.execute(() -> {
offerBook.fillOfferBookListItems(); offerBook.fillOfferBookListItems();
updateChartData(); updateChartData();
var self = this; var self = this;
priceFeedService.updateCounterProperty().removeListener(self.currenciesUpdatedListener); priceFeedService.updateCounterProperty().removeListener(self.currenciesUpdatedListener);
});
} }
}; };

View File

@ -33,6 +33,7 @@ import haveno.desktop.components.AutoTooltipCheckBox;
import haveno.desktop.components.AutoTooltipLabel; import haveno.desktop.components.AutoTooltipLabel;
import haveno.desktop.components.BusyAnimation; import haveno.desktop.components.BusyAnimation;
import haveno.desktop.main.MainView; import haveno.desktop.main.MainView;
import haveno.desktop.util.CssTheme;
import haveno.desktop.util.FormBuilder; import haveno.desktop.util.FormBuilder;
import haveno.desktop.util.GUIUtil; import haveno.desktop.util.GUIUtil;
import haveno.desktop.util.Layout; import haveno.desktop.util.Layout;
@ -41,6 +42,8 @@ import javafx.animation.Interpolator;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.KeyValue; import javafx.animation.KeyValue;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
@ -56,6 +59,7 @@ import javafx.scene.control.Button;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.Hyperlink; import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy; import javafx.scene.control.ScrollPane.ScrollBarPolicy;
@ -163,7 +167,8 @@ public abstract class Overlay<T extends Overlay<T>> {
protected boolean useAnimation = true; protected boolean useAnimation = true;
protected boolean showScrollPane = false; protected boolean showScrollPane = false;
protected Label headlineIcon, copyIcon, headLineLabel, messageLabel; protected TextArea messageTextArea;
protected Label headlineIcon, copyIcon, headLineLabel;
protected String headLine, message, closeButtonText, actionButtonText, protected String headLine, message, closeButtonText, actionButtonText,
secondaryActionButtonText, dontShowAgainId, dontShowAgainText, secondaryActionButtonText, dontShowAgainId, dontShowAgainText,
truncatedMessage; truncatedMessage;
@ -847,20 +852,37 @@ public abstract class Overlay<T extends Overlay<T>> {
protected void addMessage() { protected void addMessage() {
if (message != null) { if (message != null) {
messageLabel = new AutoTooltipLabel(truncatedMessage); messageTextArea = new TextArea(truncatedMessage);
messageLabel.setMouseTransparent(true); messageTextArea.setEditable(false);
messageLabel.setWrapText(true); messageTextArea.getStyleClass().add("text-area-no-border");
messageTextArea.sceneProperty().addListener((o, oldScene, newScene) -> {
if (newScene != null) {
// avoid javafx css warning
CssTheme.loadSceneStyles(newScene, CssTheme.CSS_THEME_LIGHT, false);
messageTextArea.applyCss();
var text = messageTextArea.lookup(".text");
messageTextArea.prefHeightProperty().bind(Bindings.createDoubleBinding(() -> {
return messageTextArea.getFont().getSize() + text.getBoundsInLocal().getHeight();
}, text.boundsInLocalProperty()));
text.boundsInLocalProperty().addListener((observableBoundsAfter, boundsBefore, boundsAfter) -> {
Platform.runLater(() -> messageTextArea.requestLayout());
});
}
});
messageTextArea.setWrapText(true);
Region messageRegion; Region messageRegion;
if (showScrollPane) { if (showScrollPane) {
scrollPane = new ScrollPane(messageLabel); scrollPane = new ScrollPane(messageTextArea);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER); scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED); scrollPane.setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
scrollPane.setFitToWidth(true); scrollPane.setFitToWidth(true);
messageRegion = scrollPane; messageRegion = scrollPane;
} else } else
messageRegion = messageLabel; messageRegion = messageTextArea;
GridPane.setHalignment(messageRegion, HPos.LEFT); GridPane.setHalignment(messageRegion, HPos.LEFT);
GridPane.setHgrow(messageRegion, Priority.ALWAYS); GridPane.setHgrow(messageRegion, Priority.ALWAYS);
@ -892,7 +914,7 @@ public abstract class Overlay<T extends Overlay<T>> {
} }
private void addReportErrorButtons() { private void addReportErrorButtons() {
messageLabel.setText(Res.get("popup.reportError", truncatedMessage)); messageTextArea.setText(Res.get("popup.reportError", truncatedMessage));
Button logButton = new AutoTooltipButton(Res.get("popup.reportError.log")); Button logButton = new AutoTooltipButton(Res.get("popup.reportError.log"));
GridPane.setMargin(logButton, new Insets(20, 0, 0, 0)); GridPane.setMargin(logButton, new Insets(20, 0, 0, 0));

View File

@ -26,7 +26,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static haveno.desktop.util.FormBuilder.addMultilineLabel;
public class DisplayAlertMessageWindow extends Overlay<DisplayAlertMessageWindow> { public class DisplayAlertMessageWindow extends Overlay<DisplayAlertMessageWindow> {
private static final Logger log = LoggerFactory.getLogger(DisplayAlertMessageWindow.class); private static final Logger log = LoggerFactory.getLogger(DisplayAlertMessageWindow.class);
@ -41,6 +40,7 @@ public class DisplayAlertMessageWindow extends Overlay<DisplayAlertMessageWindow
type = Type.Attention; type = Type.Attention;
} }
@Override
public void show() { public void show() {
width = 768; width = 768;
// need to set headLine, otherwise the fields will not be created in addHeadLine // need to set headLine, otherwise the fields will not be created in addHeadLine
@ -75,7 +75,8 @@ public class DisplayAlertMessageWindow extends Overlay<DisplayAlertMessageWindow
private void addContent() { private void addContent() {
checkNotNull(alert, "alertMessage must not be null"); checkNotNull(alert, "alertMessage must not be null");
addMultilineLabel(gridPane, ++rowIndex, alert.getMessage(), 10); message(alert.getMessage());
addMessage();
if (alert.isSoftwareUpdateNotification()) { if (alert.isSoftwareUpdateNotification()) {
String url = "https://haveno.exchange/downloads"; String url = "https://haveno.exchange/downloads";
HyperlinkWithIcon hyperlinkWithIcon = FormBuilder.addLabelHyperlinkWithIcon(gridPane, ++rowIndex, HyperlinkWithIcon hyperlinkWithIcon = FormBuilder.addLabelHyperlinkWithIcon(gridPane, ++rowIndex,

View File

@ -301,7 +301,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
tooltip = new Tooltip(CountryUtil.getNamesByCodesString(acceptedCountryCodes)); tooltip = new Tooltip(CountryUtil.getNamesByCodesString(acceptedCountryCodes));
} }
} }
Label acceptedCountries = addConfirmationLabelLabel(gridPane, ++rowIndex, Label acceptedCountries = addConfirmationLabelLabel(gridPane, true, ++rowIndex,
Res.get("shared.acceptedTakerCountries"), countries).second; Res.get("shared.acceptedTakerCountries"), countries).second;
if (tooltip != null) { if (tooltip != null) {
acceptedCountries.setMouseTransparent(false); acceptedCountries.setMouseTransparent(false);

View File

@ -89,7 +89,7 @@ public class TacWindow extends Overlay<TacWindow> {
protected void addMessage() { protected void addMessage() {
super.addMessage(); super.addMessage();
String fontStyleClass = smallScreen ? "small-text" : "normal-text"; String fontStyleClass = smallScreen ? "small-text" : "normal-text";
messageLabel.getStyleClass().add(fontStyleClass); messageTextArea.getStyleClass().add(fontStyleClass);
// TODO: link to the wiki // TODO: link to the wiki
// HyperlinkWithIcon hyperlinkWithIcon = addHyperlinkWithIcon(gridPane, ++rowIndex, Res.get("tacWindow.arbitrationSystem"), // HyperlinkWithIcon hyperlinkWithIcon = addHyperlinkWithIcon(gridPane, ++rowIndex, Res.get("tacWindow.arbitrationSystem"),

View File

@ -102,7 +102,6 @@ class EditOfferViewModel extends MutableOfferViewModel<EditOfferDataModel> {
} }
public void onInvalidateMarketPriceMarginPct() { public void onInvalidateMarketPriceMarginPct() {
marketPriceMargin.set("0.00%");
marketPriceMargin.set(FormattingUtils.formatToPercent(dataModel.getMarketPriceMarginPct())); marketPriceMargin.set(FormattingUtils.formatToPercent(dataModel.getMarketPriceMarginPct()));
} }

View File

@ -57,6 +57,7 @@ import haveno.desktop.components.TxIdTextField;
import javafx.geometry.HPos; import javafx.geometry.HPos;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
@ -379,15 +380,30 @@ public class FormBuilder {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Confirmation Fields // Confirmation Fields
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public static Tuple2<Label, Label> addConfirmationLabelLabel(GridPane gridPane,
int rowIndex,
String title1,
String title2,
double top) {
return addConfirmationLabelLabel(gridPane, false, rowIndex, title1, title2, top);
}
public static Tuple2<Label, Label> addConfirmationLabelLabel(GridPane gridPane, public static Tuple2<Label, Label> addConfirmationLabelLabel(GridPane gridPane,
int rowIndex, int rowIndex,
String title1, String title1,
String title2) { String title2) {
return addConfirmationLabelLabel(gridPane, rowIndex, title1, title2, 0); return addConfirmationLabelLabel(gridPane, false, rowIndex, title1, title2, 0);
} }
public static Tuple2<Label, Label> addConfirmationLabelLabel(GridPane gridPane, public static Tuple2<Label, Label> addConfirmationLabelLabel(GridPane gridPane,
boolean isWrapped,
int rowIndex,
String title1,
String title2) {
return addConfirmationLabelLabel(gridPane, isWrapped, rowIndex, title1, title2, 0);
}
public static Tuple2<Label, Label> addConfirmationLabelLabel(GridPane gridPane,
boolean isWrapped,
int rowIndex, int rowIndex,
String title1, String title1,
String title2, String title2,
@ -396,10 +412,14 @@ public class FormBuilder {
label1.getStyleClass().add("confirmation-label"); label1.getStyleClass().add("confirmation-label");
Label label2 = addLabel(gridPane, rowIndex, title2); Label label2 = addLabel(gridPane, rowIndex, title2);
label2.getStyleClass().add("confirmation-value"); label2.getStyleClass().add("confirmation-value");
label2.setWrapText(isWrapped);
GridPane.setColumnIndex(label2, 1); GridPane.setColumnIndex(label2, 1);
GridPane.setMargin(label1, new Insets(top, 0, 0, 0)); GridPane.setMargin(label1, new Insets(top, 0, 0, 0));
GridPane.setHalignment(label1, HPos.LEFT); GridPane.setHalignment(label1, HPos.LEFT);
GridPane.setValignment(label1, VPos.TOP);
GridPane.setMargin(label2, new Insets(top, 0, 0, 0)); GridPane.setMargin(label2, new Insets(top, 0, 0, 0));
GridPane.setHalignment(label2, HPos.LEFT);
GridPane.setValignment(label2, VPos.TOP);
return new Tuple2<>(label1, label2); return new Tuple2<>(label1, label2);
} }
@ -451,12 +471,21 @@ public class FormBuilder {
String title1, String title1,
String title2, String title2,
double top) { double top) {
return addConfirmationLabelTextArea(gridPane, false, rowIndex, title1, title2, top);
}
public static Tuple2<Label, TextArea> addConfirmationLabelTextArea(GridPane gridPane,
boolean isWrapped,
int rowIndex,
String title1,
String title2,
double top) {
Label label = addLabel(gridPane, rowIndex, title1); Label label = addLabel(gridPane, rowIndex, title1);
label.getStyleClass().add("confirmation-label"); label.getStyleClass().add("confirmation-label");
TextArea textArea = addTextArea(gridPane, rowIndex, title2); TextArea textArea = addTextArea(gridPane, rowIndex, title2);
((JFXTextArea) textArea).setLabelFloat(false); ((JFXTextArea) textArea).setLabelFloat(false);
textArea.setWrapText(isWrapped);
GridPane.setColumnIndex(textArea, 1); GridPane.setColumnIndex(textArea, 1);
GridPane.setMargin(label, new Insets(top, 0, 0, 0)); GridPane.setMargin(label, new Insets(top, 0, 0, 0));
GridPane.setHalignment(label, HPos.LEFT); GridPane.setHalignment(label, HPos.LEFT);
@ -466,6 +495,7 @@ public class FormBuilder {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Label + TextFieldWithIcon // Label + TextFieldWithIcon
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -48,6 +48,11 @@ Follow [instructions](https://github.com/haveno-dex/haveno-ts#run-tests) to run
Based on these instructions: https://github.com/monero-project/monero#cross-compiling Based on these instructions: https://github.com/monero-project/monero#cross-compiling
> Note:
> If during building you get the prompt "Reversed (or previously applied) patch detected! Assume -R? [n]" then confirm 'y'.
**Prepare Linux x86_64**
1. Install Ubuntu 20.04 on x86_64. 1. Install Ubuntu 20.04 on x86_64.
2. `sudo apt-get update && sudo apt-get upgrade` 2. `sudo apt-get update && sudo apt-get upgrade`
3. Install monero dependencies: `sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache` 3. Install monero dependencies: `sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache`
@ -57,15 +62,12 @@ Based on these instructions: https://github.com/monero-project/monero#cross-comp
7. `git fetch origin && git reset --hard origin/release-v0.18` 7. `git fetch origin && git reset --hard origin/release-v0.18`
8. `git submodule update --init --force` 8. `git submodule update --init --force`
> Note: **Build for Linux x86_64**
> If you get the prompt "Reversed (or previously applied) patch detected! Assume -R? [n]" then confirm 'y'.
**Build for Linux**
1. `make depends target=x86_64-linux-gnu -j<num cores>` 1. `make depends target=x86_64-linux-gnu -j<num cores>`
2. `cd build/x86_64-linux-gnu/release/bin/` 2. `cd build/x86_64-linux-gnu/release/bin/`
3. `tar -zcvf monero-bins-haveno-linux.tar.gz monerod monero-wallet-rpc` 3. `tar -zcvf monero-bins-haveno-linux-x86_64.tar.gz monerod monero-wallet-rpc`
4. Save monero-bins-haveno-linux.tar.gz for release. 4. Save monero-bins-haveno-linux-x86_64.tar.gz for release.
**Build for Mac** **Build for Mac**
@ -83,6 +85,24 @@ Based on these instructions: https://github.com/monero-project/monero#cross-comp
5. `zip monero-bins-haveno-windows.zip monerod.exe monero-wallet-rpc.exe` 5. `zip monero-bins-haveno-windows.zip monerod.exe monero-wallet-rpc.exe`
6. Save monero-bins-haveno-windows.zip for release. 6. Save monero-bins-haveno-windows.zip for release.
**Prepare Linux aarch64**
1. Install Ubuntu 20.04 on aarch64.
2. `sudo apt-get update && sudo apt-get upgrade`
3. Install monero dependencies: `sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache`
4. `sudo apt install cmake imagemagick libcap-dev librsvg2-bin libz-dev libbz2-dev libtiff-tools python-dev libtinfo5 autoconf libtool libtool-bin gperf git curl`
5. `git clone https://github.com/haveno-dex/monero.git`
6. `cd ./monero` (or rename to haveno-monero: `mv monero/ haveno-monero && cd ./haveno-monero`)
7. `git fetch origin && git reset --hard origin/release-v0.18`
8. `git submodule update --init --force`
**Build for Linux aarch64**
1. `make depends target=aarch64-linux-gnu -j<num cores>`
2. `cd build/aarch64-linux-gnu/release/bin/`
3. `tar -zcvf monero-bins-haveno-linux-aarch64.tar.gz monerod monero-wallet-rpc`
4. Save monero-bins-haveno-linux-aarch64.tar.gz for release.
## Build executable installers for each platform ## Build executable installers for each platform
See [instructions](/desktop/package/README.md). See [instructions](/desktop/package/README.md).

View File

@ -886,9 +886,9 @@
<sha256 value="c92e2ca40a3f2474d61e56831aeb379cf8ae3dddeea61b4a828cee2d99f71f38" origin="Generated by Gradle"/> <sha256 value="c92e2ca40a3f2474d61e56831aeb379cf8ae3dddeea61b4a828cee2d99f71f38" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="io.github.woodser" name="monero-java" version="0.8.29"> <component group="io.github.woodser" name="monero-java" version="0.8.31">
<artifact name="monero-java-0.8.29.jar"> <artifact name="monero-java-0.8.31.jar">
<sha256 value="41eba8c84a1b1c99f209628fdc8302b493b629cedadc790774cb215da2f08b4d" origin="Generated by Gradle"/> <sha256 value="46b81b98bc76f60a965bc7de429ff72cf6c443858987cbd51b9cacd2f8a8d28b" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="io.grpc" name="grpc-api" version="1.42.1"> <component group="io.grpc" name="grpc-api" version="1.42.1">

View File

@ -0,0 +1,11 @@
# Steps to use (This has serious security concerns to tails threat model only run when you need to access haveno)
## 1. Enable persistent storage and admin password before starting tails
## 2. Get your haveno deb file in persistent storage, currently most people use haveno-reto (amd64 version for tails)
## 3. Edit the path to the haveno deb file if necessary then run ```sudo ./haveno-install.sh```
## 4. As amnesia run ```source ~/.bashrc```
## 5. Start haveno using ```haveno-tails```
## You will need to run this script after each reset, but your data will be saved persistently in /home/amnesia/Persistence/Haveno-reto

View File

@ -0,0 +1,77 @@
#!/bin/bash
#############################################################################
# Written by BrandyJson, with heavy inspiration from bisq.wiki tails script #
#############################################################################
echo "Installing dpkg from persistent, (1.07-1, if this is out of date change the deb path in the script or manually install after running"
dpkg -i "/home/amnesia/Persistent/haveno_1.0.7-1_amd64.deb"
echo -e "Allowing amnesia to read tor control port cookie, only run this script when you actually want to use haveno\n\n!!! not secure !!!\n"
chmod o+r /var/run/tor/control.authcookie
echo "Updating apparmor-profile"
echo "---
- apparmor-profiles:
- '/opt/haveno/bin/Haveno'
users:
- 'amnesia'
commands:
AUTHCHALLENGE:
- 'SAFECOOKIE .*'
SETEVENTS:
- 'CIRC ORCONN INFO NOTICE WARN ERR HS_DESC HS_DESC_CONTENT'
GETINFO:
- pattern: 'status/bootstrap-phase'
response:
- pattern: '250-status/bootstrap-phase=*'
replacement: '250-status/bootstrap-phase=NOTICE BOOTSTRAP PROGRESS=100 TAG=done SUMMARY="Done"'
- 'net/listeners/socks'
ADD_ONION:
- pattern: 'NEW:(\S+) Port=9999,(\S+)'
replacement: 'NEW:{} Port=9999,{client-address}:{}'
- pattern: '(\S+):(\S+) Port=9999,(\S+)'
replacement: '{}:{} Port=9999,{client-address}:{}'
DEL_ONION:
- '.+'
HSFETCH:
- '.+'
events:
CIRC:
suppress: true
ORCONN:
suppress: true
INFO:
suppress: true
NOTICE:
suppress: true
WARN:
suppress: true
ERR:
suppress: true
HS_DESC:
response:
- pattern: '650 HS_DESC CREATED (\S+) (\S+) (\S+) \S+ (.+)'
replacement: '650 HS_DESC CREATED {} {} {} redacted {}'
- pattern: '650 HS_DESC UPLOAD (\S+) (\S+) .*'
replacement: '650 HS_DESC UPLOAD {} {} redacted redacted'
- pattern: '650 HS_DESC UPLOADED (\S+) (\S+) .+'
replacement: '650 HS_DESC UPLOADED {} {} redacted'
- pattern: '650 HS_DESC REQUESTED (\S+) NO_AUTH'
replacement: '650 HS_DESC REQUESTED {} NO_AUTH'
- pattern: '650 HS_DESC REQUESTED (\S+) NO_AUTH \S+ \S+'
replacement: '650 HS_DESC REQUESTED {} NO_AUTH redacted redacted'
- pattern: '650 HS_DESC RECEIVED (\S+) NO_AUTH \S+ \S+'
replacement: '650 HS_DESC RECEIVED {} NO_AUTH redacted redacted'
- pattern: '.*'
replacement: ''
HS_DESC_CONTENT:
suppress: true" > /etc/onion-grater.d/haveno.yml
echo "Adding rule to iptables to allow for monero-wallet-rpc to work"
iptables -I OUTPUT 2 -p tcp -d 127.0.0.1 -m tcp --dport 18081 -m owner --uid-owner 1855 -j ACCEPT
echo "Updating torsocks to allow for inbound connection"
sed -i 's/#AllowInbound/AllowInbound/g' /etc/tor/torsocks.conf
echo "Restarting onion-grater service"
systemctl restart onion-grater.service
echo "alias haveno-tails='torsocks /opt/haveno/bin/Haveno --torControlPort 951 --torControlCookieFile=/var/run/tor/control.authcookie --torControlUseSafeCookieAuth --useTorForXmr=ON --userDataDir=/home/amnesia/Persistent/'" >> /home/amnesia/.bashrc
echo -e "Everything is set up just run\n\nsource ~/.bashrc\n\nThen you can start haveno using haveno-tails"

View File

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