support password prompt in legacy ui

Co-authored-by: niyid <neeyeed@gmail.com>
This commit is contained in:
woodser 2023-02-26 09:08:10 -05:00
parent 99653fe40d
commit 4dde53f0e8
19 changed files with 357 additions and 272 deletions

View File

@ -116,6 +116,7 @@ public class CoreAccountService {
public void changePassword(String oldPassword, String newPassword) { public void changePassword(String oldPassword, String newPassword) {
if (!isAccountOpen()) throw new IllegalStateException("Cannot change password on unopened account"); if (!isAccountOpen()) throw new IllegalStateException("Cannot change password on unopened account");
if ("".equals(oldPassword)) oldPassword = null; // normalize to null
if (!StringUtils.equals(this.password, oldPassword)) throw new IllegalStateException("Incorrect password"); if (!StringUtils.equals(this.password, oldPassword)) throw new IllegalStateException("Incorrect password");
if (newPassword != null && newPassword.length() < 8) throw new IllegalStateException("Password must be at least 8 characters"); if (newPassword != null && newPassword.length() < 8) throw new IllegalStateException("Password must be at least 8 characters");
keyStorage.saveKeyRing(keyRing, oldPassword, newPassword); keyStorage.saveKeyRing(keyRing, oldPassword, newPassword);
@ -125,6 +126,12 @@ public class CoreAccountService {
} }
} }
public void verifyPassword(String password) throws IncorrectPasswordException {
if (!StringUtils.equals(this.password, password)) {
throw new IncorrectPasswordException("Incorrect password");
}
}
public void closeAccount() { public void closeAccount() {
if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account"); if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account");
keyRing.lockKeys(); // closed account means the keys are locked keyRing.lockKeys(); // closed account means the keys are locked

View File

@ -31,13 +31,14 @@ import bisq.core.trade.HavenoUtils;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
import bisq.common.config.HavenoHelpFormatter;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.config.ConfigException; import bisq.common.config.ConfigException;
import bisq.common.config.HavenoHelpFormatter;
import bisq.common.crypto.IncorrectPasswordException; import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
import bisq.common.persistence.PersistenceManager; import bisq.common.persistence.PersistenceManager;
@ -51,8 +52,11 @@ import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import java.io.Console; import java.io.Console;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -135,7 +139,6 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
// thread the application is running and we don't run into thread interference. // thread the application is running and we don't run into thread interference.
protected abstract void launchApplication(); protected abstract void launchApplication();
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// If application is a JavaFX application we need wait for onApplicationLaunched // If application is a JavaFX application we need wait for onApplicationLaunched
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -165,12 +168,25 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
}); });
// Attempt to login, subclasses should implement interactive login and or rpc login. // Attempt to login, subclasses should implement interactive login and or rpc login.
if (!isReadOnly && loginAccount()) { CompletableFuture<Boolean> loginFuture = loginAccount();
readAllPersisted(this::startApplication); loginFuture.whenComplete((result, throwable) -> {
} else { if (throwable != null) {
log.warn("Running application in readonly mode"); log.error("Error logging in to account", throwable);
startApplication(); shutDownNoPersist(null, false);
} return;
}
try {
if (!isReadOnly && loginFuture.get()) {
readAllPersisted(this::startApplication);
} else {
log.warn("Running application in readonly mode");
startApplication();
}
} catch (InterruptedException | ExecutionException e) {
log.error("An error occurred: {}", e.getMessage());
e.printStackTrace();
}
});
} }
/** /**
@ -199,19 +215,26 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
* *
* @return true if account is opened successfully. * @return true if account is opened successfully.
*/ */
protected boolean loginAccount() { protected CompletableFuture<Boolean> loginAccount() {
CompletableFuture<Boolean> result = new CompletableFuture<>();
if (accountService.accountExists()) { if (accountService.accountExists()) {
log.info("Account already exists, attempting to open"); log.info("Account already exists, attempting to open");
try { try {
accountService.openAccount(null); accountService.openAccount(null);
result.complete(accountService.isAccountOpen());
} catch (IncorrectPasswordException ipe) { } catch (IncorrectPasswordException ipe) {
log.info("Account password protected, password required"); log.info("Account password protected, password required");
result.complete(false);
} }
} else if (!config.passwordRequired) { } else if (!config.passwordRequired) {
log.info("Creating Haveno account with null password"); log.info("Creating Haveno account with null password");
accountService.createAccount(null); accountService.createAccount(null);
result.complete(accountService.isAccountOpen());
} else {
log.info("Account does not exist and password is required");
result.complete(false);
} }
return accountService.isAccountOpen(); return result;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -83,7 +83,6 @@ public class HavenoHeadlessApp implements HeadlessApp {
bisqSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg)); bisqSetup.setChainFileLockedExceptionHandler(msg -> log.error("onChainFileLockedExceptionHandler: msg={}", msg));
bisqSetup.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg)); bisqSetup.setLockedUpFundsHandler(msg -> log.info("onLockedUpFundsHandler: msg={}", msg));
bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(() -> log.info("onShowFirstPopupIfResyncSPVRequestedHandler")); bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(() -> log.info("onShowFirstPopupIfResyncSPVRequestedHandler"));
bisqSetup.setRequestWalletPasswordHandler(aesKeyHandler -> log.info("onRequestWalletPasswordHandler"));
bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler")); bisqSetup.setDisplayUpdateHandler((alert, key) -> log.info("onDisplayUpdateHandler"));
bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert)); bisqSetup.setDisplayAlertHandler(alert -> log.info("onDisplayAlertHandler. alert={}", alert));
bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification)); bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> log.info("onDisplayPrivateNotificationHandler. privateNotification={}", privateNotification));

View File

@ -83,8 +83,6 @@ import javafx.beans.value.ChangeListener;
import javafx.collections.SetChangeListener; import javafx.collections.SetChangeListener;
import org.bouncycastle.crypto.params.KeyParameter;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileWriter; import java.io.FileWriter;
@ -160,9 +158,6 @@ public class HavenoSetup {
private Runnable showFirstPopupIfResyncSPVRequestedHandler; private Runnable showFirstPopupIfResyncSPVRequestedHandler;
@Setter @Setter
@Nullable @Nullable
private Consumer<Consumer<KeyParameter>> requestWalletPasswordHandler;
@Setter
@Nullable
private Consumer<Alert> displayAlertHandler; private Consumer<Alert> displayAlertHandler;
@Setter @Setter
@Nullable @Nullable
@ -215,30 +210,30 @@ public class HavenoSetup {
@Inject @Inject
public HavenoSetup(DomainInitialisation domainInitialisation, public HavenoSetup(DomainInitialisation domainInitialisation,
P2PNetworkSetup p2PNetworkSetup, P2PNetworkSetup p2PNetworkSetup,
WalletAppSetup walletAppSetup, WalletAppSetup walletAppSetup,
WalletsManager walletsManager, WalletsManager walletsManager,
WalletsSetup walletsSetup, WalletsSetup walletsSetup,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
BtcWalletService btcWalletService, BtcWalletService btcWalletService,
P2PService p2PService, P2PService p2PService,
PrivateNotificationManager privateNotificationManager, PrivateNotificationManager privateNotificationManager,
SignedWitnessStorageService signedWitnessStorageService, SignedWitnessStorageService signedWitnessStorageService,
TradeManager tradeManager, TradeManager tradeManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
Preferences preferences, Preferences preferences,
User user, User user,
AlertManager alertManager, AlertManager alertManager,
Config config, Config config,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
TorSetup torSetup, TorSetup torSetup,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
LocalBitcoinNode localBitcoinNode, LocalBitcoinNode localBitcoinNode,
AppStartupState appStartupState, AppStartupState appStartupState,
Socks5ProxyProvider socks5ProxyProvider, Socks5ProxyProvider socks5ProxyProvider,
MediationManager mediationManager, MediationManager mediationManager,
RefundManager refundManager, RefundManager refundManager,
ArbitrationManager arbitrationManager) { ArbitrationManager arbitrationManager) {
this.domainInitialisation = domainInitialisation; this.domainInitialisation = domainInitialisation;
this.p2PNetworkSetup = p2PNetworkSetup; this.p2PNetworkSetup = p2PNetworkSetup;
this.walletAppSetup = walletAppSetup; this.walletAppSetup = walletAppSetup;
@ -264,6 +259,8 @@ public class HavenoSetup {
this.refundManager = refundManager; this.refundManager = refundManager;
this.arbitrationManager = arbitrationManager; this.arbitrationManager = arbitrationManager;
xmrWalletService.setHavenoSetup(this);
MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode); MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode);
} }
@ -411,9 +408,10 @@ public class HavenoSetup {
return; return;
} }
log.warn("startupTimeout called"); log.warn("startupTimeout called");
if (walletsManager.areWalletsEncrypted()) //TODO (niyid) This has a part to play in the display of the password prompt
walletInitialized.addListener(walletInitializedListener); // if (walletsManager.areWalletsEncrypted())
else if (displayTorNetworkSettingsHandler != null) // walletInitialized.addListener(walletInitializedListener);
if (displayTorNetworkSettingsHandler != null)
displayTorNetworkSettingsHandler.accept(true); displayTorNetworkSettingsHandler.accept(true);
log.info("Set log level for org.berndpruenster.netlayer classes to DEBUG to show more details for " + log.info("Set log level for org.berndpruenster.netlayer classes to DEBUG to show more details for " +
@ -453,35 +451,11 @@ public class HavenoSetup {
private void initWallet() { private void initWallet() {
log.info("Init wallet"); log.info("Init wallet");
havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet); havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet);
Runnable walletPasswordHandler = () -> {
log.info("Wallet password required");
havenoSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword);
if (p2pNetworkReady.get())
p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true);
if (requestWalletPasswordHandler != null) {
requestWalletPasswordHandler.accept(aesKey -> {
walletsManager.setAesKey(aesKey);
walletsSetup.getWalletConfig().maybeAddSegwitKeychain(walletsSetup.getWalletConfig().btcWallet(),
aesKey);
if (getResyncSpvSemaphore()) {
if (showFirstPopupIfResyncSPVRequestedHandler != null)
showFirstPopupIfResyncSPVRequestedHandler.run();
} else {
// TODO no guarantee here that the wallet is really fully initialized
// We would need a new walletInitializedButNotEncrypted state to track
// Usually init is fast and we have our wallet initialized at that state though.
walletInitialized.set(true);
}
});
}
};
walletAppSetup.init(chainFileLockedExceptionHandler, walletAppSetup.init(chainFileLockedExceptionHandler,
spvFileCorruptedHandler, spvFileCorruptedHandler,
getResyncSpvSemaphore(), getResyncSpvSemaphore(),
showFirstPopupIfResyncSPVRequestedHandler, showFirstPopupIfResyncSPVRequestedHandler,
showPopupIfInvalidBtcConfigHandler, showPopupIfInvalidBtcConfigHandler,
walletPasswordHandler,
() -> { () -> {
if (allBasicServicesInitialized) { if (allBasicServicesInitialized) {
checkForLockedUpFunds(); checkForLockedUpFunds();
@ -867,5 +841,7 @@ public class HavenoSetup {
return p2PNetworkSetup.getP2pNetworkLabelId(); return p2PNetworkSetup.getP2pNetworkLabelId();
} }
public BooleanProperty getWalletInitialized() {
return walletInitialized;
}
} }

View File

@ -108,7 +108,6 @@ public class WalletAppSetup {
boolean isSpvResyncRequested, boolean isSpvResyncRequested,
@Nullable Runnable showFirstPopupIfResyncSPVRequestedHandler, @Nullable Runnable showFirstPopupIfResyncSPVRequestedHandler,
@Nullable Runnable showPopupIfInvalidBtcConfigHandler, @Nullable Runnable showPopupIfInvalidBtcConfigHandler,
Runnable walletPasswordHandler,
Runnable downloadCompleteHandler, Runnable downloadCompleteHandler,
Runnable walletInitializedHandler) { Runnable walletInitializedHandler) {
log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}", log.info("Initialize WalletAppSetup with BitcoinJ version {} and hash of BitcoinJ commit {}",
@ -171,17 +170,7 @@ public class WalletAppSetup {
walletsSetup.initialize(null, walletsSetup.initialize(null,
() -> { () -> {
// We only check one wallet as we apply encryption to all or none walletInitializedHandler.run();
if (walletsManager.areWalletsEncrypted() && !coreContext.isApiUser()) {
walletPasswordHandler.run();
} else {
if (isSpvResyncRequested && !coreContext.isApiUser()) {
if (showFirstPopupIfResyncSPVRequestedHandler != null)
showFirstPopupIfResyncSPVRequestedHandler.run();
} else {
walletInitializedHandler.run();
}
}
}, },
exception -> { exception -> {
if (exception instanceof InvalidHostException && showPopupIfInvalidBtcConfigHandler != null) { if (exception instanceof InvalidHostException && showPopupIfInvalidBtcConfigHandler != null) {

View File

@ -307,7 +307,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
try { try {
return Encryption.decrypt(encrypted, secret); return Encryption.decrypt(encrypted, secret);
} catch (CryptoException e) { } catch (CryptoException e) {
throw new IllegalArgumentException("Illegal old password", e); throw new IllegalArgumentException("Incorrect password", e);
} }
} }

View File

@ -23,8 +23,6 @@ import bisq.common.crypto.ScryptUtil;
import bisq.common.handlers.ExceptionHandler; import bisq.common.handlers.ExceptionHandler;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.crypto.KeyCrypter; import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.wallet.DeterministicSeed; import org.bitcoinj.wallet.DeterministicSeed;

View File

@ -10,6 +10,7 @@ import bisq.common.util.Utilities;
import bisq.core.api.AccountServiceListener; import bisq.core.api.AccountServiceListener;
import bisq.core.api.CoreAccountService; import bisq.core.api.CoreAccountService;
import bisq.core.api.CoreMoneroConnectionsService; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup;
import bisq.core.btc.listeners.XmrBalanceListener; import bisq.core.btc.listeners.XmrBalanceListener;
import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.model.XmrAddressEntryList; import bisq.core.btc.model.XmrAddressEntryList;
@ -92,6 +93,7 @@ public class XmrWalletService {
private final CoreMoneroConnectionsService connectionsService; private final CoreMoneroConnectionsService connectionsService;
private final XmrAddressEntryList xmrAddressEntryList; private final XmrAddressEntryList xmrAddressEntryList;
private final WalletsSetup walletsSetup; private final WalletsSetup walletsSetup;
private final File walletDir; private final File walletDir;
private final File xmrWalletFile; private final File xmrWalletFile;
private final int rpcBindPort; private final int rpcBindPort;
@ -103,6 +105,8 @@ public class XmrWalletService {
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>(); private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
private boolean isShutDown = false; private boolean isShutDown = false;
private HavenoSetup havenoSetup;
@Inject @Inject
XmrWalletService(CoreAccountService accountService, XmrWalletService(CoreAccountService accountService,
CoreMoneroConnectionsService connectionsService, CoreMoneroConnectionsService connectionsService,
@ -148,8 +152,8 @@ public class XmrWalletService {
@Override @Override
public void onPasswordChanged(String oldPassword, String newPassword) { public void onPasswordChanged(String oldPassword, String newPassword) {
log.info(getClass() + "accountservice.onPasswordChanged()"); log.info(getClass() + "accountservice.onPasswordChanged()");
if (oldPassword == null) oldPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD; if (oldPassword == null || oldPassword.isEmpty()) oldPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD;
if (newPassword == null) newPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD; if (newPassword == null || newPassword.isEmpty()) newPassword = MONERO_WALLET_RPC_DEFAULT_PASSWORD;
changeWalletPasswords(oldPassword, newPassword); changeWalletPasswords(oldPassword, newPassword);
} }
}); });
@ -383,41 +387,41 @@ public class XmrWalletService {
// verify tx not submitted to pool // verify tx not submitted to pool
MoneroTx tx = daemon.getTx(txHash); MoneroTx tx = daemon.getTx(txHash);
if (tx != null) throw new RuntimeException("Tx is already submitted"); if (tx != null) throw new RuntimeException("Tx is already submitted");
// submit tx to pool // submit tx to pool
MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency? MoneroSubmitTxResult result = daemon.submitTxHex(txHex, true); // TODO (woodser): invert doNotRelay flag to relay for library consistency?
if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result)); if (!result.isGood()) throw new RuntimeException("Failed to submit tx to daemon: " + JsonUtils.serialize(result));
tx = getTx(txHash); tx = getTx(txHash);
// verify key images // verify key images
if (keyImages != null) { if (keyImages != null) {
Set<String> txKeyImages = new HashSet<String>(); Set<String> txKeyImages = new HashSet<String>();
for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex()); for (MoneroOutput input : tx.getInputs()) txKeyImages.add(input.getKeyImage().getHex());
if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Tx inputs do not match claimed key images"); if (!txKeyImages.equals(new HashSet<String>(keyImages))) throw new Error("Tx inputs do not match claimed key images");
} }
// verify unlock height // verify unlock height
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0"); if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
// verify trade fee // verify trade fee
String feeAddress = HavenoUtils.getTradeFeeAddress(); String feeAddress = HavenoUtils.getTradeFeeAddress();
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress); MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee"); if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount()); if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
// verify miner fee // verify miner fee
BigInteger feeEstimate = getFeeEstimate(tx.getWeight()); BigInteger feeEstimate = getFeeEstimate(tx.getWeight());
double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal? double feeDiff = tx.getFee().subtract(feeEstimate).abs().doubleValue() / feeEstimate.doubleValue(); // TODO: use BigDecimal?
if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee()); if (feeDiff > MINER_FEE_TOLERANCE) throw new Error("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + feeEstimate + " but was " + tx.getFee());
log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff); log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), feeDiff);
// verify sufficient security deposit // verify sufficient security deposit
check = wallet.checkTxKey(txHash, txKey, address); check = wallet.checkTxKey(txHash, txKey, address);
if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount"); if (!check.isGood()) throw new RuntimeException("Invalid proof of deposit amount");
BigInteger minSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger(); BigInteger minSecurityDeposit = new BigDecimal(securityDeposit).multiply(new BigDecimal(1.0 - SECURITY_DEPOSIT_TOLERANCE)).toBigInteger();
BigInteger actualSecurityDeposit = check.getReceivedAmount().subtract(sendAmount); BigInteger actualSecurityDeposit = check.getReceivedAmount().subtract(sendAmount);
if (actualSecurityDeposit.compareTo(minSecurityDeposit) < 0) throw new RuntimeException("Security deposit amount is not enough, needed " + minSecurityDeposit + " but was " + actualSecurityDeposit); if (actualSecurityDeposit.compareTo(minSecurityDeposit) < 0) throw new RuntimeException("Security deposit amount is not enough, needed " + minSecurityDeposit + " but was " + actualSecurityDeposit);
// verify deposit amount + miner fee within dust tolerance // verify deposit amount + miner fee within dust tolerance
BigInteger minDepositAndFee = sendAmount.add(securityDeposit).subtract(new BigDecimal(tx.getFee()).multiply(new BigDecimal(1.0 - DUST_TOLERANCE)).toBigInteger()); BigInteger minDepositAndFee = sendAmount.add(securityDeposit).subtract(new BigDecimal(tx.getFee()).multiply(new BigDecimal(1.0 - DUST_TOLERANCE)).toBigInteger());
BigInteger actualDepositAndFee = check.getReceivedAmount().add(tx.getFee()); BigInteger actualDepositAndFee = check.getReceivedAmount().add(tx.getFee());
@ -558,6 +562,9 @@ public class XmrWalletService {
System.out.println("Monero wallet balance: " + wallet.getBalance(0)); System.out.println("Monero wallet balance: " + wallet.getBalance(0));
System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0)); System.out.println("Monero wallet unlocked balance: " + wallet.getUnlockedBalance(0));
// notify setup that main wallet is initialized
havenoSetup.getWalletInitialized().set(true); // TODO: change to listener pattern?
// register internal listener to notify external listeners // register internal listener to notify external listeners
wallet.addListener(new XmrWalletListener()); wallet.addListener(new XmrWalletListener());
} }
@ -961,6 +968,10 @@ public class XmrWalletService {
log.info("\n" + tracePrefix + ":" + sb.toString()); log.info("\n" + tracePrefix + ":" + sb.toString());
} }
public void setHavenoSetup(HavenoSetup havenoSetup) {
this.havenoSetup = havenoSetup;
}
// -------------------------------- HELPERS ------------------------------- // -------------------------------- HELPERS -------------------------------
/** /**

View File

@ -17,11 +17,10 @@
package bisq.daemon.app; package bisq.daemon.app;
import bisq.core.app.ConsoleInput;
import bisq.core.app.HavenoHeadlessAppMain;
import bisq.core.app.HavenoSetup;
import bisq.core.api.AccountServiceListener; import bisq.core.api.AccountServiceListener;
import bisq.core.app.ConsoleInput;
import bisq.core.app.CoreModule; import bisq.core.app.CoreModule;
import bisq.core.app.HavenoHeadlessAppMain;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
@ -32,7 +31,10 @@ import bisq.common.persistence.PersistenceManager;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.Console; import java.io.Console;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -43,7 +45,7 @@ import lombok.extern.slf4j.Slf4j;
import bisq.daemon.grpc.GrpcServer; import bisq.daemon.grpc.GrpcServer;
@Slf4j @Slf4j
public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSetup.HavenoSetupListener { public class HavenoDaemonMain extends HavenoHeadlessAppMain {
private GrpcServer grpcServer; private GrpcServer grpcServer;
@ -139,53 +141,60 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
* Start the grpcServer to allow logging in remotely. * Start the grpcServer to allow logging in remotely.
*/ */
@Override @Override
protected boolean loginAccount() { protected CompletableFuture<Boolean> loginAccount() {
boolean opened = super.loginAccount(); CompletableFuture<Boolean> opened = super.loginAccount();
// Start rpc server in case login is coming in from rpc // Start rpc server in case login is coming in from rpc
grpcServer = injector.getInstance(GrpcServer.class); grpcServer = injector.getInstance(GrpcServer.class);
if (!opened) { CompletableFuture<Boolean> inputResult = new CompletableFuture<Boolean>();
// Nonblocking, we need to stop if the login occurred through rpc. try {
// TODO: add a mode to mask password if (opened.get()) {
ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS); grpcServer.start();
Thread t = new Thread(() -> { return opened;
interactiveLogin(reader); } else {
});
// Handle asynchronous account opens. // Nonblocking, we need to stop if the login occurred through rpc.
// Will need to also close and reopen account. // TODO: add a mode to mask password
AccountServiceListener accountListener = new AccountServiceListener() { ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
@Override public void onAccountCreated() { onLogin(); } Thread t = new Thread(() -> {
@Override public void onAccountOpened() { onLogin(); } interactiveLogin(reader);
private void onLogin() { });
log.info("Logged in successfully");
reader.cancel(); // closing the reader will stop all read attempts and end the interactive login thread // Handle asynchronous account opens.
// Will need to also close and reopen account.
AccountServiceListener accountListener = new AccountServiceListener() {
@Override public void onAccountCreated() { onLogin(); }
@Override public void onAccountOpened() { onLogin(); }
private void onLogin() {
log.info("Logged in successfully");
reader.cancel(); // closing the reader will stop all read attempts and end the interactive login thread
}
};
accountService.addListener(accountListener);
// start server after the listener is registered
grpcServer.start();
try {
// Wait until interactive login or rpc. Check one more time if account is open to close race condition.
if (!accountService.isAccountOpen()) {
log.info("Interactive login required");
t.start();
t.join();
}
} catch (InterruptedException e) {
// expected
} }
};
accountService.addListener(accountListener);
// start server after the listener is registered accountService.removeListener(accountListener);
grpcServer.start(); inputResult.complete(accountService.isAccountOpen());
try {
// Wait until interactive login or rpc. Check one more time if account is open to close race condition.
if (!accountService.isAccountOpen()) {
log.info("Interactive login required");
t.start();
t.join();
}
} catch (InterruptedException e) {
// expected
} }
} catch (InterruptedException | ExecutionException e) {
accountService.removeListener(accountListener); inputResult.completeExceptionally(e);
opened = accountService.isAccountOpen();
} else {
grpcServer.start();
} }
return opened; return inputResult;
} }
/** /**

View File

@ -67,8 +67,8 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.geometry.Rectangle2D;
import javafx.geometry.BoundingBox; import javafx.geometry.BoundingBox;
import javafx.geometry.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -128,6 +128,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler {
} }
public void startApplication(Runnable onApplicationStartedHandler) { public void startApplication(Runnable onApplicationStartedHandler) {
log.info("Running startApplication...");
try { try {
mainView = loadMainView(injector); mainView = loadMainView(injector);
mainView.setOnApplicationStartedHandler(onApplicationStartedHandler); mainView.setOnApplicationStartedHandler(onApplicationStartedHandler);
@ -149,7 +150,7 @@ public class HavenoApp extends Application implements UncaughtExceptionHandler {
.show(); .show();
new Thread(() -> { new Thread(() -> {
gracefulShutDownHandler.gracefulShutDown(() -> { gracefulShutDownHandler.gracefulShutDown(() -> {
log.debug("App shutdown complete"); log.info("App shutdown complete");
}); });
}).start(); }).start();
shutDownRequested = true; shutDownRequested = true;

View File

@ -20,6 +20,7 @@ package bisq.desktop.app;
import bisq.desktop.common.UITimer; import bisq.desktop.common.UITimer;
import bisq.desktop.common.view.guice.InjectorViewFactory; import bisq.desktop.common.view.guice.InjectorViewFactory;
import bisq.desktop.setup.DesktopPersistedDataHost; import bisq.desktop.setup.DesktopPersistedDataHost;
import bisq.desktop.util.ImageUtil;
import bisq.core.app.AvoidStandbyModeService; import bisq.core.app.AvoidStandbyModeService;
import bisq.core.app.HavenoExecutable; import bisq.core.app.HavenoExecutable;
@ -27,10 +28,25 @@ import bisq.core.app.HavenoExecutable;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.crypto.IncorrectPasswordException;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -139,4 +155,86 @@ public class HavenoAppMain extends HavenoExecutable {
// Therefore, calling this as part of onApplicationStarted() // Therefore, calling this as part of onApplicationStarted()
log.info("Using JavaFX {}", System.getProperty("javafx.version")); log.info("Using JavaFX {}", System.getProperty("javafx.version"));
} }
@Override
protected CompletableFuture<Boolean> loginAccount() {
// attempt default login
CompletableFuture<Boolean> result = super.loginAccount();
try {
if (result.get()) return result;
} catch (InterruptedException | ExecutionException e) {
throw new IllegalStateException(e);
}
// login using dialog
CompletableFuture<Boolean> dialogResult = new CompletableFuture<>();
Platform.setImplicitExit(false);
Platform.runLater(() -> {
// show password dialog until account open
String errorMessage = null;
while (!accountService.isAccountOpen()) {
// create the password dialog
PasswordDialog passwordDialog = new PasswordDialog(errorMessage);
// wait for user to enter password
Optional<String> passwordResult = passwordDialog.showAndWait();
if (passwordResult.isPresent()) {
try {
accountService.openAccount(passwordResult.get());
dialogResult.complete(accountService.isAccountOpen());
} catch (IncorrectPasswordException e) {
errorMessage = "Incorrect password";
}
} else {
// if the user cancelled the dialog, complete the passwordFuture exceptionally
dialogResult.completeExceptionally(new Exception("Password dialog cancelled"));
break;
}
}
});
return dialogResult;
}
private class PasswordDialog extends Dialog<String> {
public PasswordDialog(String errorMessage) {
setTitle("Enter Password");
setHeaderText("Please enter your Haveno password:");
// Add an icon to the dialog
Stage stage = (Stage) getDialogPane().getScene().getWindow();
stage.getIcons().add(ImageUtil.getImageByPath("lock.png"));
// Create the password field
PasswordField passwordField = new PasswordField();
passwordField.setPromptText("Password");
// Create the error message field
Label errorMessageField = new Label(errorMessage);
errorMessageField.setTextFill(Color.color(1, 0, 0));
// Set the dialog content
VBox vbox = new VBox(10);
vbox.getChildren().addAll(new ImageView(ImageUtil.getImageByPath("logo_splash.png")), passwordField, errorMessageField);
getDialogPane().setContent(vbox);
// Add OK and Cancel buttons
ButtonType okButton = new ButtonType("OK", ButtonBar.ButtonData.OK_DONE);
ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
getDialogPane().getButtonTypes().addAll(okButton, cancelButton);
// Convert the result to a string when the OK button is clicked
setResultConverter(buttonType -> {
if (buttonType == okButton) {
return passwordField.getText();
} else {
new Thread(() -> HavenoApp.getShutDownHandler().run()).start();
return null;
}
});
}
}
} }

View File

@ -18,7 +18,6 @@
package bisq.desktop.main; package bisq.desktop.main;
import bisq.desktop.Navigation; import bisq.desktop.Navigation;
import bisq.desktop.app.HavenoApp;
import bisq.desktop.common.model.ViewModel; import bisq.desktop.common.model.ViewModel;
import bisq.desktop.components.TxIdTextField; import bisq.desktop.components.TxIdTextField;
import bisq.desktop.main.account.AccountView; import bisq.desktop.main.account.AccountView;
@ -109,7 +108,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener { public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener {
private final HavenoSetup bisqSetup; private final HavenoSetup havenoSetup;
private final CoreMoneroConnectionsService connectionService; private final CoreMoneroConnectionsService connectionService;
private final User user; private final User user;
private final BalancePresentation balancePresentation; private final BalancePresentation balancePresentation;
@ -154,7 +153,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public MainViewModel(HavenoSetup bisqSetup, public MainViewModel(HavenoSetup havenoSetup,
CoreMoneroConnectionsService connectionService, CoreMoneroConnectionsService connectionService,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
User user, User user,
@ -179,7 +178,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
TorNetworkSettingsWindow torNetworkSettingsWindow, TorNetworkSettingsWindow torNetworkSettingsWindow,
CorruptedStorageFileHandler corruptedStorageFileHandler, CorruptedStorageFileHandler corruptedStorageFileHandler,
Navigation navigation) { Navigation navigation) {
this.bisqSetup = bisqSetup; this.havenoSetup = havenoSetup;
this.connectionService = connectionService; this.connectionService = connectionService;
this.user = user; this.user = user;
this.balancePresentation = balancePresentation; this.balancePresentation = balancePresentation;
@ -211,7 +210,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
GUIUtil.setPreferences(preferences); GUIUtil.setPreferences(preferences);
setupHandlers(); setupHandlers();
bisqSetup.addHavenoSetupListener(this); havenoSetup.addHavenoSetupListener(this);
} }
@ -303,7 +302,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
} }
void onOpenDownloadWindow() { void onOpenDownloadWindow() {
bisqSetup.displayAlertIfPresent(user.getDisplayedAlert(), true); havenoSetup.displayAlertIfPresent(user.getDisplayedAlert(), true);
} }
void setPriceFeedComboBoxItem(PriceFeedComboBoxItem item) { void setPriceFeedComboBoxItem(PriceFeedComboBoxItem item) {
@ -316,34 +315,30 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private void setupHandlers() { private void setupHandlers() {
bisqSetup.setDisplayTacHandler(acceptedHandler -> UserThread.runAfter(() -> { havenoSetup.setDisplayTacHandler(acceptedHandler -> UserThread.runAfter(() -> {
//noinspection FunctionalExpressionCanBeFolded //noinspection FunctionalExpressionCanBeFolded
tacWindow.onAction(acceptedHandler::run).show(); tacWindow.onAction(acceptedHandler::run).show();
}, 1)); }, 1));
bisqSetup.setDisplayTorNetworkSettingsHandler(show -> { havenoSetup.setDisplayTorNetworkSettingsHandler(show -> {
if (show) { if (show) {
torNetworkSettingsWindow.show(); torNetworkSettingsWindow.show();
} else if (torNetworkSettingsWindow.isDisplayed()) { } else if (torNetworkSettingsWindow.isDisplayed()) {
torNetworkSettingsWindow.hide(); torNetworkSettingsWindow.hide();
} }
}); });
bisqSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg) havenoSetup.setSpvFileCorruptedHandler(msg -> new Popup().warning(msg)
.actionButtonText(Res.get("settings.net.reSyncSPVChainButton")) .actionButtonText(Res.get("settings.net.reSyncSPVChainButton"))
.onAction(() -> GUIUtil.reSyncSPVChain(preferences)) .onAction(() -> GUIUtil.reSyncSPVChain(preferences))
.show()); .show());
bisqSetup.setChainFileLockedExceptionHandler(msg -> new Popup().warning(msg) havenoSetup.setChainFileLockedExceptionHandler(msg -> new Popup().warning(msg)
.useShutDownButton() .useShutDownButton()
.show()); .show());
bisqSetup.setLockedUpFundsHandler(msg -> new Popup().width(850).warning(msg).show()); havenoSetup.setLockedUpFundsHandler(msg -> new Popup().width(850).warning(msg).show());
bisqSetup.setShowFirstPopupIfResyncSPVRequestedHandler(this::showFirstPopupIfResyncSPVRequested); havenoSetup.setShowFirstPopupIfResyncSPVRequestedHandler(this::showFirstPopupIfResyncSPVRequested);
bisqSetup.setRequestWalletPasswordHandler(aesKeyHandler -> walletPasswordWindow
.onAesKey(aesKeyHandler::accept)
.onClose(() -> HavenoApp.getShutDownHandler().run())
.show());
bisqSetup.setDisplayUpdateHandler((alert, key) -> new DisplayUpdateDownloadWindow(alert, config) havenoSetup.setDisplayUpdateHandler((alert, key) -> new DisplayUpdateDownloadWindow(alert, config)
.actionButtonText(Res.get("displayUpdateDownloadWindow.button.downloadLater")) .actionButtonText(Res.get("displayUpdateDownloadWindow.button.downloadLater"))
.onAction(() -> { .onAction(() -> {
preferences.dontShowAgain(key, false); // update later preferences.dontShowAgain(key, false); // update later
@ -353,19 +348,19 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
preferences.dontShowAgain(key, true); // ignore update preferences.dontShowAgain(key, true); // ignore update
}) })
.show()); .show());
bisqSetup.setDisplayAlertHandler(alert -> new DisplayAlertMessageWindow() havenoSetup.setDisplayAlertHandler(alert -> new DisplayAlertMessageWindow()
.alertMessage(alert) .alertMessage(alert)
.closeButtonText(Res.get("shared.close")) .closeButtonText(Res.get("shared.close"))
.onClose(() -> user.setDisplayedAlert(alert)) .onClose(() -> user.setDisplayedAlert(alert))
.show()); .show());
bisqSetup.setDisplayPrivateNotificationHandler(privateNotification -> havenoSetup.setDisplayPrivateNotificationHandler(privateNotification ->
new Popup().headLine(Res.get("popup.privateNotification.headline")) new Popup().headLine(Res.get("popup.privateNotification.headline"))
.attention(privateNotification.getMessage()) .attention(privateNotification.getMessage())
.onClose(privateNotificationManager::removePrivateNotification) .onClose(privateNotificationManager::removePrivateNotification)
.useIUnderstandButton() .useIUnderstandButton()
.show()); .show());
bisqSetup.setDisplaySecurityRecommendationHandler(key -> {}); havenoSetup.setDisplaySecurityRecommendationHandler(key -> {});
bisqSetup.setDisplayLocalhostHandler(key -> { havenoSetup.setDisplayLocalhostHandler(key -> {
if (!DevEnv.isDevMode()) { if (!DevEnv.isDevMode()) {
Popup popup = new Popup().backgroundInfo(Res.get("popup.bitcoinLocalhostNode.msg")) Popup popup = new Popup().backgroundInfo(Res.get("popup.bitcoinLocalhostNode.msg"))
.dontShowAgainId(key); .dontShowAgainId(key);
@ -373,31 +368,31 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
popupQueue.add(popup); popupQueue.add(popup);
} }
}); });
bisqSetup.setDisplaySignedByArbitratorHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( havenoSetup.setDisplaySignedByArbitratorHandler(key -> accountPresentation.showOneTimeAccountSigningPopup(
key, "popup.accountSigning.signedByArbitrator")); key, "popup.accountSigning.signedByArbitrator"));
bisqSetup.setDisplaySignedByPeerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( havenoSetup.setDisplaySignedByPeerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup(
key, "popup.accountSigning.signedByPeer", String.valueOf(SignedWitnessService.SIGNER_AGE_DAYS))); key, "popup.accountSigning.signedByPeer", String.valueOf(SignedWitnessService.SIGNER_AGE_DAYS)));
bisqSetup.setDisplayPeerLimitLiftedHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( havenoSetup.setDisplayPeerLimitLiftedHandler(key -> accountPresentation.showOneTimeAccountSigningPopup(
key, "popup.accountSigning.peerLimitLifted")); key, "popup.accountSigning.peerLimitLifted"));
bisqSetup.setDisplayPeerSignerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup( havenoSetup.setDisplayPeerSignerHandler(key -> accountPresentation.showOneTimeAccountSigningPopup(
key, "popup.accountSigning.peerSigner")); key, "popup.accountSigning.peerSigner"));
bisqSetup.setWrongOSArchitectureHandler(msg -> new Popup().warning(msg).show()); havenoSetup.setWrongOSArchitectureHandler(msg -> new Popup().warning(msg).show());
bisqSetup.setRejectedTxErrorMessageHandler(msg -> new Popup().width(850).warning(msg).show()); havenoSetup.setRejectedTxErrorMessageHandler(msg -> new Popup().width(850).warning(msg).show());
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig); havenoSetup.setShowPopupIfInvalidBtcConfigHandler(this::showPopupIfInvalidBtcConfig);
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> { havenoSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> {
// We copy the array as we will mutate it later // We copy the array as we will mutate it later
showRevolutAccountUpdateWindow(new ArrayList<>(revolutAccountList)); showRevolutAccountUpdateWindow(new ArrayList<>(revolutAccountList));
}); });
bisqSetup.setAmazonGiftCardAccountsUpdateHandler(amazonGiftCardAccountList -> { havenoSetup.setAmazonGiftCardAccountsUpdateHandler(amazonGiftCardAccountList -> {
// We copy the array as we will mutate it later // We copy the array as we will mutate it later
showAmazonGiftCardAccountUpdateWindow(new ArrayList<>(amazonGiftCardAccountList)); showAmazonGiftCardAccountUpdateWindow(new ArrayList<>(amazonGiftCardAccountList));
}); });
bisqSetup.setOsxKeyLoggerWarningHandler(() -> { }); havenoSetup.setOsxKeyLoggerWarningHandler(() -> { });
bisqSetup.setQubesOSInfoHandler(() -> { havenoSetup.setQubesOSInfoHandler(() -> {
String key = "qubesOSSetupInfo"; String key = "qubesOSSetupInfo";
if (preferences.showAgain(key)) { if (preferences.showAgain(key)) {
new Popup().information(Res.get("popup.info.qubesOSSetupInfo")) new Popup().information(Res.get("popup.info.qubesOSSetupInfo"))
@ -407,14 +402,14 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
} }
}); });
bisqSetup.setDownGradePreventionHandler(lastVersion -> { havenoSetup.setDownGradePreventionHandler(lastVersion -> {
new Popup().warning(Res.get("popup.warn.downGradePrevention", lastVersion, Version.VERSION)) new Popup().warning(Res.get("popup.warn.downGradePrevention", lastVersion, Version.VERSION))
.useShutDownButton() .useShutDownButton()
.hideCloseButton() .hideCloseButton()
.show(); .show();
}); });
bisqSetup.setTorAddressUpgradeHandler(() -> new Popup().information(Res.get("popup.info.torMigration.msg")) havenoSetup.setTorAddressUpgradeHandler(() -> new Popup().information(Res.get("popup.info.torMigration.msg"))
.actionButtonTextWithGoTo("navigation.account.backup") .actionButtonTextWithGoTo("navigation.account.backup")
.onAction(() -> { .onAction(() -> {
navigation.setReturnPath(navigation.getCurrentPath()); navigation.setReturnPath(navigation.getCurrentPath());
@ -430,9 +425,9 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
.warning(Res.get("popup.error.takeOfferRequestFailed", errorMessage)) .warning(Res.get("popup.error.takeOfferRequestFailed", errorMessage))
.show()); .show());
bisqSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress()); havenoSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> updateBtcSyncProgress());
bisqSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show()); havenoSetup.setFilterWarningHandler(warning -> new Popup().warning(warning).show());
this.footerVersionInfo.setValue("v" + Version.VERSION); this.footerVersionInfo.setValue("v" + Version.VERSION);
this.getNewVersionAvailableProperty().addListener((observable, oldValue, newValue) -> { this.getNewVersionAvailableProperty().addListener((observable, oldValue, newValue) -> {
@ -538,10 +533,10 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
private void showFirstPopupIfResyncSPVRequested() { private void showFirstPopupIfResyncSPVRequested() {
Popup firstPopup = new Popup(); Popup firstPopup = new Popup();
firstPopup.information(Res.get("settings.net.reSyncSPVAfterRestart")).show(); firstPopup.information(Res.get("settings.net.reSyncSPVAfterRestart")).show();
if (bisqSetup.getBtcSyncProgress().get() == 1) { if (havenoSetup.getBtcSyncProgress().get() == 1) {
showSecondPopupIfResyncSPVRequested(firstPopup); showSecondPopupIfResyncSPVRequested(firstPopup);
} else { } else {
bisqSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> { havenoSetup.getBtcSyncProgress().addListener((observable, oldValue, newValue) -> {
if ((double) newValue == 1) if ((double) newValue == 1)
showSecondPopupIfResyncSPVRequested(firstPopup); showSecondPopupIfResyncSPVRequested(firstPopup);
}); });
@ -596,7 +591,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
} }
private void updateBtcSyncProgress() { private void updateBtcSyncProgress() {
final DoubleProperty btcSyncProgress = bisqSetup.getBtcSyncProgress(); final DoubleProperty btcSyncProgress = havenoSetup.getBtcSyncProgress();
combinedSyncProgress.set(btcSyncProgress.doubleValue()); combinedSyncProgress.set(btcSyncProgress.doubleValue());
} }
@ -635,7 +630,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
BooleanProperty getNewVersionAvailableProperty() { BooleanProperty getNewVersionAvailableProperty() {
return bisqSetup.getNewVersionAvailableProperty(); return havenoSetup.getNewVersionAvailableProperty();
} }
StringProperty getNumOpenSupportTickets() { StringProperty getNumOpenSupportTickets() {
@ -670,7 +665,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
// Wallet // Wallet
StringProperty getBtcInfo() { StringProperty getBtcInfo() {
final StringProperty combinedInfo = new SimpleStringProperty(); final StringProperty combinedInfo = new SimpleStringProperty();
combinedInfo.bind(bisqSetup.getBtcInfo()); combinedInfo.bind(havenoSetup.getBtcInfo());
return combinedInfo; return combinedInfo;
} }
@ -685,44 +680,44 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
} }
StringProperty getWalletServiceErrorMsg() { StringProperty getWalletServiceErrorMsg() {
return bisqSetup.getWalletServiceErrorMsg(); return havenoSetup.getWalletServiceErrorMsg();
} }
StringProperty getBtcSplashSyncIconId() { StringProperty getBtcSplashSyncIconId() {
return bisqSetup.getBtcSplashSyncIconId(); return havenoSetup.getBtcSplashSyncIconId();
} }
BooleanProperty getUseTorForBTC() { BooleanProperty getUseTorForBTC() {
return bisqSetup.getUseTorForBTC(); return havenoSetup.getUseTorForBTC();
} }
// P2P // P2P
StringProperty getP2PNetworkInfo() { StringProperty getP2PNetworkInfo() {
return bisqSetup.getP2PNetworkInfo(); return havenoSetup.getP2PNetworkInfo();
} }
BooleanProperty getSplashP2PNetworkAnimationVisible() { BooleanProperty getSplashP2PNetworkAnimationVisible() {
return bisqSetup.getSplashP2PNetworkAnimationVisible(); return havenoSetup.getSplashP2PNetworkAnimationVisible();
} }
StringProperty getP2pNetworkWarnMsg() { StringProperty getP2pNetworkWarnMsg() {
return bisqSetup.getP2pNetworkWarnMsg(); return havenoSetup.getP2pNetworkWarnMsg();
} }
StringProperty getP2PNetworkIconId() { StringProperty getP2PNetworkIconId() {
return bisqSetup.getP2PNetworkIconId(); return havenoSetup.getP2PNetworkIconId();
} }
StringProperty getP2PNetworkStatusIconId() { StringProperty getP2PNetworkStatusIconId() {
return bisqSetup.getP2PNetworkStatusIconId(); return havenoSetup.getP2PNetworkStatusIconId();
} }
BooleanProperty getUpdatedDataReceived() { BooleanProperty getUpdatedDataReceived() {
return bisqSetup.getUpdatedDataReceived(); return havenoSetup.getUpdatedDataReceived();
} }
StringProperty getP2pNetworkLabelId() { StringProperty getP2pNetworkLabelId() {
return bisqSetup.getP2pNetworkLabelId(); return havenoSetup.getP2pNetworkLabelId();
} }
// marketPricePresentation // marketPricePresentation

View File

@ -27,16 +27,15 @@ import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView; import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView; import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.backup.BackupView; import bisq.desktop.main.account.content.backup.BackupView;
import bisq.desktop.main.account.content.seedwords.SeedWordsView;
import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.Layout; import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.PasswordValidator; import bisq.desktop.util.validation.PasswordValidator;
import bisq.core.api.CoreAccountService;
import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.common.crypto.ScryptUtil;
import bisq.common.util.Tuple4;
import org.bitcoinj.crypto.KeyCrypterScrypt; import bisq.common.util.Tuple4;
import javax.inject.Inject; import javax.inject.Inject;
@ -61,6 +60,7 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
private final WalletsManager walletsManager; private final WalletsManager walletsManager;
private final PasswordValidator passwordValidator; private final PasswordValidator passwordValidator;
private final Navigation navigation; private final Navigation navigation;
private final CoreAccountService accountService;
private PasswordTextField passwordField; private PasswordTextField passwordField;
private PasswordTextField repeatedPasswordField; private PasswordTextField repeatedPasswordField;
@ -77,10 +77,11 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private PasswordView(WalletsManager walletsManager, PasswordValidator passwordValidator, Navigation navigation) { private PasswordView(CoreAccountService accountService, WalletsManager walletsManager, PasswordValidator passwordValidator, Navigation navigation) {
this.walletsManager = walletsManager; this.walletsManager = walletsManager;
this.passwordValidator = passwordValidator; this.passwordValidator = passwordValidator;
this.navigation = navigation; this.navigation = navigation;
this.accountService = accountService;
} }
@Override @Override
@ -141,41 +142,38 @@ public class PasswordView extends ActivatableView<GridPane, Void> {
deriveStatusLabel.setText(Res.get("password.deriveKey")); deriveStatusLabel.setText(Res.get("password.deriveKey"));
busyAnimation.play(); busyAnimation.play();
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt(); if (walletsManager.areWalletsEncrypted()) {
ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> { try {
deriveStatusLabel.setText(""); accountService.changePassword(password, null);
busyAnimation.stop(); new Popup()
.feedback(Res.get("password.walletDecrypted"))
if (walletsManager.areWalletsEncrypted()) { .show();
if (walletsManager.checkAESKey(aesKey)) { backupWalletAndResetFields();
walletsManager.decryptWallets(aesKey); } catch (Throwable t) {
new Popup() pwButton.setDisable(false);
.feedback(Res.get("password.walletDecrypted")) new Popup()
.show(); .warning(Res.get("password.wrongPw"))
backupWalletAndResetFields(); .show();
} else {
pwButton.setDisable(false);
new Popup()
.warning(Res.get("password.wrongPw"))
.show();
}
} else {
try {
walletsManager.encryptWallets(keyCrypterScrypt, aesKey);
new Popup()
.feedback(Res.get("password.walletEncrypted"))
.show();
backupWalletAndResetFields();
walletsManager.clearBackup();
} catch (Throwable t) {
new Popup()
.warning(Res.get("password.walletEncryptionFailed"))
.show();
}
} }
setText(); } else {
updatePasswordListeners(); try {
}); accountService.changePassword(accountService.getPassword(), password);
new Popup()
.feedback(Res.get("password.walletEncrypted"))
.show();
backupWalletAndResetFields();
walletsManager.clearBackup();
} catch (Throwable t) {
new Popup()
.warning(Res.get("password.walletEncryptionFailed"))
.show();
}
}
setText();
updatePasswordListeners();
deriveStatusLabel.setText("");
busyAnimation.stop();
} }
private void backupWalletAndResetFields() { private void backupWalletAndResetFields() {

View File

@ -218,7 +218,7 @@ public class SeedWordsView extends ActivatableView<GridPane, Void> {
} }
private void askForPassword() { private void askForPassword() {
walletPasswordWindow.headLine(Res.get("account.seed.enterPw")).onAesKey(aesKey -> { walletPasswordWindow.headLine(Res.get("account.seed.enterPw")).onSuccess(() -> {
initSeedWords(xmrWalletService.getWallet().getMnemonic()); initSeedWords(xmrWalletService.getWallet().getMnemonic());
showSeedScreen(); showSeedScreen();
}).hideForgotPasswordButton().show(); }).hideForgotPasswordButton().show();

View File

@ -121,14 +121,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
emptyWalletButton.setDisable(!isBalanceSufficient && addressInputTextField.getText().length() > 0); emptyWalletButton.setDisable(!isBalanceSufficient && addressInputTextField.getText().length() > 0);
emptyWalletButton.setOnAction(e -> { emptyWalletButton.setOnAction(e -> {
if (addressInputTextField.getText().length() > 0 && isBalanceSufficient) { if (addressInputTextField.getText().length() > 0 && isBalanceSufficient) {
if (btcWalletService.isEncrypted()) { log.warn(getClass().getSimpleName() + ".addContent() needs updated for XMR");
walletPasswordWindow
.onAesKey(this::doEmptyWallet)
.onClose(this::blurAgain)
.show();
} else {
doEmptyWallet(null);
}
} }
}); });

View File

@ -29,7 +29,6 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload; import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.support.dispute.agent.DisputeAgentLookupMap;
import bisq.core.support.dispute.arbitration.ArbitrationManager; import bisq.core.support.dispute.arbitration.ArbitrationManager;
import bisq.core.trade.Contract; import bisq.core.trade.Contract;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;

View File

@ -25,18 +25,15 @@ import bisq.desktop.main.SharedPresentation;
import bisq.desktop.main.overlays.Overlay; import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.Layout; import bisq.desktop.util.Layout;
import bisq.desktop.util.Transitions; import bisq.core.api.CoreAccountService;
import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsManager;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.OpenOfferManager; import bisq.core.offer.OpenOfferManager;
import bisq.common.UserThread;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.ScryptUtil; import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.crypto.MnemonicCode; import org.bitcoinj.crypto.MnemonicCode;
import org.bitcoinj.crypto.MnemonicException; import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.wallet.DeterministicSeed; import org.bitcoinj.wallet.DeterministicSeed;
@ -65,8 +62,6 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import org.bouncycastle.crypto.params.KeyParameter;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
@ -75,8 +70,6 @@ import java.time.ZoneOffset;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.desktop.util.FormBuilder.addPasswordTextField; import static bisq.desktop.util.FormBuilder.addPasswordTextField;
@ -88,12 +81,13 @@ import static javafx.beans.binding.Bindings.createBooleanBinding;
@Slf4j @Slf4j
public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> { public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
private final CoreAccountService accountService;
private final WalletsManager walletsManager; private final WalletsManager walletsManager;
private final OpenOfferManager openOfferManager; private final OpenOfferManager openOfferManager;
private File storageDir; private File storageDir;
private Button unlockButton; private Button unlockButton;
private AesKeyHandler aesKeyHandler; private WalletPasswordHandler passwordHandler;
private PasswordTextField passwordTextField; private PasswordTextField passwordTextField;
private Button forgotPasswordButton; private Button forgotPasswordButton;
private Button restoreButton; private Button restoreButton;
@ -111,14 +105,16 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
// Interface // Interface
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public interface AesKeyHandler { public interface WalletPasswordHandler {
void onAesKey(KeyParameter aesKey); void onSuccess();
} }
@Inject @Inject
private WalletPasswordWindow(WalletsManager walletsManager, private WalletPasswordWindow(CoreAccountService accountService,
WalletsManager walletsManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
@Named(Config.STORAGE_DIR) File storageDir) { @Named(Config.STORAGE_DIR) File storageDir) {
this.accountService = accountService;
this.walletsManager = walletsManager; this.walletsManager = walletsManager;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
this.storageDir = storageDir; this.storageDir = storageDir;
@ -149,8 +145,8 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
display(); display();
} }
public WalletPasswordWindow onAesKey(AesKeyHandler aesKeyHandler) { public WalletPasswordWindow onSuccess(WalletPasswordHandler passwordHandler) {
this.aesKeyHandler = aesKeyHandler; this.passwordHandler = passwordHandler;
return this; return this;
} }
@ -213,27 +209,16 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
unlockButton.setOnAction(e -> { unlockButton.setOnAction(e -> {
String password = passwordTextField.getText(); String password = passwordTextField.getText();
checkArgument(password.length() < 500, Res.get("password.tooLong")); checkArgument(password.length() < 500, Res.get("password.tooLong"));
KeyCrypterScrypt keyCrypterScrypt = walletsManager.getKeyCrypterScrypt(); try {
if (keyCrypterScrypt != null) { accountService.verifyPassword(password);
busyAnimation.play(); if (passwordHandler != null) passwordHandler.onSuccess();
deriveStatusLabel.setText(Res.get("password.deriveKey")); hide();
ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> { } catch (IncorrectPasswordException e2) {
if (walletsManager.checkAESKey(aesKey)) { busyAnimation.stop();
if (aesKeyHandler != null) deriveStatusLabel.setText("");
aesKeyHandler.onAesKey(aesKey); new Popup()
.warning(Res.get("password.wrongPw"))
hide(); .onClose(this::blurAgain).show();
} else {
busyAnimation.stop();
deriveStatusLabel.setText("");
UserThread.runAfter(() -> new Popup()
.warning(Res.get("password.wrongPw"))
.onClose(this::blurAgain).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS);
}
});
} else {
log.error("wallet.getKeyCrypter() is null, that must not happen.");
} }
}); });

View File

@ -70,6 +70,10 @@ public class ImageUtil {
} }
} }
public static Image getImageByPath(String imagePath) {
return getImageByUrl("/images/" + imagePath);
}
// determine if this is a MacOS retina display // determine if this is a MacOS retina display
// https://stackoverflow.com/questions/20767708/how-do-you-detect-a-retina-display-in-java#20767802 // https://stackoverflow.com/questions/20767708/how-do-you-detect-a-retina-display-in-java#20767802
public static boolean isRetina() { public static boolean isRetina() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB