Merge branch 'master' into appimage-support

This commit is contained in:
TheTollingBell 2024-10-01 17:54:56 -04:00 committed by GitHub
commit 984e507ca2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
87 changed files with 618 additions and 249 deletions

View file

@ -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

View file

@ -72,7 +72,7 @@ To bring Haveno to life, we need resources. If you have the possibility, please
### Monero
<p>
<img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_monero.png" alt="Donate Monero" width="150" height="150"><br>
<img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_monero.png" alt="Donate Monero" width="115" height="115"><br>
<code>42sjokkT9FmiWPqVzrWPFE5NCJXwt96bkBozHf4vgLR9hXyJDqKHEHKVscAARuD7in5wV1meEcSTJTanCTDzidTe2cFXS1F</code>
</p>
@ -81,6 +81,6 @@ If you are using a wallet that supports OpenAlias (like the 'official' CLI and G
### Bitcoin
<p>
<img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_bitcoin.png" alt="Donate Bitcoin" width="150" height="150"><br>
<img src="https://raw.githubusercontent.com/haveno-dex/haveno/master/media/donate_bitcoin.png" alt="Donate Bitcoin" width="115" height="115"><br>
<code>1AKq3CE1yBAnxGmHXbNFfNYStcByNDc5gQ</code>
</p>

View file

@ -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);
}

View file

@ -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')
}

View file

@ -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<ILoggingEvent> 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<ILoggingEvent> 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<ILoggingEvent> 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) {

View file

@ -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);
}
}

View file

@ -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));
}
}
}

View file

@ -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<T extends Model> {
private final Queue<Class<? extends Task<T>>> tasks = new LinkedBlockingQueue<>();
@ -67,8 +69,8 @@ public class TaskRunner<T extends Model> {
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();

View file

@ -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);
}
}

View file

@ -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();
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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());
}
}

View file

@ -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())

View file

@ -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());
}
}

View file

@ -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());

View file

@ -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();

View file

@ -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 +

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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());
}

View file

@ -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.");

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}
});

View file

@ -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
}
}
}

View file

@ -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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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<T extends DisputeList<Dispute>> 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();
}

View file

@ -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"));
}
}

View file

@ -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<ArbitrationDisputeL
requestPersistence(trade);
} catch (Exception e) {
log.warn("Error processing dispute closed message: {}", e.getMessage());
e.printStackTrace();
log.warn(ExceptionUtils.getStackTrace(e));
requestPersistence(trade);
// nack bad message and do not reprocess
if (e instanceof IllegalArgumentException || e instanceof IllegalStateException) {
if (HavenoUtils.isIllegal(e)) {
trade.getArbitrator().setDisputeClosedMessage(null); // message is processed
trade.setDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
String warningMsg = "Error processing dispute closed message: " + e.getMessage() + "\n\nOpen another dispute to try again (ctrl+o).";
@ -489,8 +491,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
try {
feeEstimateTx = createDisputePayoutTx(trade, dispute.getContract(), disputeResult, false);
} catch (Exception e) {
e.printStackTrace();
log.warn("Could not recreate dispute payout tx to verify fee: " + e.getMessage());
log.warn("Could not recreate dispute payout tx to verify fee: {}\n", e.getMessage(), e);
}
if (feeEstimateTx != null) {
BigInteger feeEstimate = feeEstimateTx.getFee();

View file

@ -27,7 +27,9 @@ import haveno.common.crypto.Hash;
import haveno.common.crypto.KeyRing;
import haveno.common.crypto.PubKeyRing;
import haveno.common.crypto.Sig;
import haveno.common.file.FileUtil;
import haveno.common.util.Utilities;
import haveno.core.api.CoreNotificationService;
import haveno.core.api.XmrConnectionService;
import haveno.core.app.HavenoSetup;
import haveno.core.offer.OfferPayload;
@ -36,9 +38,12 @@ import haveno.core.support.dispute.arbitration.ArbitrationManager;
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
import haveno.core.trade.messages.PaymentReceivedMessage;
import haveno.core.trade.messages.PaymentSentMessage;
import haveno.core.user.Preferences;
import haveno.core.util.JsonUtil;
import haveno.core.xmr.wallet.XmrWalletService;
import haveno.network.p2p.NodeAddress;
import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
@ -53,6 +58,13 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection;
import monero.daemon.model.MoneroOutput;
@ -110,11 +122,18 @@ public class HavenoUtils {
public static XmrWalletService xmrWalletService;
public static XmrConnectionService xmrConnectionService;
public static OpenOfferManager openOfferManager;
public static CoreNotificationService notificationService;
public static Preferences preferences;
public static boolean isSeedNode() {
return havenoSetup == null;
}
public static boolean isDaemon() {
if (isSeedNode()) return true;
return havenoSetup.getCoreContext().isApiUser();
}
@SuppressWarnings("unused")
public static Date getReleaseDate() {
if (RELEASE_DATE == null) return null;
@ -510,19 +529,83 @@ public class HavenoUtils {
havenoSetup.getTopErrorMsg().set(msg);
}
public static boolean isConnectionRefused(Exception e) {
public static boolean isConnectionRefused(Throwable e) {
return e != null && e.getMessage().contains("Connection refused");
}
public static boolean isReadTimeout(Exception e) {
public static boolean isReadTimeout(Throwable e) {
return e != null && e.getMessage().contains("Read timed out");
}
public static boolean isUnresponsive(Exception e) {
public static boolean isUnresponsive(Throwable e) {
return isConnectionRefused(e) || isReadTimeout(e);
}
public static boolean isNotEnoughSigners(Exception e) {
public static boolean isNotEnoughSigners(Throwable e) {
return e != null && e.getMessage().contains("Not enough signers");
}
public static boolean isTransactionRejected(Throwable e) {
return e != null && e.getMessage().contains("was rejected");
}
public static boolean isIllegal(Throwable e) {
return e instanceof IllegalArgumentException || e instanceof IllegalStateException;
}
public static void playChimeSound() {
playAudioFile("chime.wav");
}
public static void playCashRegisterSound() {
playAudioFile("cash_register.wav");
}
private static void playAudioFile(String fileName) {
if (isDaemon()) return; // ignore if running as daemon
if (!preferences.getUseSoundForNotificationsProperty().get()) return; // ignore if sounds disabled
new Thread(() -> {
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();
}
}

View file

@ -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
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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);

View file

@ -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;

View file

@ -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());
}

View file

@ -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;

View file

@ -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);
}
});
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -61,7 +61,7 @@ public abstract class TradeTask extends Task<Trade> {
@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();

View file

@ -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);

View file

@ -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(),

View file

@ -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);
}
}
}

View file

@ -35,6 +35,8 @@ public class XmrNodeSettings implements PersistableEnvelope {
String bootstrapUrl;
@Nullable
List<String> 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();
}
}

View file

@ -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;

View file

@ -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<XmrAddressEntry> 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);
}
}
}

Binary file not shown.

Binary file not shown.

View file

@ -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

View file

@ -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í

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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=必要承認

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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=已要求确认

View file

@ -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=已要求確認

View file

@ -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<PaymentAccount> 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() {

View file

@ -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<PaymentAccount> 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() {

View file

@ -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;
}

View file

@ -36,7 +36,8 @@
<TableColumn fx:id="detailsColumn" minWidth="220" maxWidth="220"/>
<TableColumn fx:id="addressColumn" minWidth="260"/>
<TableColumn fx:id="transactionColumn" minWidth="180"/>
<TableColumn fx:id="amountColumn" minWidth="130" maxWidth="130"/>
<TableColumn fx:id="amountColumn" minWidth="110" maxWidth="110"/>
<TableColumn fx:id="txFeeColumn" minWidth="110" maxWidth="110"/>
<TableColumn fx:id="memoColumn" minWidth="40"/>
<TableColumn fx:id="confidenceColumn" minWidth="120" maxWidth="130"/>
<TableColumn fx:id="revertTxColumn" sortable="false" minWidth="110" maxWidth="110" visible="false"/>

View file

@ -70,7 +70,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@FXML
TableView<TransactionsListItem> tableView;
@FXML
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, memoColumn, confidenceColumn, revertTxColumn;
TableColumn<TransactionsListItem, TransactionsListItem> dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, txFeeColumn, memoColumn, confidenceColumn, revertTxColumn;
@FXML
Label numItems;
@FXML
@ -89,7 +89,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private EventHandler<KeyEvent> 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<VBox, Void> {
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<VBox, Void> {
setAddressColumnCellFactory();
setTransactionColumnCellFactory();
setAmountColumnCellFactory();
setTxFeeColumnCellFactory();
setMemoColumnCellFactory();
setConfidenceColumnCellFactory();
setRevertTxColumnCellFactory();
@ -156,7 +158,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
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<VBox, Void> {
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<VBox, Void> {
});
}
private void setTxFeeColumnCellFactory() {
txFeeColumn.setCellValueFactory((addressListItem) ->
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
txFeeColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<TransactionsListItem, TransactionsListItem> call(TableColumn<TransactionsListItem,
TransactionsListItem> 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()));

View file

@ -678,7 +678,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
closeTicketButton.disableProperty().unbind();
hide();
}, (errMessage, err) -> {
log.error(errMessage);
log.error("Error closing dispute ticket: " + errMessage + "\n", err);
new Popup().error(err.toString()).show();
});
}

View file

@ -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);
}
}

View file

@ -108,7 +108,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ComboBox<TradeCurrency> 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<GridPane, Preferenc
///////////////////////////////////////////////////////////////////////////////////////////
private void initializeGeneralOptions() {
int titledGroupBgRowSpan = displayStandbyModeFeature ? 7 : 6;
int titledGroupBgRowSpan = displayStandbyModeFeature ? 8 : 7;
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, titledGroupBgRowSpan, Res.get("setting.preferences.general"));
GridPane.setColumnSpan(titledGroupBg, 1);
@ -285,6 +285,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
avoidStandbyMode = addSlideToggleButton(root, ++gridRow,
Res.get("setting.preferences.avoidStandbyMode"));
}
useSoundForNotifications = addSlideToggleButton(root, ++gridRow,
Res.get("setting.preferences.useSoundForNotifications"), Layout.GROUP_DISTANCE * -1); // TODO: why must negative value be used to place toggle consistently?
}
private void initializeSeparator() {
@ -518,6 +521,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
GridPane.setHgrow(resetDontShowAgainButton, Priority.ALWAYS);
GridPane.setColumnIndex(resetDontShowAgainButton, 0);
}
private void initializeAutoConfirmOptions() {
GridPane autoConfirmGridPane = new GridPane();
GridPane.setHgrow(autoConfirmGridPane, Priority.ALWAYS);
@ -790,6 +794,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
} else {
preferences.setUseStandbyMode(false);
}
useSoundForNotifications.setSelected(preferences.isUseSoundForNotifications());
useSoundForNotifications.setOnAction(e -> preferences.setUseSoundForNotifications(useSoundForNotifications.isSelected()));
}
private void activateAutoConfirmPreferences() {

View file

@ -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);
}
}
}

View file

@ -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<PersistableEnvelope> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
PersistenceManager<PersistableEnvelope> 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<PaymentAccountList> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, keyRing);
PersistenceManager<PaymentAccountList> persistenceManager = new PersistenceManager<>(new File(directory), persistenceProtoResolver, corruptedStorageFileHandler, null);
persistenceManager.readPersisted(fileName, persisted -> {
StringBuilder msg = new StringBuilder();
HashSet<PaymentAccount> paymentAccounts = new HashSet<>();

View file

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

View file

@ -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" +

View file

@ -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());
}
}

View file

@ -63,8 +63,7 @@ public class MailboxMessageList extends PersistableList<MailboxItem> {
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;
}
})

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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;

View file

@ -116,8 +116,7 @@ public abstract class StoreService<T extends PersistableEnvelope> {
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);

View file

@ -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;
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -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.

View file

@ -0,0 +1,11 @@
# Steps to use (This has serious security concerns to tails threat model only run when you need to access haveno)
## 1. Enable persistent storage and admin password before starting tails
## 2. Get your haveno deb file in persistent storage (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

View file

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

View file

@ -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);
}
});
}