diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2b422a09..3d510e26 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ jobs:
build:
strategy:
matrix:
- os: [ubuntu-latest, macos-13, windows-latest]
+ os: [ubuntu-22.04, macos-13, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
@@ -37,7 +37,7 @@ jobs:
name: cached-localnet
path: .localnet
- name: Install dependencies
- if: ${{ matrix.os == 'ubuntu-latest' }}
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
run: |
sudo apt update
sudo apt install -y rpm fuse
@@ -52,27 +52,77 @@ jobs:
./gradlew clean build --refresh-keys --refresh-dependencies
./gradlew packageInstallers
working-directory: .
+
+ # get version from jar
+ - name: Set Version Unix
+ if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-13' }}
+ run: |
+ export VERSION=$(ls desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 | grep -Eo 'desktop-[0-9]+\.[0-9]+\.[0-9]+' | sed 's/desktop-//')
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
+ - name: Set Version Windows
+ if: ${{ matrix.os == 'windows-latest' }}
+ run: |
+ $VERSION = (Get-ChildItem -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256).Name -replace 'desktop-', '' -replace '-.*', ''
+ "VERSION=$VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
+ shell: powershell
+
- name: Move Release Files on Unix
- if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-13' }}
+ if: ${{ matrix.os == 'ubuntu-22.04' || matrix.os == 'macos-13' }}
run: |
mkdir ${{ github.workspace }}/release
- if [ "${{ matrix.os }}" == "ubuntu-latest" ]; then
- mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release
- mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release
- mv desktop/build/temp-*/binaries/haveno_*.AppImage ${{ github.workspace }}/release
+ if [ "${{ matrix.os }}" == "ubuntu-22.04" ]; then
+ mkdir ${{ github.workspace }}/release-rpm
+ mkdir ${{ github.workspace }}/release-deb
+ mkdir ${{ github.workspace }}/release-appimage
+ mv desktop/build/temp-*/binaries/haveno-*.rpm ${{ github.workspace }}/release-rpm/Haveno-${{ env.VERSION }}-x86_64.rpm
+ mv desktop/build/temp-*/binaries/haveno_*.deb ${{ github.workspace }}/release-deb/Haveno-${{ env.VERSION }}-x86_64.deb
+ mv desktop/build/temp-*/binaries/haveno_*.AppImage ${{ github.workspace }}/release-appimage/Haveno-${{ env.VERSION }}-x86_64.AppImage
else
- mv desktop/build/temp-*/binaries/Haveno-*.dmg ${{ github.workspace }}/release
+ mv desktop/build/temp-*/binaries/Haveno-*.dmg ${{ github.workspace }}/release/Haveno-${{ env.VERSION }}.dmg
fi
- mv desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release
+ cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release
+ cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-deb
+ cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-rpm
+ cp desktop/build/temp-*/binaries/desktop-*.jar.SHA-256 ${{ github.workspace }}/release-appimage
shell: bash
- name: Move Release Files on Windows
if: ${{ matrix.os == 'windows-latest' }}
run: |
mkdir ${{ github.workspace }}/release
- Move-Item -Path desktop\build\temp-*/binaries\Haveno-*.exe -Destination ${{ github.workspace }}/release
+ Move-Item -Path desktop\build\temp-*/binaries\Haveno-*.exe -Destination ${{ github.workspace }}/release/Haveno-${{ env.VERSION }}.exe
Move-Item -Path desktop\build\temp-*/binaries\desktop-*.jar.SHA-256 -Destination ${{ github.workspace }}/release
shell: powershell
+
+ # win
- uses: actions/upload-artifact@v3
+ name: "Windows artifacts"
+ if: ${{ matrix.os == 'windows-latest'}}
with:
- name: HavenoInstaller-${{ matrix.os }}
+ name: haveno-windows
path: ${{ github.workspace }}/release
+ # macos
+ - uses: actions/upload-artifact@v3
+ name: "macOS artifacts"
+ if: ${{ matrix.os == 'macos-13' }}
+ with:
+ name: haveno-macos
+ path: ${{ github.workspace }}/release
+ # linux
+ - uses: actions/upload-artifact@v3
+ name: "Linux - deb artifact"
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
+ with:
+ name: haveno-linux-deb
+ path: ${{ github.workspace }}/release-deb
+ - uses: actions/upload-artifact@v3
+ name: "Linux - rpm artifact"
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
+ with:
+ name: haveno-linux-rpm
+ path: ${{ github.workspace }}/release-rpm
+ - uses: actions/upload-artifact@v3
+ name: "Linux - AppImage artifact"
+ if: ${{ matrix.os == 'ubuntu-22.04' }}
+ with:
+ name: haveno-linux-appimage
+ path: ${{ github.workspace }}/release-appimg
\ No newline at end of file
diff --git a/README.md b/README.md
index 465e04f9..7f4d1eb2 100644
--- a/README.md
+++ b/README.md
@@ -72,7 +72,7 @@ To bring Haveno to life, we need resources. If you have the possibility, please
### Monero
- 
+ 
42sjokkT9FmiWPqVzrWPFE5NCJXwt96bkBozHf4vgLR9hXyJDqKHEHKVscAARuD7in5wV1meEcSTJTanCTDzidTe2cFXS1F
@@ -81,6 +81,6 @@ If you are using a wallet that supports OpenAlias (like the 'official' CLI and G
### Bitcoin
- 
+ 
1AKq3CE1yBAnxGmHXbNFfNYStcByNDc5gQ
diff --git a/apitest/src/main/java/haveno/apitest/ApiTestMain.java b/apitest/src/main/java/haveno/apitest/ApiTestMain.java
index 4da4b920..ad383ff1 100644
--- a/apitest/src/main/java/haveno/apitest/ApiTestMain.java
+++ b/apitest/src/main/java/haveno/apitest/ApiTestMain.java
@@ -78,7 +78,7 @@ public class ApiTestMain {
} catch (Throwable ex) {
err.println("Fault: An unexpected error occurred. " +
- "Please file a report at https://haveno.exchange/issues");
+ "Please file a report at https://github.com/haveno-dex/haveno/issues");
ex.printStackTrace(err);
exit(EXIT_FAILURE);
}
diff --git a/build.gradle b/build.gradle
index 83d42efb..bcd7d082 100644
--- a/build.gradle
+++ b/build.gradle
@@ -49,7 +49,7 @@ configure(subprojects) {
gsonVersion = '2.8.5'
guavaVersion = '32.1.1-jre'
guiceVersion = '7.0.0'
- moneroJavaVersion = '0.8.31'
+ moneroJavaVersion = '0.8.33'
httpclient5Version = '5.0'
hamcrestVersion = '2.2'
httpclientVersion = '4.5.12'
@@ -334,6 +334,7 @@ configure(project(':p2p')) {
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
implementation "org.fxmisc.easybind:easybind:$easybindVersion"
implementation "org.slf4j:slf4j-api:$slf4jVersion"
+ implementation "org.apache.commons:commons-lang3:$langVersion"
implementation("com.github.haveno-dex.netlayer:tor.external:$netlayerVersion") {
exclude(module: 'slf4j-api')
}
diff --git a/common/src/main/java/haveno/common/app/Log.java b/common/src/main/java/haveno/common/app/Log.java
index 1922a519..67ca2ab9 100644
--- a/common/src/main/java/haveno/common/app/Log.java
+++ b/common/src/main/java/haveno/common/app/Log.java
@@ -21,6 +21,7 @@ import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.filter.ThresholdFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
@@ -52,11 +53,12 @@ public class Log {
SizeBasedTriggeringPolicy triggeringPolicy = new SizeBasedTriggeringPolicy<>();
triggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB"));
+ triggeringPolicy.setContext(loggerContext);
triggeringPolicy.start();
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setContext(loggerContext);
- encoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg %xEx%n");
+ encoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{15}: %msg%n");
encoder.start();
appender.setEncoder(encoder);
@@ -64,25 +66,43 @@ public class Log {
appender.setTriggeringPolicy(triggeringPolicy);
appender.start();
- logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
- logbackLogger.addAppender(appender);
- logbackLogger.setLevel(Level.INFO);
-
// log errors in separate file
- // not working as expected still.... damn logback...
- /* FileAppender errorAppender = new FileAppender();
- errorAppender.setEncoder(encoder);
+ PatternLayoutEncoder errorEncoder = new PatternLayoutEncoder();
+ errorEncoder.setContext(loggerContext);
+ errorEncoder.setPattern("%d{MMM-dd HH:mm:ss.SSS} [%thread] %-5level %logger: %msg%n%ex");
+ errorEncoder.start();
+
+ RollingFileAppender errorAppender = new RollingFileAppender<>();
+ errorAppender.setEncoder(errorEncoder);
errorAppender.setName("Error");
errorAppender.setContext(loggerContext);
errorAppender.setFile(fileName + "_error.log");
- LevelFilter levelFilter = new LevelFilter();
- levelFilter.setLevel(Level.ERROR);
- levelFilter.setOnMatch(FilterReply.ACCEPT);
- levelFilter.setOnMismatch(FilterReply.DENY);
- levelFilter.start();
- errorAppender.addFilter(levelFilter);
+
+ FixedWindowRollingPolicy errorRollingPolicy = new FixedWindowRollingPolicy();
+ errorRollingPolicy.setContext(loggerContext);
+ errorRollingPolicy.setParent(errorAppender);
+ errorRollingPolicy.setFileNamePattern(fileName + "_error_%i.log");
+ errorRollingPolicy.setMinIndex(1);
+ errorRollingPolicy.setMaxIndex(20);
+ errorRollingPolicy.start();
+
+ SizeBasedTriggeringPolicy errorTriggeringPolicy = new SizeBasedTriggeringPolicy<>();
+ errorTriggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB"));
+ errorTriggeringPolicy.start();
+
+ ThresholdFilter thresholdFilter = new ThresholdFilter();
+ thresholdFilter.setLevel("WARN");
+ thresholdFilter.start();
+
+ errorAppender.setRollingPolicy(errorRollingPolicy);
+ errorAppender.setTriggeringPolicy(errorTriggeringPolicy);
+ errorAppender.addFilter(thresholdFilter);
errorAppender.start();
- logbackLogger.addAppender(errorAppender);*/
+
+ logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+ logbackLogger.addAppender(errorAppender);
+ logbackLogger.addAppender(appender);
+ logbackLogger.setLevel(Level.INFO);
}
public static void setCustomLogLevel(String pattern, Level logLevel) {
diff --git a/common/src/main/java/haveno/common/file/FileUtil.java b/common/src/main/java/haveno/common/file/FileUtil.java
index 449faea6..27058f30 100644
--- a/common/src/main/java/haveno/common/file/FileUtil.java
+++ b/common/src/main/java/haveno/common/file/FileUtil.java
@@ -68,8 +68,7 @@ public class FileUtil {
pruneBackup(backupFileDir, numMaxBackupFiles);
} catch (IOException e) {
- log.error("Backup key failed: " + e.getMessage());
- e.printStackTrace();
+ log.error("Backup key failed: {}\n", e.getMessage(), e);
}
}
}
@@ -97,7 +96,7 @@ public class FileUtil {
try {
FileUtils.deleteDirectory(backupFileDir);
} catch (IOException e) {
- e.printStackTrace();
+ log.error("Delete backup key failed: {}\n", e.getMessage(), e);
}
}
@@ -173,8 +172,7 @@ public class FileUtil {
}
}
} catch (Throwable t) {
- log.error(t.toString());
- t.printStackTrace();
+ log.error("Could not delete file, error={}\n", t.getMessage(), t);
throw new IOException(t);
}
}
diff --git a/common/src/main/java/haveno/common/setup/CommonSetup.java b/common/src/main/java/haveno/common/setup/CommonSetup.java
index f606d3b5..0c929b3a 100644
--- a/common/src/main/java/haveno/common/setup/CommonSetup.java
+++ b/common/src/main/java/haveno/common/setup/CommonSetup.java
@@ -69,11 +69,7 @@ public class CommonSetup {
"The system tray is not supported on the current platform.".equals(throwable.getMessage())) {
log.warn(throwable.getMessage());
} else {
- log.error("Uncaught Exception from thread " + Thread.currentThread().getName());
- log.error("throwableMessage= " + throwable.getMessage());
- log.error("throwableClass= " + throwable.getClass());
- log.error("Stack trace:\n" + ExceptionUtils.getStackTrace(throwable));
- throwable.printStackTrace();
+ log.error("Uncaught Exception from thread {}, error={}\n", Thread.currentThread().getName(), throwable.getMessage(), throwable);
UserThread.execute(() -> uncaughtExceptionHandler.handleUncaughtException(throwable, false));
}
};
@@ -113,8 +109,7 @@ public class CommonSetup {
if (!pathOfCodeSource.endsWith("classes"))
log.info("Path to Haveno jar file: " + pathOfCodeSource);
} catch (URISyntaxException e) {
- log.error(e.toString());
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
}
}
}
diff --git a/common/src/main/java/haveno/common/taskrunner/TaskRunner.java b/common/src/main/java/haveno/common/taskrunner/TaskRunner.java
index e49b4ccd..087ffce7 100644
--- a/common/src/main/java/haveno/common/taskrunner/TaskRunner.java
+++ b/common/src/main/java/haveno/common/taskrunner/TaskRunner.java
@@ -25,6 +25,8 @@ import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
@Slf4j
public class TaskRunner {
private final Queue>> tasks = new LinkedBlockingQueue<>();
@@ -67,8 +69,8 @@ public class TaskRunner {
log.info("Run task: " + currentTask.getSimpleName());
currentTask.getDeclaredConstructor(TaskRunner.class, sharedModelClass).newInstance(this, sharedModel).run();
} catch (Throwable throwable) {
- throwable.printStackTrace();
- handleErrorMessage("Error at taskRunner: " + throwable.getMessage());
+ log.error(ExceptionUtils.getStackTrace(throwable));
+ handleErrorMessage("Error at taskRunner, error=" + throwable.getMessage());
}
} else {
resultHandler.handleResult();
diff --git a/common/src/main/java/haveno/common/util/Utilities.java b/common/src/main/java/haveno/common/util/Utilities.java
index b4afe417..240ea49e 100644
--- a/common/src/main/java/haveno/common/util/Utilities.java
+++ b/common/src/main/java/haveno/common/util/Utilities.java
@@ -331,8 +331,7 @@ public class Utilities {
clipboard.setContent(clipboardContent);
}
} catch (Throwable e) {
- log.error("copyToClipboard failed " + e.getMessage());
- e.printStackTrace();
+ log.error("copyToClipboard failed: {}\n", e.getMessage(), e);
}
}
diff --git a/core/src/main/java/haveno/core/api/CoreApi.java b/core/src/main/java/haveno/core/api/CoreApi.java
index 25f98c34..9afc4ee2 100644
--- a/core/src/main/java/haveno/core/api/CoreApi.java
+++ b/core/src/main/java/haveno/core/api/CoreApi.java
@@ -260,11 +260,11 @@ public class CoreApi {
}
public void startXmrNode(XmrNodeSettings settings) throws IOException {
- xmrLocalNode.startNode(settings);
+ xmrLocalNode.start(settings);
}
public void stopXmrNode() {
- xmrLocalNode.stopNode();
+ xmrLocalNode.stop();
}
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/haveno/core/api/CoreDisputesService.java b/core/src/main/java/haveno/core/api/CoreDisputesService.java
index 7edbed9b..f193287e 100644
--- a/core/src/main/java/haveno/core/api/CoreDisputesService.java
+++ b/core/src/main/java/haveno/core/api/CoreDisputesService.java
@@ -52,6 +52,9 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import lombok.extern.slf4j.Slf4j;
@@ -204,7 +207,7 @@ public class CoreDisputesService {
throw new IllegalStateException(errMessage, err);
});
} catch (Exception e) {
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
throw new IllegalStateException(e.getMessage() == null ? ("Error resolving dispute for trade " + trade.getId()) : e.getMessage());
}
}
diff --git a/core/src/main/java/haveno/core/api/CoreNotificationService.java b/core/src/main/java/haveno/core/api/CoreNotificationService.java
index 6c40d749..484208d0 100644
--- a/core/src/main/java/haveno/core/api/CoreNotificationService.java
+++ b/core/src/main/java/haveno/core/api/CoreNotificationService.java
@@ -3,7 +3,11 @@ package haveno.core.api;
import com.google.inject.Singleton;
import haveno.core.api.model.TradeInfo;
import haveno.core.support.messages.ChatMessage;
+import haveno.core.trade.HavenoUtils;
+import haveno.core.trade.MakerTrade;
+import haveno.core.trade.SellerTrade;
import haveno.core.trade.Trade;
+import haveno.core.trade.Trade.Phase;
import haveno.proto.grpc.NotificationMessage;
import haveno.proto.grpc.NotificationMessage.NotificationType;
import java.util.Iterator;
@@ -46,7 +50,15 @@ public class CoreNotificationService {
.build());
}
- public void sendTradeNotification(Trade trade, String title, String message) {
+ public void sendTradeNotification(Trade trade, Phase phase, String title, String message) {
+
+ // play chime when maker's trade is taken
+ if (trade instanceof MakerTrade && phase == Trade.Phase.DEPOSITS_PUBLISHED) HavenoUtils.playChimeSound();
+
+ // play chime when seller sees buyer confirm payment sent
+ if (trade instanceof SellerTrade && phase == Trade.Phase.PAYMENT_SENT) HavenoUtils.playChimeSound();
+
+ // send notification
sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.TRADE_UPDATE)
.setTrade(TradeInfo.toTradeInfo(trade).toProtoMessage())
@@ -57,6 +69,7 @@ public class CoreNotificationService {
}
public void sendChatNotification(ChatMessage chatMessage) {
+ HavenoUtils.playChimeSound();
sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.CHAT_MESSAGE)
.setTimestamp(System.currentTimeMillis())
diff --git a/core/src/main/java/haveno/core/api/CoreTradesService.java b/core/src/main/java/haveno/core/api/CoreTradesService.java
index 16fa22a8..431ab9a6 100644
--- a/core/src/main/java/haveno/core/api/CoreTradesService.java
+++ b/core/src/main/java/haveno/core/api/CoreTradesService.java
@@ -66,6 +66,8 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.bitcoinj.core.Coin;
@Singleton
@@ -161,7 +163,7 @@ class CoreTradesService {
errorMessageHandler
);
} catch (Exception e) {
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
errorMessageHandler.handleErrorMessage(e.getMessage());
}
}
diff --git a/core/src/main/java/haveno/core/api/XmrConnectionService.java b/core/src/main/java/haveno/core/api/XmrConnectionService.java
index 4465fd2a..ffeacbfd 100644
--- a/core/src/main/java/haveno/core/api/XmrConnectionService.java
+++ b/core/src/main/java/haveno/core/api/XmrConnectionService.java
@@ -41,6 +41,9 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
@@ -464,7 +467,7 @@ public final class XmrConnectionService {
log.info(getClass() + ".onAccountOpened() called");
initialize();
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Error initializing connection service after account opened, error={}\n", e.getMessage(), e);
throw new RuntimeException(e);
}
}
@@ -620,10 +623,9 @@ public final class XmrConnectionService {
if (connectionManager.getConnection() != null && xmrLocalNode.equalsUri(connectionManager.getConnection().getUri()) && !xmrLocalNode.isDetected() && !xmrLocalNode.shouldBeIgnored()) {
try {
log.info("Starting local node");
- xmrLocalNode.startMoneroNode();
+ xmrLocalNode.start();
} catch (Exception e) {
- log.warn("Unable to start local monero node: " + e.getMessage());
- e.printStackTrace();
+ log.error("Unable to start local monero node, error={}\n", e.getMessage(), e);
}
}
}
@@ -721,8 +723,8 @@ public final class XmrConnectionService {
// log error message periodically
if (lastLogPollErrorTimestamp == null || System.currentTimeMillis() - lastLogPollErrorTimestamp > HavenoUtils.LOG_POLL_ERROR_PERIOD_MS) {
- log.warn("Failed to fetch daemon info, trying to switch to best connection: " + e.getMessage());
- if (DevEnv.isDevMode()) e.printStackTrace();
+ log.warn("Failed to fetch daemon info, trying to switch to best connection, error={}", e.getMessage());
+ if (DevEnv.isDevMode()) log.error(ExceptionUtils.getStackTrace(e));
lastLogPollErrorTimestamp = System.currentTimeMillis();
}
@@ -734,6 +736,12 @@ public final class XmrConnectionService {
// connected to daemon
isConnected = true;
+ // determine if blockchain is syncing locally
+ boolean blockchainSyncing = lastInfo.getHeight().equals(lastInfo.getHeightWithoutBootstrap()) || (lastInfo.getTargetHeight().equals(0l) && lastInfo.getHeightWithoutBootstrap().equals(0l)); // blockchain is syncing if height equals height without bootstrap, or target height and height without bootstrap both equal 0
+
+ // write sync status to preferences
+ preferences.getXmrNodeSettings().setSyncBlockchain(blockchainSyncing);
+
// throttle warnings if daemon not synced
if (!isSyncedWithinTolerance() && System.currentTimeMillis() - lastLogDaemonNotSyncedTimestamp > HavenoUtils.LOG_DAEMON_NOT_SYNCED_WARN_PERIOD_MS) {
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), getTargetHeight());
diff --git a/core/src/main/java/haveno/core/api/XmrLocalNode.java b/core/src/main/java/haveno/core/api/XmrLocalNode.java
index 80682e9a..cd5ed266 100644
--- a/core/src/main/java/haveno/core/api/XmrLocalNode.java
+++ b/core/src/main/java/haveno/core/api/XmrLocalNode.java
@@ -150,16 +150,16 @@ public class XmrLocalNode {
/**
* Start a local Monero node from settings.
*/
- public void startMoneroNode() throws IOException {
+ public void start() throws IOException {
var settings = preferences.getXmrNodeSettings();
- this.startNode(settings);
+ this.start(settings);
}
/**
* Start local Monero node. Throws MoneroError if the node cannot be started.
* Persist the settings to preferences if the node started successfully.
*/
- public void startNode(XmrNodeSettings settings) throws IOException {
+ public void start(XmrNodeSettings settings) throws IOException {
if (isDetected()) throw new IllegalStateException("Local Monero node already online");
log.info("Starting local Monero node: " + settings);
@@ -177,6 +177,11 @@ public class XmrLocalNode {
args.add("--bootstrap-daemon-address=" + bootstrapUrl);
}
+ var syncBlockchain = settings.getSyncBlockchain();
+ if (syncBlockchain != null && !syncBlockchain) {
+ args.add("--no-sync");
+ }
+
var flags = settings.getStartupFlags();
if (flags != null) {
args.addAll(flags);
@@ -191,7 +196,7 @@ public class XmrLocalNode {
* Stop the current local Monero node if we own its process.
* Does not remove the last XmrNodeSettings.
*/
- public void stopNode() {
+ public void stop() {
if (!isDetected()) throw new IllegalStateException("Local Monero node is not running");
if (daemon.getProcess() == null || !daemon.getProcess().isAlive()) throw new IllegalStateException("Cannot stop local Monero node because we don't own its process"); // TODO (woodser): remove isAlive() check after monero-java 0.5.4 which nullifies internal process
daemon.stopProcess();
diff --git a/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java b/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java
index 2a9e23cd..76e661ae 100644
--- a/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java
+++ b/core/src/main/java/haveno/core/api/model/XmrBalanceInfo.java
@@ -98,7 +98,7 @@ public class XmrBalanceInfo implements Payload {
public String toString() {
return "XmrBalanceInfo{" +
"balance=" + balance +
- "unlockedBalance=" + availableBalance +
+ ", unlockedBalance=" + availableBalance +
", lockedBalance=" + pendingBalance +
", reservedOfferBalance=" + reservedOfferBalance +
", reservedTradeBalance=" + reservedTradeBalance +
diff --git a/core/src/main/java/haveno/core/app/HavenoExecutable.java b/core/src/main/java/haveno/core/app/HavenoExecutable.java
index e213bdd6..aa25b12d 100644
--- a/core/src/main/java/haveno/core/app/HavenoExecutable.java
+++ b/core/src/main/java/haveno/core/app/HavenoExecutable.java
@@ -124,7 +124,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
System.exit(EXIT_FAILURE);
} catch (Throwable ex) {
System.err.println("fault: An unexpected error occurred. " +
- "Please file a report at https://haveno.exchange/issues");
+ "Please file a report at https://github.com/haveno-dex/haveno/issues");
ex.printStackTrace(System.err);
System.exit(EXIT_FAILURE);
}
@@ -201,8 +201,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
startApplication();
}
} catch (InterruptedException | ExecutionException e) {
- log.error("An error occurred: {}", e.getMessage());
- e.printStackTrace();
+ log.error("An error occurred: {}\n", e.getMessage(), e);
}
});
}
@@ -362,7 +361,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
try {
ThreadUtils.awaitTasks(tasks, tasks.size(), 90000l); // run in parallel with timeout
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Failed to notify all services to prepare for shutdown: {}\n", e.getMessage(), e);
}
injector.getInstance(TradeManager.class).shutDown();
@@ -397,8 +396,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
});
});
} catch (Throwable t) {
- log.error("App shutdown failed with exception {}", t.toString());
- t.printStackTrace();
+ log.error("App shutdown failed with exception: {}\n", t.getMessage(), t);
completeShutdown(resultHandler, EXIT_FAILURE, systemExit);
}
}
diff --git a/core/src/main/java/haveno/core/app/HavenoSetup.java b/core/src/main/java/haveno/core/app/HavenoSetup.java
index 0d1ee2b9..d80ca807 100644
--- a/core/src/main/java/haveno/core/app/HavenoSetup.java
+++ b/core/src/main/java/haveno/core/app/HavenoSetup.java
@@ -53,6 +53,7 @@ import haveno.core.alert.Alert;
import haveno.core.alert.AlertManager;
import haveno.core.alert.PrivateNotificationManager;
import haveno.core.alert.PrivateNotificationPayload;
+import haveno.core.api.CoreContext;
import haveno.core.api.XmrConnectionService;
import haveno.core.api.XmrLocalNode;
import haveno.core.locale.Res;
@@ -131,7 +132,10 @@ public class HavenoSetup {
private final Preferences preferences;
private final User user;
private final AlertManager alertManager;
+ @Getter
private final Config config;
+ @Getter
+ private final CoreContext coreContext;
private final AccountAgeWitnessService accountAgeWitnessService;
private final TorSetup torSetup;
private final CoinFormatter formatter;
@@ -228,6 +232,7 @@ public class HavenoSetup {
User user,
AlertManager alertManager,
Config config,
+ CoreContext coreContext,
AccountAgeWitnessService accountAgeWitnessService,
TorSetup torSetup,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
@@ -253,6 +258,7 @@ public class HavenoSetup {
this.user = user;
this.alertManager = alertManager;
this.config = config;
+ this.coreContext = coreContext;
this.accountAgeWitnessService = accountAgeWitnessService;
this.torSetup = torSetup;
this.formatter = formatter;
@@ -263,6 +269,7 @@ public class HavenoSetup {
this.arbitrationManager = arbitrationManager;
HavenoUtils.havenoSetup = this;
+ HavenoUtils.preferences = preferences;
}
///////////////////////////////////////////////////////////////////////////////////////////
@@ -376,8 +383,7 @@ public class HavenoSetup {
moneroWalletRpcFile.setExecutable(true);
}
} catch (Exception e) {
- e.printStackTrace();
- log.warn("Failed to install Monero binaries: " + e.toString());
+ log.warn("Failed to install Monero binaries: {}\n", e.getMessage(), e);
}
}
diff --git a/core/src/main/java/haveno/core/app/TorSetup.java b/core/src/main/java/haveno/core/app/TorSetup.java
index d878464a..c28e509e 100644
--- a/core/src/main/java/haveno/core/app/TorSetup.java
+++ b/core/src/main/java/haveno/core/app/TorSetup.java
@@ -28,6 +28,9 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import javax.annotation.Nullable;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import lombok.extern.slf4j.Slf4j;
@Slf4j
@@ -48,8 +51,7 @@ public class TorSetup {
if (resultHandler != null)
resultHandler.run();
} catch (IOException e) {
- e.printStackTrace();
- log.error(e.toString());
+ log.error(ExceptionUtils.getStackTrace(e));
if (errorMessageHandler != null)
errorMessageHandler.handleErrorMessage(e.toString());
}
diff --git a/core/src/main/java/haveno/core/app/misc/ExecutableForAppWithP2p.java b/core/src/main/java/haveno/core/app/misc/ExecutableForAppWithP2p.java
index 8086d563..725ccd87 100644
--- a/core/src/main/java/haveno/core/app/misc/ExecutableForAppWithP2p.java
+++ b/core/src/main/java/haveno/core/app/misc/ExecutableForAppWithP2p.java
@@ -123,7 +123,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
try {
ThreadUtils.awaitTasks(tasks, tasks.size(), 120000l); // run in parallel with timeout
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Error awaiting tasks to complete: {}\n", e.getMessage(), e);
}
JsonFileManager.shutDownAllInstances();
@@ -177,8 +177,7 @@ public abstract class ExecutableForAppWithP2p extends HavenoExecutable {
}, 1);
}
} catch (Throwable t) {
- log.debug("App shutdown failed with exception");
- t.printStackTrace();
+ log.info("App shutdown failed with exception: {}\n", t.getMessage(), t);
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
resultHandler.handleResult();
log.info("Graceful shutdown resulted in an error. Exiting now.");
diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java
index aa561f12..4824579d 100644
--- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java
@@ -977,7 +977,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// handle result
resultHandler.handleResult(null);
} catch (Exception e) {
- if (!openOffer.isCanceled()) e.printStackTrace();
+ if (!openOffer.isCanceled()) log.error("Error processing pending offer: {}\n", e.getMessage(), e);
errorMessageHandler.handleErrorMessage(e.getMessage());
}
}).start();
@@ -1365,9 +1365,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
});
result = true;
} catch (Exception e) {
- e.printStackTrace();
errorMessage = "Exception at handleSignOfferRequest " + e.getMessage();
- log.error(errorMessage);
+ log.error(errorMessage + "\n", e);
} finally {
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
}
@@ -1519,8 +1518,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
result = true;
} catch (Throwable t) {
errorMessage = "Exception at handleRequestIsOfferAvailableMessage " + t.getMessage();
- log.error(errorMessage);
- t.printStackTrace();
+ log.error(errorMessage + "\n", t);
} finally {
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), result, errorMessage);
}
diff --git a/core/src/main/java/haveno/core/provider/fee/FeeProvider.java b/core/src/main/java/haveno/core/provider/fee/FeeProvider.java
index 30d140f8..18838cee 100644
--- a/core/src/main/java/haveno/core/provider/fee/FeeProvider.java
+++ b/core/src/main/java/haveno/core/provider/fee/FeeProvider.java
@@ -59,8 +59,7 @@ public class FeeProvider extends HttpClientProvider {
map.put(Config.BTC_TX_FEE, btcTxFee);
map.put(Config.BTC_MIN_TX_FEE, btcMinTxFee);
} catch (Throwable t) {
- log.error(t.toString());
- t.printStackTrace();
+ log.error("Error getting fees: {}\n", t.getMessage(), t);
}
return new Tuple2<>(tsMap, map);
}
diff --git a/core/src/main/java/haveno/core/provider/price/PriceProvider.java b/core/src/main/java/haveno/core/provider/price/PriceProvider.java
index 871151a9..17bef33a 100644
--- a/core/src/main/java/haveno/core/provider/price/PriceProvider.java
+++ b/core/src/main/java/haveno/core/provider/price/PriceProvider.java
@@ -68,8 +68,7 @@ public class PriceProvider extends HttpClientProvider {
long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec"));
marketPriceMap.put(currencyCode, new MarketPrice(currencyCode, price, timestampSec, true));
} catch (Throwable t) {
- log.error(t.toString());
- t.printStackTrace();
+ log.error("Error getting all prices: {}\n", t.getMessage(), t);
}
});
diff --git a/core/src/main/java/haveno/core/support/SupportManager.java b/core/src/main/java/haveno/core/support/SupportManager.java
index d7615448..10cbfdaf 100644
--- a/core/src/main/java/haveno/core/support/SupportManager.java
+++ b/core/src/main/java/haveno/core/support/SupportManager.java
@@ -199,7 +199,7 @@ public abstract class SupportManager {
if (dispute.isClosed()) dispute.reOpen();
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_OPENED);
} else if (dispute.isClosed()) {
- trade.pollWalletNormallyForMs(30000); // sync to check for payout
+ trade.pollWalletNormallyForMs(60000); // sync to check for payout
}
}
}
diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java
index 192516b5..eb49e0c5 100644
--- a/core/src/main/java/haveno/core/support/dispute/DisputeManager.java
+++ b/core/src/main/java/haveno/core/support/dispute/DisputeManager.java
@@ -83,6 +83,9 @@ import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet;
import javax.annotation.Nullable;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import java.math.BigInteger;
import java.security.KeyPair;
import java.time.Instant;
@@ -523,7 +526,7 @@ public abstract class DisputeManager> extends Sup
DisputeValidation.validateSenderNodeAddress(dispute, message.getSenderNodeAddress(), config);
//DisputeValidation.testIfDisputeTriesReplay(dispute, disputeList.getList());
} catch (DisputeValidation.ValidationException e) {
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
validationExceptions.add(e);
throw e;
}
@@ -532,9 +535,9 @@ public abstract class DisputeManager> extends Sup
try {
DisputeValidation.validatePaymentAccountPayload(dispute); // TODO: add field to dispute details: valid, invalid, missing
} catch (Exception e) {
- e.printStackTrace();
- log.warn(e.getMessage());
+ log.error(ExceptionUtils.getStackTrace(e));
trade.prependErrorMessage(e.getMessage());
+ throw e;
}
// get sender
@@ -606,9 +609,8 @@ public abstract class DisputeManager> extends Sup
}
}
} catch (Exception e) {
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
errorMessage = e.getMessage();
- log.warn(errorMessage);
if (trade != null) trade.setErrorMessage(errorMessage);
}
@@ -852,7 +854,7 @@ public abstract class DisputeManager> extends Sup
// the state, as that is displayed to the user and we only persist that msg
disputeResult.getChatMessage().setArrived(true);
trade.advanceDisputeState(Trade.DisputeState.ARBITRATOR_SAW_ARRIVED_DISPUTE_CLOSED_MSG);
- trade.pollWalletNormallyForMs(30000);
+ trade.pollWalletNormallyForMs(60000);
requestPersistence(trade);
resultHandler.handleResult();
}
diff --git a/core/src/main/java/haveno/core/support/dispute/DisputeSummaryVerification.java b/core/src/main/java/haveno/core/support/dispute/DisputeSummaryVerification.java
index a2f2f2f2..5fbd64db 100644
--- a/core/src/main/java/haveno/core/support/dispute/DisputeSummaryVerification.java
+++ b/core/src/main/java/haveno/core/support/dispute/DisputeSummaryVerification.java
@@ -71,7 +71,7 @@ public class DisputeSummaryVerification {
disputeAgent = arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).orElse(null);
checkNotNull(disputeAgent, "Dispute agent is null");
} catch (Throwable e) {
- e.printStackTrace();
+ log.error("Error verifying signature: {}\n", e.getMessage(), e);
throw new IllegalArgumentException(Res.get("support.sigCheck.popup.invalidFormat"));
}
@@ -93,7 +93,7 @@ public class DisputeSummaryVerification {
throw new IllegalArgumentException(Res.get("support.sigCheck.popup.failed"));
}
} catch (Throwable e) {
- e.printStackTrace();
+ log.error("Error verifying signature with agent pub key ring: {}\n", e.getMessage(), e);
throw new IllegalArgumentException(Res.get("support.sigCheck.popup.invalidFormat"));
}
}
diff --git a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java
index 8082aad4..50be387c 100644
--- a/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java
+++ b/core/src/main/java/haveno/core/support/dispute/arbitration/ArbitrationManager.java
@@ -94,6 +94,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
@@ -355,11 +357,11 @@ public final class ArbitrationManager extends DisputeManager {
+ try {
+
+ // get audio file
+ File wavFile = new File(havenoSetup.getConfig().appDataDir, fileName);
+ if (!wavFile.exists()) FileUtil.resourceToFile(fileName, wavFile);
+ AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(wavFile);
+
+ // get original format
+ AudioFormat baseFormat = audioInputStream.getFormat();
+
+ // set target format: PCM_SIGNED, 16-bit
+ AudioFormat targetFormat = new AudioFormat(
+ AudioFormat.Encoding.PCM_SIGNED,
+ baseFormat.getSampleRate(),
+ 16, // 16-bit instead of 32-bit float
+ baseFormat.getChannels(),
+ baseFormat.getChannels() * 2, // Frame size: 2 bytes per channel (16-bit)
+ baseFormat.getSampleRate(),
+ false // Little-endian
+ );
+
+ // convert audio to target format
+ AudioInputStream convertedStream = AudioSystem.getAudioInputStream(targetFormat, audioInputStream);
+
+ // play audio
+ DataLine.Info info = new DataLine.Info(SourceDataLine.class, targetFormat);
+ SourceDataLine sourceLine = (SourceDataLine) AudioSystem.getLine(info);
+ sourceLine.open(targetFormat);
+ sourceLine.start();
+ byte[] buffer = new byte[1024];
+ int bytesRead = 0;
+ while ((bytesRead = convertedStream.read(buffer, 0, buffer.length)) != -1) {
+ sourceLine.write(buffer, 0, bytesRead);
+ }
+ sourceLine.drain();
+ sourceLine.close();
+ convertedStream.close();
+ audioInputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }).start();
+ }
}
diff --git a/core/src/main/java/haveno/core/trade/Trade.java b/core/src/main/java/haveno/core/trade/Trade.java
index ca3df853..5f51e8ea 100644
--- a/core/src/main/java/haveno/core/trade/Trade.java
+++ b/core/src/main/java/haveno/core/trade/Trade.java
@@ -649,6 +649,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
ThreadUtils.submitToPool(() -> {
if (newValue == Trade.Phase.DEPOSIT_REQUESTED) startPolling();
if (newValue == Trade.Phase.DEPOSITS_PUBLISHED) onDepositsPublished();
+ if (newValue == Trade.Phase.PAYMENT_SENT) onPaymentSent();
if (isDepositsPublished() && !isPayoutUnlocked()) updatePollPeriod();
if (isPaymentReceived()) {
UserThread.execute(() -> {
@@ -999,8 +1000,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
xmrWalletService.deleteWallet(getWalletName());
xmrWalletService.deleteWalletBackups(getWalletName());
} catch (Exception e) {
- log.warn(e.getMessage());
- e.printStackTrace();
+ log.warn("Error deleting wallet for {} {}: {}\n", getClass().getSimpleName(), getId(), e.getMessage(), e);
setErrorMessage(e.getMessage());
processModel.getTradeManager().getNotificationService().sendErrorNotification("Error", e.getMessage());
}
@@ -1051,7 +1051,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
synchronized (HavenoUtils.getWalletFunctionLock()) {
MoneroTxWallet tx = wallet.createTx(txConfig);
exportMultisigHex();
- requestSaveWallet();
+ saveWallet();
return tx;
}
}
@@ -1152,14 +1152,14 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
throw e;
}
}
- requestSaveWallet();
+ saveWallet();
}
log.info("Done importing multisig hexes for {} {} in {} ms, count={}", getClass().getSimpleName(), getShortId(), System.currentTimeMillis() - startTime, multisigHexes.size());
}
private void handleWalletError(Exception e, MoneroRpcConnection sourceConnection) {
if (HavenoUtils.isUnresponsive(e)) forceCloseWallet(); // wallet can be stuck a while
- if (xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection);
+ if (!HavenoUtils.isIllegal(e) && xmrConnectionService.isConnected()) requestSwitchToNextBestConnection(sourceConnection);
getWallet(); // re-open wallet
}
@@ -1279,7 +1279,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
} catch (IllegalArgumentException | IllegalStateException e) {
throw e;
} catch (Exception e) {
- log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage());
+ log.warn("Failed to process payout tx, tradeId={}, attempt={}/{}, error={}", getShortId(), i + 1, TradeProtocol.MAX_ATTEMPTS, e.getMessage(), e);
handleWalletError(e, sourceConnection);
if (i == TradeProtocol.MAX_ATTEMPTS - 1) throw e;
HavenoUtils.waitFor(TradeProtocol.REPROCESS_DELAY_MS); // wait before retrying
@@ -1351,20 +1351,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
try {
MoneroMultisigSignResult result = wallet.signMultisigTxHex(payoutTxHex);
if (result.getSignedMultisigTxHex() == null) throw new IllegalArgumentException("Error signing payout tx, signed multisig hex is null");
- payoutTxHex = result.getSignedMultisigTxHex();
- setPayoutTxHex(payoutTxHex);
+ setPayoutTxHex(result.getSignedMultisigTxHex());
} catch (Exception e) {
throw new IllegalStateException(e);
}
// describe result
- describedTxSet = wallet.describeMultisigTxSet(payoutTxHex);
+ 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?
@@ -1373,17 +1373,20 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
}
// save trade state
+ saveWallet();
requestPersistence();
// submit payout tx
- if (publish) {
+ boolean doPublish = publish && !isPayoutPublished();
+ if (doPublish) {
try {
- wallet.submitMultisigTxHex(payoutTxHex);
+ wallet.submitMultisigTxHex(getPayoutTxHex());
setPayoutStatePublished();
} catch (Exception e) {
- if (isPayoutPublished()) throw new IllegalStateException("Payout tx already published for " + getClass().getSimpleName() + " " + getShortId());
- if (HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e);
- throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId(), e);
+ if (!isPayoutPublished()) {
+ if (HavenoUtils.isTransactionRejected(e) || HavenoUtils.isNotEnoughSigners(e)) throw new IllegalArgumentException(e);
+ throw new RuntimeException("Failed to submit payout tx for " + getClass().getSimpleName() + " " + getId() + ", error=" + e.getMessage(), e);
+ }
}
}
}
@@ -1536,8 +1539,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
try {
ThreadUtils.awaitTask(shutDownTask, SHUTDOWN_TIMEOUT_MS);
} catch (Exception e) {
- log.warn("Error shutting down {} {}: {}", getClass().getSimpleName(), getId(), e.getMessage());
- e.printStackTrace();
+ log.warn("Error shutting down {} {}: {}\n", getClass().getSimpleName(), getId(), e.getMessage(), e);
// force close wallet
forceCloseWallet();
@@ -2817,8 +2819,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
processing = false;
if (!isInitialized || isShutDownStarted) return;
if (isWalletConnectedToDaemon()) {
- e.printStackTrace();
- log.warn("Error polling idle trade for {} {}: {}. Monerod={}", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getXmrConnectionService().getConnection());
+ log.warn("Error polling idle trade for {} {}: {}. Monerod={}\n", getClass().getSimpleName(), getId(), e.getMessage(), getXmrWalletService().getXmrConnectionService().getConnection(), e);
};
}
}, getId());
@@ -2833,6 +2834,7 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
// close open offer or reset address entries
if (this instanceof MakerTrade) {
processModel.getOpenOfferManager().closeOpenOffer(getOffer());
+ HavenoUtils.notificationService.sendTradeNotification(this, Phase.DEPOSITS_PUBLISHED, "Offer Taken", "Your offer " + offer.getId() + " has been accepted"); // TODO (woodser): use language translation
} else {
getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
}
@@ -2841,6 +2843,12 @@ public abstract class Trade extends XmrWalletBase implements Tradable, Model {
ThreadUtils.submitToPool(() -> xmrWalletService.freezeOutputs(getSelf().getReserveTxKeyImages()));
}
+ private void onPaymentSent() {
+ if (this instanceof SellerTrade) {
+ HavenoUtils.notificationService.sendTradeNotification(this, Phase.PAYMENT_SENT, "Payment Sent", "The buyer has sent the payment"); // TODO (woodser): use language translation
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/src/main/java/haveno/core/trade/TradeManager.java b/core/src/main/java/haveno/core/trade/TradeManager.java
index b88330a3..55acf63d 100644
--- a/core/src/main/java/haveno/core/trade/TradeManager.java
+++ b/core/src/main/java/haveno/core/trade/TradeManager.java
@@ -68,7 +68,6 @@ import haveno.core.support.dispute.mediation.mediator.MediatorManager;
import haveno.core.support.dispute.messages.DisputeClosedMessage;
import haveno.core.support.dispute.messages.DisputeOpenedMessage;
import haveno.core.trade.Trade.DisputeState;
-import haveno.core.trade.Trade.Phase;
import haveno.core.trade.failed.FailedTradesManager;
import haveno.core.trade.handlers.TradeResultHandler;
import haveno.core.trade.messages.DepositRequest;
@@ -134,7 +133,6 @@ import lombok.Setter;
import monero.daemon.model.MoneroTx;
import org.bitcoinj.core.Coin;
import org.bouncycastle.crypto.params.KeyParameter;
-import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -258,7 +256,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
failedTradesManager.setUnFailTradeCallback(this::unFailTrade);
- xmrWalletService.setTradeManager(this);
+ // TODO: better way to set references
+ xmrWalletService.setTradeManager(this); // TODO: set reference in HavenoUtils for consistency
+ HavenoUtils.notificationService = notificationService;
}
@@ -366,8 +366,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
trade.onShutDownStarted();
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("Connection reset")) return; // expected if shut down with ctrl+c
- log.warn("Error notifying {} {} that shut down started {}", trade.getClass().getSimpleName(), trade.getId());
- e.printStackTrace();
+ log.warn("Error notifying {} {} that shut down started: {}\n", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
}
});
try {
@@ -396,15 +395,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
trade.shutDown();
} catch (Exception e) {
if (e.getMessage() != null && (e.getMessage().contains("Connection reset") || e.getMessage().contains("Connection refused"))) return; // expected if shut down with ctrl+c
- log.warn("Error closing {} {}", trade.getClass().getSimpleName(), trade.getId());
- e.printStackTrace();
+ log.warn("Error closing {} {}: {}", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
}
});
try {
ThreadUtils.awaitTasks(tasks);
} catch (Exception e) {
- log.warn("Error shutting down trades: {}", e.getMessage());
- e.printStackTrace();
+ log.warn("Error shutting down trades: {}\n", e.getMessage(), e);
}
}
@@ -462,8 +459,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
} catch (Exception e) {
if (!isShutDownStarted) {
- e.printStackTrace();
- log.warn("Error initializing {} {}: {}", trade.getClass().getSimpleName(), trade.getId(), e.getMessage());
+ log.warn("Error initializing {} {}: {}\n", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
trade.setInitError(e);
}
}
@@ -603,14 +599,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
initTradeAndProtocol(trade, createTradeProtocol(trade));
addTrade(trade);
- // notify on phase changes
- // TODO (woodser): save subscription, bind on startup
- EasyBind.subscribe(trade.statePhaseProperty(), phase -> {
- if (phase == Phase.DEPOSITS_PUBLISHED) {
- notificationService.sendTradeNotification(trade, "Offer Taken", "Your offer " + offer.getId() + " has been accepted"); // TODO (woodser): use language translation
- }
- });
-
// process with protocol
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
log.warn("Maker error during trade initialization: " + errorMessage);
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java
index a97beb7d..54ced825 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessDepositRequest.java
@@ -68,7 +68,7 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
complete();
} catch (Throwable t) {
this.error = t;
- t.printStackTrace();
+ 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);
}
@@ -155,15 +155,14 @@ public class ArbitratorProcessDepositRequest extends TradeTask {
log.info("Arbitrator published deposit txs for trade " + trade.getId());
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
} catch (Exception e) {
- log.warn("Arbitrator error publishing deposit txs for trade {} {}: {}", trade.getClass().getSimpleName(), trade.getShortId(), e.getMessage());
- e.printStackTrace();
+ log.warn("Arbitrator error publishing deposit txs for trade {} {}: {}\n", trade.getClass().getSimpleName(), trade.getShortId(), e.getMessage(), e);
if (!depositTxsRelayed) {
// flush txs from pool
try {
daemon.flushTxPool(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash());
} catch (Exception e2) {
- e2.printStackTrace();
+ log.warn("Error flushing deposit txs from pool for trade {}: {}\n", trade.getId(), e2.getMessage(), e2);
}
}
throw e;
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java
index fff1192c..10baac85 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorProcessReserveTx.java
@@ -29,6 +29,8 @@ import monero.daemon.model.MoneroTx;
import java.math.BigInteger;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
/**
* Arbitrator verifies reserve tx from maker or taker.
*
@@ -73,7 +75,7 @@ public class ArbitratorProcessReserveTx extends TradeTask {
request.getReserveTxKey(),
null);
} catch (Exception e) {
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
throw new RuntimeException("Error processing reserve tx from " + (isFromMaker ? "maker " : "taker ") + processModel.getTempTradePeerNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
}
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeResendDisputeClosedMessageWithPayout.java b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeResendDisputeClosedMessageWithPayout.java
index 30c3e3d8..4dd914cb 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeResendDisputeClosedMessageWithPayout.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/MaybeResendDisputeClosedMessageWithPayout.java
@@ -62,7 +62,7 @@ public class MaybeResendDisputeClosedMessageWithPayout extends TradeTask {
HavenoUtils.arbitrationManager.closeDisputeTicket(dispute.getDisputeResultProperty().get(), dispute, dispute.getDisputeResultProperty().get().summaryNotesProperty().get(), () -> {
completeAux();
}, (errMessage, err) -> {
- err.printStackTrace();
+ log.error("Failed to close dispute ticket for trade {}: {}\n", trade.getId(), errMessage, err);
failed(err);
});
ticketClosed = true;
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositsConfirmedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositsConfirmedMessage.java
index a43c667d..c11df74f 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositsConfirmedMessage.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessDepositsConfirmedMessage.java
@@ -70,7 +70,7 @@ public class ProcessDepositsConfirmedMessage extends TradeTask {
try {
trade.importMultisigHex();
} catch (Exception e) {
- e.printStackTrace();
+ log.warn("Error importing multisig hex on deposits confirmed for trade " + trade.getId() + ": " + e.getMessage() + "\n", e);
}
});
}
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java
index 78fafed6..71b53c5d 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitMultisigRequest.java
@@ -32,6 +32,7 @@ import haveno.network.p2p.NodeAddress;
import haveno.network.p2p.SendDirectMessageListener;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
+import monero.wallet.model.MoneroMultisigInfo;
import monero.wallet.model.MoneroMultisigInitResult;
import java.util.Arrays;
@@ -118,8 +119,17 @@ public class ProcessInitMultisigRequest extends TradeTask {
if (processModel.getMultisigAddress() == null && peers[0].getExchangedMultisigHex() != null && peers[1].getExchangedMultisigHex() != null) {
log.info("Importing exchanged multisig hex for trade {}", trade.getId());
MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getExchangedMultisigHex(), peers[1].getExchangedMultisigHex()), xmrWalletService.getWalletPassword());
+
+ // check multisig state
+ MoneroMultisigInfo multisigInfo = multisigWallet.getMultisigInfo();
+ if (!multisigInfo.isMultisig()) throw new RuntimeException("Multisig wallet is not multisig on completion");
+ if (!multisigInfo.isReady()) throw new RuntimeException("Multisig wallet is not ready on completion");
+ if (multisigInfo.getThreshold() != 2) throw new RuntimeException("Multisig wallet has unexpected threshold: " + multisigInfo.getThreshold());
+ if (multisigInfo.getNumParticipants() != 3) throw new RuntimeException("Multisig wallet has unexpected number of participants: " + multisigInfo.getNumParticipants());
+
+ // set final address and save
processModel.setMultisigAddress(result.getAddress());
- new Thread(() -> trade.saveWallet()).start(); // save multisig wallet off thread on completion
+ trade.saveWallet();
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
}
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java
index d8de68da..03ff6bd0 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/ProcessPaymentReceivedMessage.java
@@ -120,7 +120,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
} catch (Throwable t) {
// do not reprocess illegal argument
- if (t instanceof IllegalArgumentException) {
+ if (HavenoUtils.isIllegal(t)) {
trade.getSeller().setPaymentReceivedMessage(null); // do not reprocess
trade.requestPersistence();
}
@@ -151,6 +151,7 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
boolean deferSignAndPublish = trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout();
if (deferSignAndPublish) {
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
+ trade.pollWalletNormallyForMs(60000);
for (int i = 0; i < 5; i++) {
if (trade.isPayoutPublished()) break;
HavenoUtils.waitFor(Trade.DEFER_PUBLISH_MS / 5);
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java
index bf0a4df0..a483026a 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/SellerPreparePaymentReceivedMessage.java
@@ -65,10 +65,10 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
trade.processPayoutTx(trade.getPayoutTxHex(), false, true);
}
} catch (IllegalArgumentException | IllegalStateException e) {
- log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage());
+ log.warn("Illegal state or argument verifying, signing, and publishing payout tx for {} {}: {}. Creating new unsigned payout tx", trade.getClass().getSimpleName(), trade.getId(), e.getMessage(), e);
createUnsignedPayoutTx();
} catch (Exception e) {
- log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage());
+ log.warn("Error verifying, signing, and publishing payout tx for trade {}: {}", trade.getId(), e.getMessage(), e);
throw e;
}
}
diff --git a/core/src/main/java/haveno/core/trade/protocol/tasks/TradeTask.java b/core/src/main/java/haveno/core/trade/protocol/tasks/TradeTask.java
index 231e5482..293b74f9 100644
--- a/core/src/main/java/haveno/core/trade/protocol/tasks/TradeTask.java
+++ b/core/src/main/java/haveno/core/trade/protocol/tasks/TradeTask.java
@@ -61,7 +61,7 @@ public abstract class TradeTask extends Task {
@Override
protected void failed(Throwable t) {
- t.printStackTrace();
+ log.error("Trade task failed, error={}\n", t.getMessage(), t);
appendExceptionToErrorMessage(t);
trade.setErrorMessage(errorMessage);
processModel.getTradeManager().requestPersistence();
diff --git a/core/src/main/java/haveno/core/user/Preferences.java b/core/src/main/java/haveno/core/user/Preferences.java
index 311bd310..b57b5708 100644
--- a/core/src/main/java/haveno/core/user/Preferences.java
+++ b/core/src/main/java/haveno/core/user/Preferences.java
@@ -132,6 +132,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
private final String xmrNodesFromOptions;
@Getter
private final BooleanProperty useStandbyModeProperty = new SimpleBooleanProperty(prefPayload.isUseStandbyMode());
+ @Getter
+ private final BooleanProperty useSoundForNotificationsProperty = new SimpleBooleanProperty(prefPayload.isUseSoundForNotifications());
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
@@ -162,6 +164,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
requestPersistence();
});
+ useSoundForNotificationsProperty.addListener((ov) -> {
+ prefPayload.setUseSoundForNotifications(useSoundForNotificationsProperty.get());
+ requestPersistence();
+ });
+
traditionalCurrenciesAsObservable.addListener((javafx.beans.Observable ov) -> {
prefPayload.getTraditionalCurrencies().clear();
prefPayload.getTraditionalCurrencies().addAll(traditionalCurrenciesAsObservable);
@@ -259,6 +266,7 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
// set all properties
useAnimationsProperty.set(prefPayload.isUseAnimations());
useStandbyModeProperty.set(prefPayload.isUseStandbyMode());
+ useSoundForNotificationsProperty.set(prefPayload.isUseSoundForNotifications());
cssThemeProperty.set(prefPayload.getCssTheme());
@@ -697,6 +705,10 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
this.useStandbyModeProperty.set(useStandbyMode);
}
+ public void setUseSoundForNotifications(boolean useSoundForNotifications) {
+ this.useSoundForNotificationsProperty.set(useSoundForNotifications);
+ }
+
public void setTakeOfferSelectedPaymentAccountId(String value) {
prefPayload.setTakeOfferSelectedPaymentAccountId(value);
requestPersistence();
@@ -946,6 +958,8 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
void setUseStandbyMode(boolean useStandbyMode);
+ void setUseSoundForNotifications(boolean useSoundForNotifications);
+
void setTakeOfferSelectedPaymentAccountId(String value);
void setIgnoreDustThreshold(int value);
diff --git a/core/src/main/java/haveno/core/user/PreferencesPayload.java b/core/src/main/java/haveno/core/user/PreferencesPayload.java
index b0a2fd7d..5484c514 100644
--- a/core/src/main/java/haveno/core/user/PreferencesPayload.java
+++ b/core/src/main/java/haveno/core/user/PreferencesPayload.java
@@ -108,6 +108,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private boolean useMarketNotifications = true;
private boolean usePriceNotifications = true;
private boolean useStandbyMode = false;
+ private boolean useSoundForNotifications = true;
@Nullable
private String rpcUser;
@Nullable
@@ -185,6 +186,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
.setUseMarketNotifications(useMarketNotifications)
.setUsePriceNotifications(usePriceNotifications)
.setUseStandbyMode(useStandbyMode)
+ .setUseSoundForNotifications(useSoundForNotifications)
.setBuyerSecurityDepositAsPercent(buyerSecurityDepositAsPercent)
.setIgnoreDustThreshold(ignoreDustThreshold)
.setClearDataAfterDays(clearDataAfterDays)
@@ -280,6 +282,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
proto.getUseMarketNotifications(),
proto.getUsePriceNotifications(),
proto.getUseStandbyMode(),
+ proto.getUseSoundForNotifications(),
proto.getRpcUser().isEmpty() ? null : proto.getRpcUser(),
proto.getRpcPw().isEmpty() ? null : proto.getRpcPw(),
proto.getTakeOfferSelectedPaymentAccountId().isEmpty() ? null : proto.getTakeOfferSelectedPaymentAccountId(),
diff --git a/core/src/main/java/haveno/core/xmr/Balances.java b/core/src/main/java/haveno/core/xmr/Balances.java
index 6958c828..fe49b941 100644
--- a/core/src/main/java/haveno/core/xmr/Balances.java
+++ b/core/src/main/java/haveno/core/xmr/Balances.java
@@ -111,6 +111,7 @@ public class Balances {
public XmrBalanceInfo getBalances() {
synchronized (this) {
+ if (availableBalance == null) return null;
return new XmrBalanceInfo(availableBalance.longValue() + pendingBalance.longValue(),
availableBalance.longValue(),
pendingBalance.longValue(),
@@ -127,6 +128,9 @@ public class Balances {
synchronized (this) {
synchronized (HavenoUtils.xmrWalletService.getWalletLock()) {
+ // get non-trade balance before
+ BigInteger balanceSumBefore = getNonTradeBalanceSum();
+
// get wallet balances
BigInteger balance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getBalance();
availableBalance = xmrWalletService.getWallet() == null ? BigInteger.ZERO : xmrWalletService.getAvailableBalance();
@@ -160,8 +164,25 @@ public class Balances {
reservedBalance = reservedOfferBalance.add(reservedTradeBalance);
// notify balance update
- UserThread.execute(() -> updateCounter.set(updateCounter.get() + 1));
+ UserThread.execute(() -> {
+
+ // check if funds received
+ boolean fundsReceived = balanceSumBefore != null && getNonTradeBalanceSum().compareTo(balanceSumBefore) > 0;
+ if (fundsReceived) {
+ HavenoUtils.playCashRegisterSound();
+ }
+
+ // increase counter to notify listeners
+ updateCounter.set(updateCounter.get() + 1);
+ });
}
}
}
+
+ private BigInteger getNonTradeBalanceSum() {
+ synchronized (this) {
+ if (availableBalance == null) return null;
+ return availableBalance.add(pendingBalance).add(reservedOfferBalance);
+ }
+ }
}
diff --git a/core/src/main/java/haveno/core/xmr/XmrNodeSettings.java b/core/src/main/java/haveno/core/xmr/XmrNodeSettings.java
index 0db9868f..9802f036 100644
--- a/core/src/main/java/haveno/core/xmr/XmrNodeSettings.java
+++ b/core/src/main/java/haveno/core/xmr/XmrNodeSettings.java
@@ -35,6 +35,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
String bootstrapUrl;
@Nullable
List startupFlags;
+ @Nullable
+ Boolean syncBlockchain;
public XmrNodeSettings() {
}
@@ -43,7 +45,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
return new XmrNodeSettings(
proto.getBlockchainPath(),
proto.getBootstrapUrl(),
- proto.getStartupFlagsList());
+ proto.getStartupFlagsList(),
+ proto.getSyncBlockchain());
}
@Override
@@ -52,6 +55,7 @@ public class XmrNodeSettings implements PersistableEnvelope {
Optional.ofNullable(blockchainPath).ifPresent(e -> builder.setBlockchainPath(blockchainPath));
Optional.ofNullable(bootstrapUrl).ifPresent(e -> builder.setBootstrapUrl(bootstrapUrl));
Optional.ofNullable(startupFlags).ifPresent(e -> builder.addAllStartupFlags(startupFlags));
+ Optional.ofNullable(syncBlockchain).ifPresent(e -> builder.setSyncBlockchain(syncBlockchain));
return builder.build();
}
}
diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java
index 0cbc41b2..81eada32 100644
--- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java
+++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletBase.java
@@ -6,6 +6,8 @@ import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+
import haveno.common.Timer;
import haveno.common.UserThread;
import haveno.core.api.XmrConnectionService;
@@ -25,7 +27,7 @@ import monero.wallet.model.MoneroWalletListener;
public class XmrWalletBase {
// constants
- public static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 60;
+ public static final int SYNC_PROGRESS_TIMEOUT_SECONDS = 120;
public static final int DIRECT_SYNC_WITHIN_BLOCKS = 100;
// inherited
@@ -106,7 +108,7 @@ public class XmrWalletBase {
height = wallet.getHeight(); // can get read timeout while syncing
} catch (Exception e) {
log.warn("Error getting wallet height while syncing with progress: " + e.getMessage());
- if (wallet != null && !isShutDownStarted) e.printStackTrace();
+ if (wallet != null && !isShutDownStarted) log.warn(ExceptionUtils.getStackTrace(e));
// stop polling and release latch
syncProgressError = e;
diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
index 325bdc13..1a9ead35 100644
--- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
+++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
@@ -818,7 +818,7 @@ public class XmrWalletService extends XmrWalletBase {
MoneroFeeEstimate feeEstimates = getDaemon().getFeeEstimate();
BigInteger baseFeeEstimate = feeEstimates.getFees().get(2); // get elevated fee per kB
BigInteger qmask = feeEstimates.getQuantizationMask();
- log.info("Monero base fee estimate={}, qmask={}: " + baseFeeEstimate, qmask);
+ log.info("Monero base fee estimate={}, qmask={}", baseFeeEstimate, qmask);
// get tx base fee
BigInteger baseFee = baseFeeEstimate.multiply(BigInteger.valueOf(txWeight));
@@ -922,8 +922,7 @@ public class XmrWalletService extends XmrWalletBase {
try {
ThreadUtils.awaitTask(shutDownTask, SHUTDOWN_TIMEOUT_MS);
} catch (Exception e) {
- log.warn("Error shutting down {}: {}", getClass().getSimpleName(), e.getMessage());
- e.printStackTrace();
+ log.warn("Error shutting down {}: {}\n", getClass().getSimpleName(), e.getMessage(), e);
// force close wallet
forceCloseMainWallet();
@@ -945,8 +944,7 @@ public class XmrWalletService extends XmrWalletBase {
List unusedAddressEntries = getUnusedAddressEntries();
if (!unusedAddressEntries.isEmpty()) return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(unusedAddressEntries.get(0), context, offerId);
} catch (Exception e) {
- log.warn("Error getting new address entry based on incoming transactions");
- e.printStackTrace();
+ log.warn("Error getting new address entry based on incoming transactions: {}\n", e.getMessage(), e);
}
// create new entry
@@ -1172,8 +1170,7 @@ public class XmrWalletService extends XmrWalletBase {
try {
balanceListener.onBalanceChanged(balance);
} catch (Exception e) {
- log.warn("Failed to notify balance listener of change");
- e.printStackTrace();
+ log.warn("Failed to notify balance listener of change: {}\n", e.getMessage(), e);
}
});
}
@@ -1309,8 +1306,7 @@ public class XmrWalletService extends XmrWalletBase {
try {
doMaybeInitMainWallet(sync, MAX_SYNC_ATTEMPTS);
} catch (Exception e) {
- log.warn("Error initializing main wallet: " + e.getMessage());
- e.printStackTrace();
+ log.warn("Error initializing main wallet: {}\n", e.getMessage(), e);
HavenoUtils.setTopError(e.getMessage());
throw e;
}
@@ -1459,9 +1455,10 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done creating full wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
return walletFull;
} catch (Exception e) {
- e.printStackTrace();
+ String errorMsg = "Could not create wallet '" + config.getPath() + "': " + e.getMessage();
+ log.warn(errorMsg + "\n", e);
if (walletFull != null) forceCloseWallet(walletFull, config.getPath());
- throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'");
+ throw new IllegalStateException(errorMsg);
}
}
@@ -1503,15 +1500,15 @@ public class XmrWalletService extends XmrWalletBase {
}
// handle success or failure
+ File originalCacheBackup = new File(cachePath + ".backup");
if (retrySuccessful) {
- originalCacheFile.delete(); // delete original wallet cache backup
+ if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
} else {
// restore original wallet cache
log.warn("Failed to open full wallet using backup cache, restoring original cache");
File cacheFile = new File(cachePath);
if (cacheFile.exists()) cacheFile.delete();
- File originalCacheBackup = new File(cachePath + ".backup");
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
// throw exception
@@ -1525,9 +1522,10 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done opening full wallet " + config.getPath());
return walletFull;
} catch (Exception e) {
- e.printStackTrace();
+ String errorMsg = "Could not open full wallet '" + config.getPath() + "': " + e.getMessage();
+ log.warn(errorMsg + "\n", e);
if (walletFull != null) forceCloseWallet(walletFull, config.getPath());
- throw new IllegalStateException("Could not open full wallet '" + config.getPath() + "'");
+ throw new IllegalStateException(errorMsg);
}
}
@@ -1557,7 +1555,7 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done creating RPC wallet " + config.getPath() + " in " + (System.currentTimeMillis() - time) + " ms");
return walletRpc;
} catch (Exception e) {
- e.printStackTrace();
+ log.warn("Could not create wallet '" + config.getPath() + "': " + e.getMessage() + "\n", e);
if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath());
throw new IllegalStateException("Could not create wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.");
}
@@ -1607,15 +1605,15 @@ public class XmrWalletService extends XmrWalletBase {
}
// handle success or failure
+ File originalCacheBackup = new File(cachePath + ".backup");
if (retrySuccessful) {
- originalCacheFile.delete(); // delete original wallet cache backup
+ if (originalCacheBackup.exists()) originalCacheBackup.delete(); // delete original wallet cache backup
} else {
// restore original wallet cache
log.warn("Failed to open RPC wallet using backup cache, restoring original cache");
File cacheFile = new File(cachePath);
if (cacheFile.exists()) cacheFile.delete();
- File originalCacheBackup = new File(cachePath + ".backup");
if (originalCacheBackup.exists()) originalCacheBackup.renameTo(new File(cachePath));
// throw exception
@@ -1629,7 +1627,7 @@ public class XmrWalletService extends XmrWalletBase {
log.info("Done opening RPC wallet " + config.getPath());
return walletRpc;
} catch (Exception e) {
- e.printStackTrace();
+ log.warn("Could not open wallet '" + config.getPath() + "': " + e.getMessage() + "\n", e);
if (walletRpc != null) forceCloseWallet(walletRpc, config.getPath());
throw new IllegalStateException("Could not open wallet '" + config.getPath() + "'. Please close Haveno, stop all monero-wallet-rpc processes in your task manager, and restart Haveno.\n\nError message: " + e.getMessage());
}
@@ -1733,7 +1731,7 @@ public class XmrWalletService extends XmrWalletBase {
wallet.changePassword(oldPassword, newPassword);
saveMainWallet();
} catch (Exception e) {
- e.printStackTrace();
+ log.warn("Error changing main wallet password: " + e.getMessage() + "\n", e);
throw e;
}
});
@@ -1916,7 +1914,7 @@ public class XmrWalletService extends XmrWalletBase {
cacheWalletInfo();
requestSaveMainWallet();
} catch (Exception e) {
- e.printStackTrace();
+ log.warn("Error caching wallet info: " + e.getMessage() + "\n", e);
}
}
}
diff --git a/core/src/main/resources/cash_register.wav b/core/src/main/resources/cash_register.wav
new file mode 100644
index 00000000..c11d9146
Binary files /dev/null and b/core/src/main/resources/cash_register.wav differ
diff --git a/core/src/main/resources/chime.wav b/core/src/main/resources/chime.wav
new file mode 100644
index 00000000..c2ba1ab0
Binary files /dev/null and b/core/src/main/resources/chime.wav differ
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index a38f13bf..8601e320 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -1266,6 +1266,7 @@ setting.preferences.general=General preferences
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. deviation from market price
setting.preferences.avoidStandbyMode=Avoid standby mode
+setting.preferences.useSoundForNotifications=Play sounds for notifications
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_cs.properties b/core/src/main/resources/i18n/displayStrings_cs.properties
index 3849693d..a3edc2ec 100644
--- a/core/src/main/resources/i18n/displayStrings_cs.properties
+++ b/core/src/main/resources/i18n/displayStrings_cs.properties
@@ -987,6 +987,7 @@ setting.preferences.general=Základní nastavení
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. odchylka od tržní ceny
setting.preferences.avoidStandbyMode=Vyhněte se pohotovostnímu režimu
+setting.preferences.useSoundForNotifications=Přehrávat zvuky pro upozornění
setting.preferences.autoConfirmXMR=Automatické potvrzení XMR
setting.preferences.autoConfirmEnabled=Povoleno
setting.preferences.autoConfirmRequiredConfirmations=Požadovaná potvrzení
diff --git a/core/src/main/resources/i18n/displayStrings_de.properties b/core/src/main/resources/i18n/displayStrings_de.properties
index 93a8edb1..1dc5d740 100644
--- a/core/src/main/resources/i18n/displayStrings_de.properties
+++ b/core/src/main/resources/i18n/displayStrings_de.properties
@@ -987,6 +987,7 @@ setting.preferences.general=Allgemeine Voreinstellungen
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Max. Abweichung vom Marktpreis
setting.preferences.avoidStandbyMode=Standby Modus verhindern
+setting.preferences.useSoundForNotifications=Spiele Geräusche für Benachrichtigungen
setting.preferences.autoConfirmXMR=XMR automatische Bestätigung
setting.preferences.autoConfirmEnabled=Aktiviert
setting.preferences.autoConfirmRequiredConfirmations=Benötigte Bestätigungen
diff --git a/core/src/main/resources/i18n/displayStrings_es.properties b/core/src/main/resources/i18n/displayStrings_es.properties
index bd4ee417..a3af6a0f 100644
--- a/core/src/main/resources/i18n/displayStrings_es.properties
+++ b/core/src/main/resources/i18n/displayStrings_es.properties
@@ -988,6 +988,7 @@ setting.preferences.general=Preferencias generales
setting.preferences.explorer=Explorador Monero
setting.preferences.deviation=Desviación máxima del precio de mercado
setting.preferences.setting.preferences.avoidStandbyMode=Evitar modo en espera
+setting.preferences.useSoundForNotifications=Reproducir sonidos para notificaciones
setting.preferences.autoConfirmXMR=Autoconfirmación XMR
setting.preferences.autoConfirmEnabled=Habilitado
setting.preferences.autoConfirmRequiredConfirmations=Confirmaciones requeridas
diff --git a/core/src/main/resources/i18n/displayStrings_fa.properties b/core/src/main/resources/i18n/displayStrings_fa.properties
index eaf7147a..b0dd0764 100644
--- a/core/src/main/resources/i18n/displayStrings_fa.properties
+++ b/core/src/main/resources/i18n/displayStrings_fa.properties
@@ -984,6 +984,7 @@ setting.preferences.general=اولویتهای عمومی
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=حداکثر تفاوت از قیمت روز بازار
setting.preferences.avoidStandbyMode=حالت «آماده باش» را نادیده بگیر
+setting.preferences.useSoundForNotifications=پخش صداها برای اعلانها
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_fr.properties b/core/src/main/resources/i18n/displayStrings_fr.properties
index 8d3b4ce4..901d3a19 100644
--- a/core/src/main/resources/i18n/displayStrings_fr.properties
+++ b/core/src/main/resources/i18n/displayStrings_fr.properties
@@ -989,6 +989,7 @@ setting.preferences.general=Préférences générales
setting.preferences.explorer=Exploreur Monero
setting.preferences.deviation=Ecart maximal par rapport au prix du marché
setting.preferences.avoidStandbyMode=Éviter le mode veille
+setting.preferences.useSoundForNotifications=Jouer des sons pour les notifications
setting.preferences.autoConfirmXMR=Auto-confirmation XMR
setting.preferences.autoConfirmEnabled=Activé
setting.preferences.autoConfirmRequiredConfirmations=Confirmations requises
diff --git a/core/src/main/resources/i18n/displayStrings_it.properties b/core/src/main/resources/i18n/displayStrings_it.properties
index 044873db..8eb8e159 100644
--- a/core/src/main/resources/i18n/displayStrings_it.properties
+++ b/core/src/main/resources/i18n/displayStrings_it.properties
@@ -986,6 +986,7 @@ setting.preferences.general=Preferenze generali
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Deviazione massima del prezzo di mercato
setting.preferences.avoidStandbyMode=Evita modalità standby
+setting.preferences.useSoundForNotifications=Riproduci suoni per le notifiche
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_ja.properties b/core/src/main/resources/i18n/displayStrings_ja.properties
index a0a71e4c..8f187cff 100644
--- a/core/src/main/resources/i18n/displayStrings_ja.properties
+++ b/core/src/main/resources/i18n/displayStrings_ja.properties
@@ -987,6 +987,7 @@ setting.preferences.general=一般設定
setting.preferences.explorer=ビットコインのエクスプローラ
setting.preferences.deviation=市場価格からの最大偏差
setting.preferences.avoidStandbyMode=スタンバイモードを避ける
+setting.preferences.useSoundForNotifications=通知音の再生
setting.preferences.autoConfirmXMR=XMR自動確認
setting.preferences.autoConfirmEnabled=有効されました
setting.preferences.autoConfirmRequiredConfirmations=必要承認
diff --git a/core/src/main/resources/i18n/displayStrings_pt-br.properties b/core/src/main/resources/i18n/displayStrings_pt-br.properties
index 177f3dc6..19d7498f 100644
--- a/core/src/main/resources/i18n/displayStrings_pt-br.properties
+++ b/core/src/main/resources/i18n/displayStrings_pt-br.properties
@@ -988,6 +988,7 @@ setting.preferences.general=Preferências gerais
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Desvio máx. do preço do mercado
setting.preferences.avoidStandbyMode=Impedir modo de economia de energia
+setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_pt.properties b/core/src/main/resources/i18n/displayStrings_pt.properties
index 6841f97c..a14e3c7b 100644
--- a/core/src/main/resources/i18n/displayStrings_pt.properties
+++ b/core/src/main/resources/i18n/displayStrings_pt.properties
@@ -985,6 +985,7 @@ setting.preferences.general=Preferências gerais
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Máx. desvio do preço de mercado
setting.preferences.avoidStandbyMode=Evite o modo espera
+setting.preferences.useSoundForNotifications=Reproduzir sons para notificações
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_ru.properties b/core/src/main/resources/i18n/displayStrings_ru.properties
index ccdcd4d6..346053e1 100644
--- a/core/src/main/resources/i18n/displayStrings_ru.properties
+++ b/core/src/main/resources/i18n/displayStrings_ru.properties
@@ -984,6 +984,7 @@ setting.preferences.general=Основные настройки
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Макс. отклонение от рыночного курса
setting.preferences.avoidStandbyMode=Избегать режима ожидания
+setting.preferences.useSoundForNotifications=Воспроизводить звуки для уведомлений
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_th.properties b/core/src/main/resources/i18n/displayStrings_th.properties
index b23765c3..626916d1 100644
--- a/core/src/main/resources/i18n/displayStrings_th.properties
+++ b/core/src/main/resources/i18n/displayStrings_th.properties
@@ -984,6 +984,7 @@ setting.preferences.general=การตั้งค่าทั่วไป
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=สูงสุด ส่วนเบี่ยงเบนจากราคาตลาด
setting.preferences.avoidStandbyMode=หลีกเลี่ยงโหมดแสตนบายด์
+setting.preferences.useSoundForNotifications=เล่นเสียงสำหรับการแจ้งเตือน
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_tr.properties b/core/src/main/resources/i18n/displayStrings_tr.properties
index f7732593..73304ada 100644
--- a/core/src/main/resources/i18n/displayStrings_tr.properties
+++ b/core/src/main/resources/i18n/displayStrings_tr.properties
@@ -1261,6 +1261,7 @@ setting.preferences.general=Genel tercihler
setting.preferences.explorer=Monero Gezgini
setting.preferences.deviation=Piyasa fiyatından maksimum sapma
setting.preferences.avoidStandbyMode=Bekleme modundan kaçın
+setting.preferences.useSoundForNotifications=Bildirimler için sesleri çal
setting.preferences.autoConfirmXMR=XMR otomatik onay
setting.preferences.autoConfirmEnabled=Etkin
setting.preferences.autoConfirmRequiredConfirmations=Gerekli onaylar
diff --git a/core/src/main/resources/i18n/displayStrings_vi.properties b/core/src/main/resources/i18n/displayStrings_vi.properties
index a1978b79..ca3f46aa 100644
--- a/core/src/main/resources/i18n/displayStrings_vi.properties
+++ b/core/src/main/resources/i18n/displayStrings_vi.properties
@@ -986,6 +986,7 @@ setting.preferences.general=Tham khảo chung
setting.preferences.explorer=Monero Explorer
setting.preferences.deviation=Sai lệch tối đa so với giá thị trường
setting.preferences.avoidStandbyMode=Tránh để chế độ chờ
+setting.preferences.useSoundForNotifications=Phát âm thanh cho thông báo
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
setting.preferences.autoConfirmRequiredConfirmations=Required confirmations
diff --git a/core/src/main/resources/i18n/displayStrings_zh-hans.properties b/core/src/main/resources/i18n/displayStrings_zh-hans.properties
index e809a310..c9dce9d7 100644
--- a/core/src/main/resources/i18n/displayStrings_zh-hans.properties
+++ b/core/src/main/resources/i18n/displayStrings_zh-hans.properties
@@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
setting.preferences.explorer=比特币区块浏览器
setting.preferences.deviation=与市场价格最大差价
setting.preferences.avoidStandbyMode=避免待机模式
+setting.preferences.useSoundForNotifications=播放通知声音
setting.preferences.autoConfirmXMR=XMR 自动确认
setting.preferences.autoConfirmEnabled=启用
setting.preferences.autoConfirmRequiredConfirmations=已要求确认
diff --git a/core/src/main/resources/i18n/displayStrings_zh-hant.properties b/core/src/main/resources/i18n/displayStrings_zh-hant.properties
index 435da337..53ee27d2 100644
--- a/core/src/main/resources/i18n/displayStrings_zh-hant.properties
+++ b/core/src/main/resources/i18n/displayStrings_zh-hant.properties
@@ -987,6 +987,7 @@ setting.preferences.general=通用偏好
setting.preferences.explorer=比特幣區塊瀏覽器
setting.preferences.deviation=與市場價格最大差價
setting.preferences.avoidStandbyMode=避免待機模式
+setting.preferences.useSoundForNotifications=播放通知音效
setting.preferences.autoConfirmXMR=XMR 自動確認
setting.preferences.autoConfirmEnabled=啟用
setting.preferences.autoConfirmRequiredConfirmations=已要求確認
diff --git a/desktop/src/main/java/haveno/desktop/main/account/content/cryptoaccounts/CryptoAccountsDataModel.java b/desktop/src/main/java/haveno/desktop/main/account/content/cryptoaccounts/CryptoAccountsDataModel.java
index d6b937ac..95fa4bbd 100644
--- a/desktop/src/main/java/haveno/desktop/main/account/content/cryptoaccounts/CryptoAccountsDataModel.java
+++ b/desktop/src/main/java/haveno/desktop/main/account/content/cryptoaccounts/CryptoAccountsDataModel.java
@@ -18,7 +18,6 @@
package haveno.desktop.main.account.content.cryptoaccounts;
import com.google.inject.Inject;
-import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.proto.persistable.PersistenceProtoResolver;
import haveno.core.account.witness.AccountAgeWitnessService;
@@ -55,7 +54,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
private final String accountsFileName = "CryptoPaymentAccounts";
private final PersistenceProtoResolver persistenceProtoResolver;
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
- private final KeyRing keyRing;
@Inject
public CryptoAccountsDataModel(User user,
@@ -64,8 +62,7 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
TradeManager tradeManager,
AccountAgeWitnessService accountAgeWitnessService,
PersistenceProtoResolver persistenceProtoResolver,
- CorruptedStorageFileHandler corruptedStorageFileHandler,
- KeyRing keyRing) {
+ CorruptedStorageFileHandler corruptedStorageFileHandler) {
this.user = user;
this.preferences = preferences;
this.openOfferManager = openOfferManager;
@@ -73,7 +70,6 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
this.accountAgeWitnessService = accountAgeWitnessService;
this.persistenceProtoResolver = persistenceProtoResolver;
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
- this.keyRing = keyRing;
setChangeListener = change -> fillAndSortPaymentAccounts();
}
@@ -157,12 +153,12 @@ class CryptoAccountsDataModel extends ActivatableDataModel {
ArrayList accounts = new ArrayList<>(user.getPaymentAccounts().stream()
.filter(paymentAccount -> paymentAccount instanceof AssetAccount)
.collect(Collectors.toList()));
- GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
+ GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
}
}
public void importAccounts(Stage stage) {
- GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
+ GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
}
public int getNumPaymentAccounts() {
diff --git a/desktop/src/main/java/haveno/desktop/main/account/content/traditionalaccounts/TraditionalAccountsDataModel.java b/desktop/src/main/java/haveno/desktop/main/account/content/traditionalaccounts/TraditionalAccountsDataModel.java
index b72b89fe..909aa945 100644
--- a/desktop/src/main/java/haveno/desktop/main/account/content/traditionalaccounts/TraditionalAccountsDataModel.java
+++ b/desktop/src/main/java/haveno/desktop/main/account/content/traditionalaccounts/TraditionalAccountsDataModel.java
@@ -18,7 +18,6 @@
package haveno.desktop.main.account.content.traditionalaccounts;
import com.google.inject.Inject;
-import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.proto.persistable.PersistenceProtoResolver;
import haveno.core.account.witness.AccountAgeWitnessService;
@@ -56,7 +55,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
private final String accountsFileName = "FiatPaymentAccounts";
private final PersistenceProtoResolver persistenceProtoResolver;
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
- private final KeyRing keyRing;
@Inject
public TraditionalAccountsDataModel(User user,
@@ -65,8 +63,7 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
TradeManager tradeManager,
AccountAgeWitnessService accountAgeWitnessService,
PersistenceProtoResolver persistenceProtoResolver,
- CorruptedStorageFileHandler corruptedStorageFileHandler,
- KeyRing keyRing) {
+ CorruptedStorageFileHandler corruptedStorageFileHandler) {
this.user = user;
this.preferences = preferences;
this.openOfferManager = openOfferManager;
@@ -74,7 +71,6 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
this.accountAgeWitnessService = accountAgeWitnessService;
this.persistenceProtoResolver = persistenceProtoResolver;
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
- this.keyRing = keyRing;
setChangeListener = change -> fillAndSortPaymentAccounts();
}
@@ -159,12 +155,12 @@ class TraditionalAccountsDataModel extends ActivatableDataModel {
ArrayList accounts = new ArrayList<>(user.getPaymentAccounts().stream()
.filter(paymentAccount -> !(paymentAccount instanceof AssetAccount))
.collect(Collectors.toList()));
- GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
+ GUIUtil.exportAccounts(accounts, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
}
}
public void importAccounts(Stage stage) {
- GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
+ GUIUtil.importAccounts(user, accountsFileName, preferences, stage, persistenceProtoResolver, corruptedStorageFileHandler);
}
public int getNumPaymentAccounts() {
diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java
index c9ef1d6c..f0baf8c9 100644
--- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java
+++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsListItem.java
@@ -54,6 +54,7 @@ class TransactionsListItem {
private boolean received;
private boolean detailsAvailable;
private BigInteger amount = BigInteger.ZERO;
+ private BigInteger txFee = BigInteger.ZERO;
private String memo = "";
private long confirmations = 0;
@Getter
@@ -107,6 +108,7 @@ class TransactionsListItem {
amount = valueSentFromMe.multiply(BigInteger.valueOf(-1));
received = false;
direction = Res.get("funds.tx.direction.sentTo");
+ txFee = tx.getFee().multiply(BigInteger.valueOf(-1));
}
if (optionalTradable.isPresent()) {
@@ -201,6 +203,14 @@ class TransactionsListItem {
return amount;
}
+ public BigInteger getTxFee() {
+ return txFee;
+ }
+
+ public String getTxFeeStr() {
+ return txFee.equals(BigInteger.ZERO) ? "" : HavenoUtils.formatXmr(txFee);
+ }
+
public String getAddressString() {
return addressString;
}
diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml
index 8cd53a17..7c5da978 100644
--- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml
+++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.fxml
@@ -36,7 +36,8 @@
-
+
+
diff --git a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java
index f5cca952..44fdab6b 100644
--- a/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java
+++ b/desktop/src/main/java/haveno/desktop/main/funds/transactions/TransactionsView.java
@@ -70,7 +70,7 @@ public class TransactionsView extends ActivatableView {
@FXML
TableView tableView;
@FXML
- TableColumn dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, memoColumn, confidenceColumn, revertTxColumn;
+ TableColumn dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, txFeeColumn, memoColumn, confidenceColumn, revertTxColumn;
@FXML
Label numItems;
@FXML
@@ -89,7 +89,7 @@ public class TransactionsView extends ActivatableView {
private EventHandler keyEventEventHandler;
private Scene scene;
- private TransactionsUpdater transactionsUpdater = new TransactionsUpdater();
+ private final TransactionsUpdater transactionsUpdater = new TransactionsUpdater();
private class TransactionsUpdater extends MoneroWalletListener {
@Override
@@ -129,11 +129,12 @@ public class TransactionsView extends ActivatableView {
addressColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.address")));
transactionColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.txId", Res.getBaseCurrencyCode())));
amountColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode())));
+ txFeeColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.txFee", Res.getBaseCurrencyCode())));
memoColumn.setGraphic(new AutoTooltipLabel(Res.get("funds.tx.memo")));
confidenceColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.confirmations", Res.getBaseCurrencyCode())));
revertTxColumn.setGraphic(new AutoTooltipLabel(Res.get("shared.revert", Res.getBaseCurrencyCode())));
- tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+ tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_FLEX_LAST_COLUMN);
tableView.setPlaceholder(new AutoTooltipLabel(Res.get("funds.tx.noTxAvailable")));
setDateColumnCellFactory();
@@ -141,6 +142,7 @@ public class TransactionsView extends ActivatableView {
setAddressColumnCellFactory();
setTransactionColumnCellFactory();
setAmountColumnCellFactory();
+ setTxFeeColumnCellFactory();
setMemoColumnCellFactory();
setConfidenceColumnCellFactory();
setRevertTxColumnCellFactory();
@@ -156,7 +158,7 @@ public class TransactionsView extends ActivatableView {
addressColumn.setComparator(Comparator.comparing(item -> item.getDirection() + item.getAddressString()));
transactionColumn.setComparator(Comparator.comparing(TransactionsListItem::getTxId));
amountColumn.setComparator(Comparator.comparing(TransactionsListItem::getAmount));
- confidenceColumn.setComparator(Comparator.comparingLong(item -> item.getNumConfirmations()));
+ confidenceColumn.setComparator(Comparator.comparingLong(TransactionsListItem::getNumConfirmations));
memoColumn.setComparator(Comparator.comparing(TransactionsListItem::getMemo));
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
@@ -216,8 +218,9 @@ public class TransactionsView extends ActivatableView {
columns[2] = item.getDirection() + " " + item.getAddressString();
columns[3] = item.getTxId();
columns[4] = item.getAmountStr();
- columns[5] = item.getMemo() == null ? "" : item.getMemo();
- columns[6] = String.valueOf(item.getNumConfirmations());
+ columns[5] = item.getTxFeeStr();
+ columns[6] = item.getMemo() == null ? "" : item.getMemo();
+ columns[7] = String.valueOf(item.getNumConfirmations());
return columns;
};
@@ -414,6 +417,33 @@ public class TransactionsView extends ActivatableView {
});
}
+
+ private void setTxFeeColumnCellFactory() {
+ txFeeColumn.setCellValueFactory((addressListItem) ->
+ new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
+ txFeeColumn.setCellFactory(
+ new Callback<>() {
+
+ @Override
+ public TableCell call(TableColumn column) {
+ return new TableCell<>() {
+
+ @Override
+ public void updateItem(final TransactionsListItem item, boolean empty) {
+ super.updateItem(item, empty);
+
+ if (item != null && !empty) {
+ setGraphic(new AutoTooltipLabel(item.getTxFeeStr()));
+ } else {
+ setGraphic(null);
+ }
+ }
+ };
+ }
+ });
+ }
+
private void setMemoColumnCellFactory() {
memoColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
diff --git a/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java b/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java
index 5f7fef32..997a7242 100644
--- a/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java
+++ b/desktop/src/main/java/haveno/desktop/main/overlays/windows/DisputeSummaryWindow.java
@@ -678,7 +678,7 @@ public class DisputeSummaryWindow extends Overlay {
closeTicketButton.disableProperty().unbind();
hide();
}, (errMessage, err) -> {
- log.error(errMessage);
+ log.error("Error closing dispute ticket: " + errMessage + "\n", err);
new Popup().error(err.toString()).show();
});
}
diff --git a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/TradeSubView.java b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/TradeSubView.java
index eeee3877..cef43e79 100644
--- a/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/TradeSubView.java
+++ b/desktop/src/main/java/haveno/desktop/main/portfolio/pendingtrades/TradeSubView.java
@@ -153,8 +153,7 @@ public abstract class TradeSubView extends HBox {
tradeStepView.setChatCallback(chatCallback);
tradeStepView.activate();
} catch (Exception e) {
- log.error("Creating viewClass {} caused an error {}", viewClass, e.getMessage());
- e.printStackTrace();
+ log.error("Creating viewClass {} caused an error {}\n", viewClass, e.getMessage(), e);
}
}
diff --git a/desktop/src/main/java/haveno/desktop/main/settings/preferences/PreferencesView.java b/desktop/src/main/java/haveno/desktop/main/settings/preferences/PreferencesView.java
index 08e6a083..06331e5f 100644
--- a/desktop/src/main/java/haveno/desktop/main/settings/preferences/PreferencesView.java
+++ b/desktop/src/main/java/haveno/desktop/main/settings/preferences/PreferencesView.java
@@ -108,7 +108,7 @@ public class PreferencesView extends ActivatableViewAndModel preferredTradeCurrencyComboBox;
private ToggleButton showOwnOffersInOfferBook, useAnimations, useDarkMode, sortMarketCurrenciesNumerically,
- avoidStandbyMode, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
+ avoidStandbyMode, useSoundForNotifications, useCustomFee, autoConfirmXmrToggle, hideNonAccountPaymentMethodsToggle, denyApiTakerToggle,
notifyOnPreReleaseToggle;
private int gridRow = 0;
private int displayCurrenciesGridRowIndex = 0;
@@ -209,7 +209,7 @@ public class PreferencesView extends ActivatableViewAndModel preferences.setUseSoundForNotifications(useSoundForNotifications.isSelected()));
}
private void activateAutoConfirmPreferences() {
diff --git a/desktop/src/main/java/haveno/desktop/main/shared/ChatView.java b/desktop/src/main/java/haveno/desktop/main/shared/ChatView.java
index 396d6026..236f8471 100644
--- a/desktop/src/main/java/haveno/desktop/main/shared/ChatView.java
+++ b/desktop/src/main/java/haveno/desktop/main/shared/ChatView.java
@@ -65,6 +65,7 @@ import javafx.scene.text.TextAlignment;
import javafx.geometry.Insets;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
@@ -565,12 +566,10 @@ public class ChatView extends AnchorPane {
inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + result.getName() + "]");
}
} catch (java.io.IOException e) {
- e.printStackTrace();
- log.error(e.getMessage());
+ log.error(ExceptionUtils.getStackTrace(e));
}
} catch (MalformedURLException e2) {
- e2.printStackTrace();
- log.error(e2.getMessage());
+ log.error(ExceptionUtils.getStackTrace(e2));
}
}
} else {
@@ -593,8 +592,7 @@ public class ChatView extends AnchorPane {
inputTextArea.setText(inputTextArea.getText() + "\n[" + Res.get("support.attachment") + " " + name + "]");
}
} catch (Exception e) {
- log.error(e.toString());
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
}
}
@@ -629,8 +627,7 @@ public class ChatView extends AnchorPane {
try (FileOutputStream fileOutputStream = new FileOutputStream(file.getAbsolutePath())) {
fileOutputStream.write(attachment.getBytes());
} catch (IOException e) {
- e.printStackTrace();
- System.out.println(e.getMessage());
+ log.error("Error opening attachment: {}\n", e.getMessage(), e);
}
}
}
diff --git a/desktop/src/main/java/haveno/desktop/util/GUIUtil.java b/desktop/src/main/java/haveno/desktop/util/GUIUtil.java
index f2ad96c2..2c314ea5 100644
--- a/desktop/src/main/java/haveno/desktop/util/GUIUtil.java
+++ b/desktop/src/main/java/haveno/desktop/util/GUIUtil.java
@@ -28,7 +28,6 @@ import com.googlecode.jcsv.writer.internal.CSVWriterBuilder;
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
import haveno.common.UserThread;
import haveno.common.config.Config;
-import haveno.common.crypto.KeyRing;
import haveno.common.file.CorruptedStorageFileHandler;
import haveno.common.persistence.PersistenceManager;
import haveno.common.proto.persistable.PersistableEnvelope;
@@ -168,12 +167,11 @@ public class GUIUtil {
Preferences preferences,
Stage stage,
PersistenceProtoResolver persistenceProtoResolver,
- CorruptedStorageFileHandler corruptedStorageFileHandler,
- KeyRing keyRing) {
+ CorruptedStorageFileHandler corruptedStorageFileHandler) {
if (!accounts.isEmpty()) {
String directory = getDirectoryFromChooser(preferences, stage);
if (!directory.isEmpty()) {
- PersistenceManager persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
+ PersistenceManager persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
PaymentAccountList paymentAccounts = new PaymentAccountList(accounts);
persistenceManager.initialize(paymentAccounts, fileName, PersistenceManager.Source.PRIVATE_LOW_PRIO);
persistenceManager.persistNow(() -> {
@@ -193,8 +191,7 @@ public class GUIUtil {
Preferences preferences,
Stage stage,
PersistenceProtoResolver persistenceProtoResolver,
- CorruptedStorageFileHandler corruptedStorageFileHandler,
- KeyRing keyRing) {
+ CorruptedStorageFileHandler corruptedStorageFileHandler) {
FileChooser fileChooser = new FileChooser();
File initDir = new File(preferences.getDirectoryChooserPath());
if (initDir.isDirectory()) {
@@ -207,7 +204,7 @@ public class GUIUtil {
if (Paths.get(path).getFileName().toString().equals(fileName)) {
String directory = Paths.get(path).getParent().toString();
preferences.setDirectoryChooserPath(directory);
- PersistenceManager persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
+ PersistenceManager persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
persistenceManager.readPersisted(fileName, persisted -> {
StringBuilder msg = new StringBuilder();
HashSet paymentAccounts = new HashSet<>();
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index dc626c93..5bc11c01 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -878,9 +878,9 @@
-
-
-
+
+
+
diff --git a/p2p/src/main/java/haveno/network/Socks5ProxyProvider.java b/p2p/src/main/java/haveno/network/Socks5ProxyProvider.java
index 8bb3e1d4..f9c498f0 100644
--- a/p2p/src/main/java/haveno/network/Socks5ProxyProvider.java
+++ b/p2p/src/main/java/haveno/network/Socks5ProxyProvider.java
@@ -24,6 +24,7 @@ import haveno.common.config.Config;
import haveno.network.p2p.network.NetworkNode;
import java.net.UnknownHostException;
import javax.annotation.Nullable;
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -96,8 +97,7 @@ public class Socks5ProxyProvider {
try {
return new Socks5Proxy(tokens[0], Integer.valueOf(tokens[1]));
} catch (UnknownHostException e) {
- log.error(e.getMessage());
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
}
} else {
log.error("Incorrect format for socks5ProxyAddress. Should be: host:port.\n" +
diff --git a/p2p/src/main/java/haveno/network/p2p/P2PService.java b/p2p/src/main/java/haveno/network/p2p/P2PService.java
index a8028d0e..117ed4a4 100644
--- a/p2p/src/main/java/haveno/network/p2p/P2PService.java
+++ b/p2p/src/main/java/haveno/network/p2p/P2PService.java
@@ -57,6 +57,8 @@ import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import lombok.Getter;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
@@ -433,15 +435,12 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
@Override
public void onFailure(@NotNull Throwable throwable) {
- log.error(throwable.toString());
- throwable.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(throwable));
sendDirectMessageListener.onFault(throwable.toString());
}
}, MoreExecutors.directExecutor());
} catch (CryptoException e) {
- e.printStackTrace();
- log.error(message.toString());
- log.error(e.toString());
+ log.error("Error sending encrypted direct message, message={}, error={}\n", message.toString(), e.getMessage(), e);
sendDirectMessageListener.onFault(e.toString());
}
}
diff --git a/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageList.java b/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageList.java
index a9d04949..451d3e7e 100644
--- a/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageList.java
+++ b/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageList.java
@@ -63,8 +63,7 @@ public class MailboxMessageList extends PersistableList {
try {
return MailboxItem.fromProto(e, networkProtoResolver);
} catch (ProtobufferException protobufferException) {
- protobufferException.printStackTrace();
- log.error("Error at MailboxItem.fromProto: {}", protobufferException.toString());
+ log.error("Error at MailboxItem.fromProto: {}", protobufferException.toString(), protobufferException);
return null;
}
})
diff --git a/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageService.java b/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageService.java
index 131b5372..c447b3fc 100644
--- a/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageService.java
+++ b/p2p/src/main/java/haveno/network/p2p/mailbox/MailboxMessageService.java
@@ -335,8 +335,7 @@ public class MailboxMessageService implements HashMapChangedListener, PersistedD
}
}, MoreExecutors.directExecutor());
} catch (CryptoException e) {
- log.error("sendEncryptedMessage failed");
- e.printStackTrace();
+ log.error("sendEncryptedMessage failed: {}\n", e.getMessage(), e);
sendMailboxMessageListener.onFault("sendEncryptedMailboxMessage failed " + e);
}
}
@@ -644,8 +643,7 @@ public class MailboxMessageService implements HashMapChangedListener, PersistedD
log.info("The mailboxEntry was already removed earlier.");
}
} catch (CryptoException e) {
- e.printStackTrace();
- log.error("Could not remove ProtectedMailboxStorageEntry from network. Error: {}", e.toString());
+ log.error("Could not remove ProtectedMailboxStorageEntry from network. Error: {}\n", e.toString(), e);
}
}
diff --git a/p2p/src/main/java/haveno/network/p2p/network/Connection.java b/p2p/src/main/java/haveno/network/p2p/network/Connection.java
index 92eddb06..1a7f1b84 100644
--- a/p2p/src/main/java/haveno/network/p2p/network/Connection.java
+++ b/p2p/src/main/java/haveno/network/p2p/network/Connection.java
@@ -91,6 +91,8 @@ import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
import org.jetbrains.annotations.Nullable;
/**
@@ -511,8 +513,7 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
Uninterruptibles.sleepUninterruptibly(200, TimeUnit.MILLISECONDS);
} catch (Throwable t) {
- log.error(t.getMessage());
- t.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(t));
} finally {
stopped = true;
ThreadUtils.execute(() -> doShutDown(closeConnectionReason, shutDownCompleteHandler), THREAD_ID);
@@ -537,16 +538,14 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
} catch (SocketException e) {
log.trace("SocketException at shutdown might be expected {}", e.getMessage());
} catch (IOException e) {
- log.error("Exception at shutdown. " + e.getMessage());
- e.printStackTrace();
+ log.error("Exception at shutdown. {}\n", e.getMessage(), e);
} finally {
capabilitiesListeners.clear();
try {
protoInputStream.close();
} catch (IOException e) {
- log.error(e.getMessage());
- e.printStackTrace();
+ log.error(ExceptionUtils.getStackTrace(e));
}
Utilities.shutdownAndAwaitTermination(executorService, SHUTDOWN_TIMEOUT, TimeUnit.MILLISECONDS);
diff --git a/p2p/src/main/java/haveno/network/p2p/network/LocalhostNetworkNode.java b/p2p/src/main/java/haveno/network/p2p/network/LocalhostNetworkNode.java
index 9254c9af..26005988 100644
--- a/p2p/src/main/java/haveno/network/p2p/network/LocalhostNetworkNode.java
+++ b/p2p/src/main/java/haveno/network/p2p/network/LocalhostNetworkNode.java
@@ -76,8 +76,7 @@ public class LocalhostNetworkNode extends NetworkNode {
try {
startServer(new ServerSocket(servicePort));
} catch (IOException e) {
- e.printStackTrace();
- log.error("Exception at startServer: " + e.getMessage());
+ log.error("Exception at startServer: {}\n", e.getMessage(), e);
}
setupListeners.stream().forEach(SetupListener::onHiddenServicePublished);
}, simulateTorDelayTorNode, TimeUnit.MILLISECONDS);
diff --git a/p2p/src/main/java/haveno/network/p2p/network/Server.java b/p2p/src/main/java/haveno/network/p2p/network/Server.java
index 9cf39f57..437e3d2f 100644
--- a/p2p/src/main/java/haveno/network/p2p/network/Server.java
+++ b/p2p/src/main/java/haveno/network/p2p/network/Server.java
@@ -97,11 +97,10 @@ class Server implements Runnable {
}
} catch (IOException e) {
if (isServerActive())
- e.printStackTrace();
+ log.error("Error executing server loop: {}\n", e.getMessage(), e);
}
} catch (Throwable t) {
- log.error("Executing task failed. " + t.getMessage());
- t.printStackTrace();
+ log.error("Executing task failed: {}\n", t.getMessage(), t);
}
}
diff --git a/p2p/src/main/java/haveno/network/p2p/storage/P2PDataStorage.java b/p2p/src/main/java/haveno/network/p2p/storage/P2PDataStorage.java
index a9c0f6ad..40be21ef 100644
--- a/p2p/src/main/java/haveno/network/p2p/storage/P2PDataStorage.java
+++ b/p2p/src/main/java/haveno/network/p2p/storage/P2PDataStorage.java
@@ -974,8 +974,7 @@ public class P2PDataStorage implements MessageListener, ConnectionListener, Pers
broadcaster.broadcast(refreshTTLMessage, sender);
} catch (IllegalArgumentException e) {
- log.error("refreshTTL failed, missing data: {}", e.toString());
- e.printStackTrace();
+ log.error("refreshTTL failed, missing data: {}\n", e.toString(), e);
return false;
}
return true;
diff --git a/p2p/src/main/java/haveno/network/p2p/storage/persistence/StoreService.java b/p2p/src/main/java/haveno/network/p2p/storage/persistence/StoreService.java
index 6d9f3df1..17c940ad 100644
--- a/p2p/src/main/java/haveno/network/p2p/storage/persistence/StoreService.java
+++ b/p2p/src/main/java/haveno/network/p2p/storage/persistence/StoreService.java
@@ -116,8 +116,7 @@ public abstract class StoreService {
log.debug("Could not find resourceFile " + resourceFileName + ". That is expected if none is provided yet.");
} catch (Throwable e) {
log.error("Could not copy resourceFile " + resourceFileName + " to " +
- destinationFile.getAbsolutePath() + ".\n" + e.getMessage());
- e.printStackTrace();
+ destinationFile.getAbsolutePath() + ".\n", e);
}
} else {
log.debug("No resource file was copied. {} exists already.", fileName);
diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto
index 03cc6b23..f63e82a1 100644
--- a/proto/src/main/proto/pb.proto
+++ b/proto/src/main/proto/pb.proto
@@ -1740,6 +1740,7 @@ message PreferencesPayload {
string buy_screen_crypto_currency_code = 60;
string sell_screen_crypto_currency_code = 61;
bool split_offer_output = 62;
+ bool use_sound_for_notifications = 63;
}
message AutoConfirmSettings {
@@ -1754,6 +1755,7 @@ message XmrNodeSettings {
string blockchain_path = 1;
string bootstrap_url = 2;
repeated string startup_flags = 3;
+ bool sync_blockchain = 4;
}
///////////////////////////////////////////////////////////////////////////////////////////
diff --git a/scripts/install_tails/README.md b/scripts/install_tails/README.md
index aa9f81ce..5619efe0 100644
--- a/scripts/install_tails/README.md
+++ b/scripts/install_tails/README.md
@@ -2,7 +2,7 @@
Install Haveno on Tails by following these steps:
-1. Enable persistent storage dotfiles and admin password before starting tails.
+1. Enable persistent storage dotfiles and admin password before starting Tails.
2. Download [haveno-install.sh](haveno-install.sh):
```
@@ -22,3 +22,9 @@ Install Haveno on Tails by following these steps:
```
4. Upon successful execution of the script (no errors), the Haveno release will be installed to persistent storage and can be launched via the desktop shortcut in the 'Other' section of the start menu.
+
+> [!note]
+> If you have already installed Haveno on Tails, we recommend moving your data directory (/home/amnesia/Persistent/Haveno-example) to the new default location (/home/amnesia/Persistent/haveno/Data/Haveno-example), to retain your history and for future support.
+
+> [!note]
+> Modern versions of Tails will invoke `curl` over Tor, but if your installation does not, then you can add `--socks5-hostname 127.0.0.1:9050` when invoking the install script.
\ No newline at end of file
diff --git a/scripts/install_tails/deprecated/README.md b/scripts/install_tails/deprecated/README.md
new file mode 100644
index 00000000..0345bd1e
--- /dev/null
+++ b/scripts/install_tails/deprecated/README.md
@@ -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 (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
\ No newline at end of file
diff --git a/scripts/install_tails/deprecated/haveno-install.sh b/scripts/install_tails/deprecated/haveno-install.sh
new file mode 100644
index 00000000..247354ff
--- /dev/null
+++ b/scripts/install_tails/deprecated/haveno-install.sh
@@ -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"
diff --git a/seednode/src/main/java/haveno/seednode/SeedNodeMain.java b/seednode/src/main/java/haveno/seednode/SeedNodeMain.java
index 5659ab2e..455f1809 100644
--- a/seednode/src/main/java/haveno/seednode/SeedNodeMain.java
+++ b/seednode/src/main/java/haveno/seednode/SeedNodeMain.java
@@ -75,7 +75,7 @@ public class SeedNodeMain extends ExecutableForAppWithP2p {
seedNode = new SeedNode();
UserThread.execute(this::onApplicationLaunched);
} catch (Exception e) {
- e.printStackTrace();
+ log.error("Error launching seed node: {}\n", e.toString(), e);
}
});
}