Add API functions to initialize Haveno account (#216)

Co-authored-by: woodser@protonmail.com
This commit is contained in:
duriancrepe 2022-02-09 01:39:57 -08:00 committed by GitHub
parent dc4692d97a
commit e3b9a9962b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 2755 additions and 1660 deletions

View File

@ -51,17 +51,6 @@ arbitrator-desktop:
--apiPassword=apitest \ --apiPassword=apitest \
--apiPort=9998 --apiPort=9998
arbitrator-daemon:
# Arbitrator and mediator need to be registerd in the UI before launching the daemon.
./haveno-daemon \
--baseCurrencyNetwork=XMR_STAGENET \
--useLocalhostForP2P=true \
--useDevPrivilegeKeys=true \
--nodePort=4444 \
--appName=haveno-XMR_STAGENET_arbitrator \
--apiPassword=apitest \
--apiPort=9998
arbitrator-desktop2: arbitrator-desktop2:
# Arbitrator and mediator need to be registerd in the UI after launching it. # Arbitrator and mediator need to be registerd in the UI after launching it.
./haveno-desktop \ ./haveno-desktop \
@ -73,6 +62,18 @@ arbitrator-desktop2:
--apiPassword=apitest \ --apiPassword=apitest \
--apiPort=10001 --apiPort=10001
arbitrator-daemon:
# Arbitrator and mediator need to be registerd in the UI before launching the daemon!
./haveno-daemon \
--baseCurrencyNetwork=XMR_STAGENET \
--useLocalhostForP2P=true \
--useDevPrivilegeKeys=true \
--nodePort=4444 \
--appName=haveno-XMR_STAGENET_arbitrator \
--apiPassword=apitest \
--apiPort=9998 \
--passwordRequired=false
alice-desktop: alice-desktop:
./haveno-desktop \ ./haveno-desktop \
--baseCurrencyNetwork=XMR_STAGENET \ --baseCurrencyNetwork=XMR_STAGENET \
@ -93,7 +94,8 @@ alice-daemon:
--appName=haveno-XMR_STAGENET_Alice \ --appName=haveno-XMR_STAGENET_Alice \
--apiPassword=apitest \ --apiPassword=apitest \
--apiPort=9999 \ --apiPort=9999 \
--walletRpcBindPort=38091 --walletRpcBindPort=38091 \
--passwordRequired=false
bob-desktop: bob-desktop:
./haveno-desktop \ ./haveno-desktop \
@ -115,7 +117,8 @@ bob-daemon:
--appName=haveno-XMR_STAGENET_Bob \ --appName=haveno-XMR_STAGENET_Bob \
--apiPassword=apitest \ --apiPassword=apitest \
--apiPort=10000 \ --apiPort=10000 \
--walletRpcBindPort=38092 --walletRpcBindPort=38092 \
--passwordRequired=false
monero-shared: monero-shared:
./.localnet/monerod \ ./.localnet/monerod \

View File

@ -114,6 +114,7 @@ public class Config {
public static final String BTC_MIN_TX_FEE = "btcMinTxFee"; public static final String BTC_MIN_TX_FEE = "btcMinTxFee";
public static final String BTC_FEES_TS = "bitcoinFeesTs"; public static final String BTC_FEES_TS = "bitcoinFeesTs";
public static final String BYPASS_MEMPOOL_VALIDATION = "bypassMempoolValidation"; public static final String BYPASS_MEMPOOL_VALIDATION = "bypassMempoolValidation";
public static final String PASSWORD_REQUIRED = "passwordRequired";
// Default values for certain options // Default values for certain options
public static final int UNSPECIFIED_PORT = -1; public static final int UNSPECIFIED_PORT = -1;
@ -190,6 +191,7 @@ public class Config {
public final boolean preventPeriodicShutdownAtSeedNode; public final boolean preventPeriodicShutdownAtSeedNode;
public final boolean republishMailboxEntries; public final boolean republishMailboxEntries;
public final boolean bypassMempoolValidation; public final boolean bypassMempoolValidation;
public final boolean passwordRequired;
// Properties derived from options but not exposed as options themselves // Properties derived from options but not exposed as options themselves
public final File torDir; public final File torDir;
@ -579,6 +581,13 @@ public class Config {
.ofType(boolean.class) .ofType(boolean.class)
.defaultsTo(false); .defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> passwordRequiredOpt =
parser.accepts(PASSWORD_REQUIRED,
"Requires a password for creating a Haveno account")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
try { try {
CompositeOptionSet options = new CompositeOptionSet(); CompositeOptionSet options = new CompositeOptionSet();
@ -686,6 +695,7 @@ public class Config {
this.preventPeriodicShutdownAtSeedNode = options.valueOf(preventPeriodicShutdownAtSeedNodeOpt); this.preventPeriodicShutdownAtSeedNode = options.valueOf(preventPeriodicShutdownAtSeedNodeOpt);
this.republishMailboxEntries = options.valueOf(republishMailboxEntriesOpt); this.republishMailboxEntries = options.valueOf(republishMailboxEntriesOpt);
this.bypassMempoolValidation = options.valueOf(bypassMempoolValidationOpt); this.bypassMempoolValidation = options.valueOf(bypassMempoolValidationOpt);
this.passwordRequired = options.valueOf(passwordRequiredOpt);
} catch (OptionException ex) { } catch (OptionException ex) {
throw new ConfigException("problem parsing option '%s': %s", throw new ConfigException("problem parsing option '%s': %s",
ex.options().get(0), ex.options().get(0),

View File

@ -54,11 +54,13 @@ public class Encryption {
public static final String ASYM_KEY_ALGO = "RSA"; public static final String ASYM_KEY_ALGO = "RSA";
private static final String ASYM_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1PADDING"; private static final String ASYM_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1PADDING";
private static final String SYM_KEY_ALGO = "AES"; public static final String SYM_KEY_ALGO = "AES";
private static final String SYM_CIPHER = "AES"; private static final String SYM_CIPHER = "AES";
private static final String HMAC = "HmacSHA256"; private static final String HMAC = "HmacSHA256";
public static final String HMAC_ERROR_MSG = "Hmac does not match.";
public static KeyPair generateKeyPair() { public static KeyPair generateKeyPair() {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
try { try {
@ -101,11 +103,6 @@ public class Encryption {
return new SecretKeySpec(secretKeyBytes, 0, secretKeyBytes.length, SYM_KEY_ALGO); return new SecretKeySpec(secretKeyBytes, 0, secretKeyBytes.length, SYM_KEY_ALGO);
} }
public static byte[] getSecretKeyBytes(SecretKey secretKey) {
return secretKey.getEncoded();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Hmac // Hmac
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -179,7 +176,7 @@ public class Encryption {
if (verifyHmac(Hex.decode(payloadAsHex), Hex.decode(hmacAsHex), secretKey)) { if (verifyHmac(Hex.decode(payloadAsHex), Hex.decode(hmacAsHex), secretKey)) {
return Hex.decode(payloadAsHex); return Hex.decode(payloadAsHex);
} else { } else {
throw new CryptoException("Hmac does not match."); throw new CryptoException(HMAC_ERROR_MSG);
} }
} }

View File

@ -0,0 +1,23 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.common.crypto;
public class IncorrectPasswordException extends Exception {
public IncorrectPasswordException(String message) {
super(message);
}
}

View File

@ -26,27 +26,93 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Getter @Getter
@EqualsAndHashCode @EqualsAndHashCode
@Slf4j @Slf4j
@Singleton @Singleton
public final class KeyRing { public final class KeyRing {
private final KeyPair signatureKeyPair;
private final KeyPair encryptionKeyPair;
private final PubKeyRing pubKeyRing;
private final KeyStorage keyStorage;
private KeyPair signatureKeyPair;
private KeyPair encryptionKeyPair;
private PubKeyRing pubKeyRing;
/**
* Creates the KeyRing. Unlocks if not encrypted. Does not generate keys.
*
* @param keyStorage Persisted storage
*/
@Inject @Inject
public KeyRing(KeyStorage keyStorage) { public KeyRing(KeyStorage keyStorage) {
this(keyStorage, null, false);
}
/**
* Creates KeyRing with a password. Attempts to generate keys if they don't exist.
*
* @param keyStorage Persisted storage
* @param password The password to unlock the keys or to generate new keys, nullable.
* @param generateKeys Generate new keys with password if not created yet.
*/
public KeyRing(KeyStorage keyStorage, String password, boolean generateKeys) {
this.keyStorage = keyStorage;
try {
unlockKeys(password, generateKeys);
} catch(IncorrectPasswordException ex) {
// no action
}
}
public boolean isUnlocked() {
boolean isUnlocked = this.signatureKeyPair != null
&& this.encryptionKeyPair != null
&& this.pubKeyRing != null;
return isUnlocked;
}
/**
* Locks the keyring disabling access to the keys until unlock is called.
* If the keys are never persisted then the keys are lost and will be regenerated.
*/
public void lockKeys() {
signatureKeyPair = null;
encryptionKeyPair = null;
pubKeyRing = null;
}
/**
* Unlocks the keyring with a given password if required. If the keyring is already
* unlocked, do nothing.
*
* @param password Decrypts the or encrypts newly generated keys with the given password.
* @return Whether KeyRing is unlocked
*/
public boolean unlockKeys(@Nullable String password, boolean generateKeys) throws IncorrectPasswordException {
if (isUnlocked()) return true;
if (keyStorage.allKeyFilesExist()) { if (keyStorage.allKeyFilesExist()) {
signatureKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_SIGNATURE); signatureKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_SIGNATURE, password);
encryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_ENCRYPTION); encryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_ENCRYPTION, password);
} else { if (signatureKeyPair != null && encryptionKeyPair != null) pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic());
// First time we create key pairs } else if (generateKeys) {
generateKeys(password);
}
return isUnlocked();
}
/**
* Generates a new set of keys if the current keyring is closed.
*
* @param password The password to unlock the keys or to generate new keys, nullable.
*/
public void generateKeys(String password) {
if (isUnlocked()) throw new Error("Current keyring must be closed to generate new keys");
signatureKeyPair = Sig.generateKeyPair(); signatureKeyPair = Sig.generateKeyPair();
encryptionKeyPair = Encryption.generateKeyPair(); encryptionKeyPair = Encryption.generateKeyPair();
keyStorage.saveKeyRing(this);
}
pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic()); pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic());
keyStorage.saveKeyRing(this, password);
} }
// Don't print keys for security reasons // Don't print keys for security reasons

View File

@ -20,11 +20,19 @@ package bisq.common.crypto;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.file.FileUtil; import bisq.common.file.FileUtil;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import com.google.inject.Inject; import com.google.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.bouncycastle.crypto.params.KeyParameter;
import javax.crypto.BadPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -39,6 +47,9 @@ import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec; import java.security.spec.RSAPublicKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -46,6 +57,7 @@ import java.io.IOException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -58,6 +70,11 @@ import static bisq.common.util.Preconditions.checkDir;
@Singleton @Singleton
public class KeyStorage { public class KeyStorage {
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class); private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);
private static final int SALT_LENGTH = 20;
private static final byte[] ENCRYPTED_FORMAT_MAGIC = "HVNENC".getBytes(StandardCharsets.UTF_8);
private static final int ENCRYPTED_FORMAT_VERSION = 1;
private static final int ENCRYPTED_FORMAT_LENGTH = 4*2; // version,salt
public enum KeyEntry { public enum KeyEntry {
MSG_SIGNATURE("sig", Sig.KEY_ALGO), MSG_SIGNATURE("sig", Sig.KEY_ALGO),
@ -104,7 +121,7 @@ public class KeyStorage {
return new File(storageDir + "/" + keyEntry.getFileName() + ".key").exists(); return new File(storageDir + "/" + keyEntry.getFileName() + ".key").exists();
} }
public KeyPair loadKeyPair(KeyEntry keyEntry) { public KeyPair loadKeyPair(KeyEntry keyEntry, String password) throws IncorrectPasswordException {
FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20); FileUtil.rollingBackup(storageDir, keyEntry.getFileName() + ".key", 20);
// long now = System.currentTimeMillis(); // long now = System.currentTimeMillis();
try { try {
@ -118,9 +135,53 @@ public class KeyStorage {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
fis.read(encodedPrivateKey); fis.read(encodedPrivateKey);
// Read magic bytes
byte[] magicBytes = Arrays.copyOfRange(encodedPrivateKey, 0, ENCRYPTED_FORMAT_MAGIC.length);
boolean isEncryptedPassword = Arrays.compare(magicBytes, ENCRYPTED_FORMAT_MAGIC) == 0;
if (isEncryptedPassword && password == null) {
throw new IncorrectPasswordException("Cannot load encrypted keys, user must open account with password " + filePrivateKey);
} else if (password != null && !isEncryptedPassword) {
log.warn("Password not needed for unencrypted key " + filePrivateKey);
}
// Decrypt using password
if (password != null) {
int position = ENCRYPTED_FORMAT_MAGIC.length;
// Read remaining header
ByteBuffer buf = ByteBuffer.wrap(encodedPrivateKey, position, ENCRYPTED_FORMAT_LENGTH);
position += ENCRYPTED_FORMAT_LENGTH;
int version = buf.getInt();
if (version != 1) throw new RuntimeException("Unable to parse encrypted keys");
int saltLength = buf.getInt();
// Read salt
byte[] salt = Arrays.copyOfRange(encodedPrivateKey, position, position + saltLength);
position += saltLength;
// Payload key derived from password
KeyCrypterScrypt crypter = ScryptUtil.getKeyCrypterScrypt(salt);
KeyParameter pwKey = ScryptUtil.deriveKeyWithScrypt(crypter, password);
SecretKey secretKey = new SecretKeySpec(pwKey.getKey(), Encryption.SYM_KEY_ALGO);
byte[] encryptedPayload = Arrays.copyOfRange(encodedPrivateKey, position, encodedPrivateKey.length);
// Decrypt key, handling exceptions caused by an incorrect password key
try {
encodedPrivateKey = Encryption.decryptPayloadWithHmac(encryptedPayload, secretKey);
} catch (CryptoException ce) {
// Most of the time (probably of slightly less than 255/256, around 99.61%) a bad password
// will result in BadPaddingException before HMAC check.
// See https://stackoverflow.com/questions/8049872/given-final-block-not-properly-padded
if (ce.getCause() instanceof BadPaddingException || ce.getMessage() == Encryption.HMAC_ERROR_MSG)
throw new IncorrectPasswordException("Incorrect password");
else
throw ce;
}
}
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
privateKey = keyFactory.generatePrivate(privateKeySpec); privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException | IOException e) { } catch (InvalidKeySpecException | IOException | CryptoException e) {
log.error("Could not load key " + keyEntry.toString(), e.getMessage()); log.error("Could not load key " + keyEntry.toString(), e.getMessage());
throw new RuntimeException("Could not load key " + keyEntry.toString(), e); throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
} }
@ -150,20 +211,44 @@ public class KeyStorage {
} }
} }
public void saveKeyRing(KeyRing keyRing) { public void saveKeyRing(KeyRing keyRing, String password) {
savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName()); savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName(), password);
savePrivateKey(keyRing.getEncryptionKeyPair().getPrivate(), KeyEntry.MSG_ENCRYPTION.getFileName()); savePrivateKey(keyRing.getEncryptionKeyPair().getPrivate(), KeyEntry.MSG_ENCRYPTION.getFileName(), password);
} }
private void savePrivateKey(PrivateKey privateKey, String name) { private void savePrivateKey(PrivateKey privateKey, String name, String password) {
if (!storageDir.exists()) if (!storageDir.exists())
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
storageDir.mkdir(); storageDir.mkdirs();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) { try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) {
fos.write(pkcs8EncodedKeySpec.getEncoded()); byte[] keyBytes = pkcs8EncodedKeySpec.getEncoded();
} catch (IOException e) { // Encrypt
if (password != null) {
// Magic
fos.write(ENCRYPTED_FORMAT_MAGIC);
// Version, salt length
ByteBuffer header = ByteBuffer.allocate(ENCRYPTED_FORMAT_LENGTH);
header.putInt(ENCRYPTED_FORMAT_VERSION);
header.putInt(SALT_LENGTH);
fos.write(header.array());
// Salt value
byte[] salt = CryptoUtils.getRandomBytes(SALT_LENGTH);
fos.write(salt);
// Generate secret from password key and salt
KeyCrypterScrypt crypter = ScryptUtil.getKeyCrypterScrypt(salt);
KeyParameter pwKey = ScryptUtil.deriveKeyWithScrypt(crypter, password);
SecretKey secretKey = new SecretKeySpec(pwKey.getKey(), Encryption.SYM_KEY_ALGO);
// Encrypt payload
keyBytes = Encryption.encryptPayloadWithHmac(keyBytes, secretKey);
}
fos.write(keyBytes);
} catch (Exception e) {
log.error("Could not save key " + name, e); log.error("Could not save key " + name, e);
throw new RuntimeException("Could not save key " + name, e); throw new RuntimeException("Could not save key " + name, e);
} }

View File

@ -3,17 +3,22 @@ package bisq.common.crypto;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
/**
* Allows User's static PubKeyRing to be injected into constructors without having to
* open the account yet. Once its opened, PubKeyRingProvider will return non-null PubKeyRing.
* Originally used via bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
*/
public class PubKeyRingProvider implements Provider<PubKeyRing> { public class PubKeyRingProvider implements Provider<PubKeyRing> {
private final PubKeyRing pubKeyRing; private final KeyRing keyRing;
@Inject @Inject
public PubKeyRingProvider(KeyRing keyRing) { public PubKeyRingProvider(KeyRing keyRing) {
pubKeyRing = keyRing.getPubKeyRing(); this.keyRing = keyRing;
} }
@Override @Override
public PubKeyRing get() { public PubKeyRing get() {
return pubKeyRing; return keyRing.getPubKeyRing();
} }
} }

View File

@ -15,7 +15,7 @@
* along with Haveno. If not, see <http://www.gnu.org/licenses/>. * along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/ */
package bisq.core.crypto; package bisq.common.crypto;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
@ -52,14 +52,25 @@ public class ScryptUtil {
return new KeyCrypterScrypt(scryptParameters); return new KeyCrypterScrypt(scryptParameters);
} }
public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, DeriveKeyResultHandler resultHandler) { public static KeyParameter deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password) {
Utilities.getThreadPoolExecutor("ScryptUtil:deriveKeyWithScrypt-%d", 1, 2, 5L).submit(() -> {
try { try {
log.debug("Doing key derivation"); log.debug("Doing key derivation");
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password); KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
long duration = System.currentTimeMillis() - start; long duration = System.currentTimeMillis() - start;
log.debug("Key derivation took {} msec", duration); log.debug("Key derivation took {} msec", duration);
return aesKey;
} catch (Throwable t) {
t.printStackTrace();
log.error("Key derivation failed. " + t.getMessage());
throw t;
}
}
public static void deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password, DeriveKeyResultHandler resultHandler) {
Utilities.getThreadPoolExecutor("ScryptUtil:deriveKeyWithScrypt-%d", 1, 2, 5L).submit(() -> {
try {
KeyParameter aesKey = deriveKeyWithScrypt(keyCrypterScrypt, password);
UserThread.execute(() -> { UserThread.execute(() -> {
try { try {
resultHandler.handleResult(aesKey); resultHandler.handleResult(aesKey);

View File

@ -114,7 +114,6 @@ public class PersistenceManager<T extends PersistableEnvelope> {
return; return;
} }
// We don't know from which thread we are called so we map to user thread // We don't know from which thread we are called so we map to user thread
UserThread.execute(() -> { UserThread.execute(() -> {
if (doShutdown) { if (doShutdown) {
@ -382,6 +381,11 @@ public class PersistenceManager<T extends PersistableEnvelope> {
return; return;
} }
if (!initCalled.get()) {
log.warn("requestPersistence() called before init. Ignoring request");
return;
}
persistenceRequested = true; persistenceRequested = true;
// If we have not initialized yet we postpone the start of the timer and call maybeStartTimerForPersistence at // If we have not initialized yet we postpone the start of the timer and call maybeStartTimerForPersistence at

View File

@ -0,0 +1,128 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.common.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ZipUtils {
/**
* Zips directory into the output stream. Empty directories are not included.
*
* @param dir The directory to create the zip from.
* @param out The stream to write to.
*/
public static void zipDirToStream(File dir, OutputStream out, int bufferSize) throws Exception {
// Get all files in directory and subdirectories.
ArrayList<String> fileList = new ArrayList<>();
getFilesRecursive(dir, fileList);
try (ZipOutputStream zos = new ZipOutputStream(out)) {
for (String filePath : fileList) {
log.info("Compressing: " + filePath);
// Creates a zip entry.
String name = filePath.substring(dir.getAbsolutePath().length() + 1);
ZipEntry zipEntry = new ZipEntry(name);
zos.putNextEntry(zipEntry);
// Read file content and write to zip output stream.
try (FileInputStream fis = new FileInputStream(filePath)) {
byte[] buffer = new byte[bufferSize];
int length;
while ((length = fis.read(buffer)) > 0) {
zos.write(buffer, 0, length);
}
// Close the zip entry.
zos.closeEntry();
}
}
}
}
/**
* Get files list from the directory recursive to the subdirectory.
*/
public static void getFilesRecursive(File directory, List<String> fileList) {
File[] files = directory.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
if (file.isFile()) {
fileList.add(file.getAbsolutePath());
} else {
getFilesRecursive(file, fileList);
}
}
}
}
/**
* Unzips the zipStream into the specified directory, overwriting any files.
* Existing files are preserved.
*
* @param dir The directory to write to.
* @param inputStream The raw stream assumed to be in zip format.
* @param bufferSize The buffer used to read from efficiently.
*/
public static void unzipToDir(File dir, InputStream inputStream, int bufferSize) throws Exception {
try (ZipInputStream zipStream = new ZipInputStream(inputStream)) {
ZipEntry entry;
byte[] buffer = new byte[bufferSize];
int count;
while ((entry = zipStream.getNextEntry()) != null) {
File file = new File(dir, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
// Make sure folder exists.
file.getParentFile().mkdirs();
log.info("Unzipped file: " + file.getAbsolutePath());
// Don't overwrite the current logs
if ("bisq.log".equals(file.getName())) {
file = new File(file.getParent() + "/" + "bisq.backup.log");
log.info("Unzipped logfile to backup path: " + file.getAbsolutePath());
}
try (FileOutputStream fileOutput = new FileOutputStream(file)) {
while ((count = zipStream.read(buffer)) != -1) {
fileOutput.write(buffer, 0, count);
}
}
}
zipStream.closeEntry();
}
}
}
}

View File

@ -0,0 +1,14 @@
package bisq.core.api;
/**
* Default account listener (takes no action).
*/
public class AccountServiceListener {
public void onAppInitialized() {}
public void onAccountCreated() {}
public void onAccountOpened() {}
public void onAccountClosed() {}
public void onAccountRestored(Runnable onShutDown) {}
public void onAccountDeleted(Runnable onShutDown) {}
public void onPasswordChanged(String oldPassword, String newPassword) {}
}

View File

@ -1,50 +1,163 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.api; package bisq.core.api;
import javax.inject.Singleton; import static com.google.common.base.Preconditions.checkState;
import bisq.common.config.Config;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import bisq.common.file.FileUtil;
import bisq.common.persistence.PersistenceManager;
import bisq.common.util.ZipUtils;
import java.io.File;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.function.Consumer;
import java.util.concurrent.CopyOnWriteArrayList; import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/** /**
* @deprecated Should be replaced by actual implementation once it is available * Manages the account state. A created account must have a password which encrypts
* all persistence in the PersistenceManager. As a result, opening the account requires
* a correct password to be passed in to deserialize the account properties that are
* persisted. It is possible to persist the objects without a password (legacy).
*
* Backup and restore flushes the persistence objects in the app folder and sends or
* restores a zip stream.
*/ */
@Singleton @Singleton
@Deprecated @Slf4j
public class CoreAccountService { public class CoreAccountService {
private final Config config;
private final KeyStorage keyStorage;
private final KeyRing keyRing;
private static final String DEFAULT_PASSWORD = "abctesting123"; @Getter
private String password;
private List<AccountServiceListener> listeners = new ArrayList<AccountServiceListener>();
private String password = DEFAULT_PASSWORD; @Inject
public CoreAccountService(Config config,
private final List<PasswordChangeListener> listeners = new CopyOnWriteArrayList<>(); KeyStorage keyStorage,
KeyRing keyRing) {
this.config = config;
public String getPassword() { this.keyStorage = keyStorage;
return password; this.keyRing = keyRing;
} }
public void setPassword(String newPassword) { public void addListener(AccountServiceListener listener) {
String oldPassword = password;
password = newPassword;
notifyListenerAboutPasswordChange(oldPassword, newPassword);
}
public void addPasswordChangeListener(PasswordChangeListener listener) {
Objects.requireNonNull(listener, "listener");
listeners.add(listener); listeners.add(listener);
} }
private void notifyListenerAboutPasswordChange(String oldPassword, String newPassword) { public boolean removeListener(AccountServiceListener listener) {
for (PasswordChangeListener listener : listeners) { return listeners.remove(listener);
listener.onPasswordChange(oldPassword, newPassword); }
public boolean accountExists() {
return keyStorage.allKeyFilesExist(); // public and private key pair indicate the existence of the account
}
public boolean isAccountOpen() {
return keyRing.isUnlocked() && accountExists();
}
public void checkAccountOpen() {
checkState(isAccountOpen(), "Account not open");
}
public void createAccount(String password) {
if (accountExists()) throw new IllegalStateException("Cannot create account if account already exists");
keyRing.generateKeys(password);
this.password = password;
for (AccountServiceListener listener : listeners) listener.onAccountCreated();
}
public void openAccount(String password) throws IncorrectPasswordException {
if (!accountExists()) throw new IllegalStateException("Cannot open account if account does not exist");
if (keyRing.unlockKeys(password, false)) {
this.password = password;
for (AccountServiceListener listener : listeners) listener.onAccountOpened();
} else {
throw new IllegalStateException("keyRing.unlockKeys() returned false, that should never happen");
} }
} }
public interface PasswordChangeListener { public void changePassword(String password) {
if (!isAccountOpen()) throw new IllegalStateException("Cannot change password on unopened account");
keyStorage.saveKeyRing(keyRing, password);
String oldPassword = this.password;
this.password = password;
for (AccountServiceListener listener : listeners) listener.onPasswordChanged(oldPassword, password);
}
void onPasswordChange(String oldPassword, String newPassword); public void closeAccount() {
if (!isAccountOpen()) throw new IllegalStateException("Cannot close unopened account");
keyRing.lockKeys(); // closed account means the keys are locked
for (AccountServiceListener listener : listeners) listener.onAccountClosed();
}
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
if (!accountExists()) throw new IllegalStateException("Cannot backup non existing account");
// flush all known persistence objects to disk
PersistenceManager.flushAllDataToDiskAtBackup(() -> {
try {
File dataDir = new File(config.appDataDir.getPath());
PipedInputStream in = new PipedInputStream(bufferSize); // pipe the serialized account object to stream which will be read by the consumer
PipedOutputStream out = new PipedOutputStream(in);
log.info("Zipping directory " + dataDir);
new Thread(() -> {
try {
ZipUtils.zipDirToStream(dataDir, out, bufferSize);
} catch (Exception ex) {
error.accept(ex);
}
}).start();
consume.accept(in);
} catch (java.io.IOException err) {
error.accept(err);
}
});
}
public void restoreAccount(InputStream inputStream, int bufferSize, Runnable onShutdown) throws Exception {
if (accountExists()) throw new IllegalStateException("Cannot restore account if there is an existing account");
File dataDir = new File(config.appDataDir.getPath());
ZipUtils.unzipToDir(dataDir, inputStream, bufferSize);
for (AccountServiceListener listener : listeners) listener.onAccountRestored(onShutdown);
}
public void deleteAccount(Runnable onShutdown) {
try {
keyRing.lockKeys();
for (AccountServiceListener listener : listeners) listener.onAccountDeleted(onShutdown);
File dataDir = new File(config.appDataDir.getPath()); // TODO (woodser): deleting directory after gracefulShutdown() so services don't throw when they try to persist (e.g. XmrTxProofService), but gracefulShutdown() should honor read-only shutdown
FileUtil.deleteDirectory(dataDir, null, false);
} catch (Exception err) {
throw new RuntimeException(err);
}
} }
} }

View File

@ -21,6 +21,7 @@ import bisq.core.api.model.AddressBalanceInfo;
import bisq.core.api.model.BalancesInfo; import bisq.core.api.model.BalancesInfo;
import bisq.core.api.model.MarketPriceInfo; import bisq.core.api.model.MarketPriceInfo;
import bisq.core.api.model.TxFeeRateInfo; import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.app.AppStartupState;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
@ -33,6 +34,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
@ -46,6 +48,8 @@ import javax.inject.Singleton;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -58,7 +62,6 @@ import lombok.NonNull;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.wallet.model.MoneroDestination; import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
@ -73,6 +76,8 @@ public class CoreApi {
@Getter @Getter
private final Config config; private final Config config;
private final AppStartupState appStartupState;
private final CoreAccountService coreAccountService;
private final CoreDisputeAgentsService coreDisputeAgentsService; private final CoreDisputeAgentsService coreDisputeAgentsService;
private final CoreHelpService coreHelpService; private final CoreHelpService coreHelpService;
private final CoreOffersService coreOffersService; private final CoreOffersService coreOffersService;
@ -86,6 +91,8 @@ public class CoreApi {
@Inject @Inject
public CoreApi(Config config, public CoreApi(Config config,
AppStartupState appStartupState,
CoreAccountService coreAccountService,
CoreDisputeAgentsService coreDisputeAgentsService, CoreDisputeAgentsService coreDisputeAgentsService,
CoreHelpService coreHelpService, CoreHelpService coreHelpService,
CoreOffersService coreOffersService, CoreOffersService coreOffersService,
@ -97,6 +104,8 @@ public class CoreApi {
CoreNotificationService notificationService, CoreNotificationService notificationService,
CoreMoneroConnectionsService coreMoneroConnectionsService) { CoreMoneroConnectionsService coreMoneroConnectionsService) {
this.config = config; this.config = config;
this.appStartupState = appStartupState;
this.coreAccountService = coreAccountService;
this.coreDisputeAgentsService = coreDisputeAgentsService; this.coreDisputeAgentsService = coreDisputeAgentsService;
this.coreHelpService = coreHelpService; this.coreHelpService = coreHelpService;
this.coreOffersService = coreOffersService; this.coreOffersService = coreOffersService;
@ -115,11 +124,197 @@ public class CoreApi {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Dispute Agents // Help
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void registerDisputeAgent(String disputeAgentType, String registrationKey) { public String getMethodHelp(String methodName) {
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey); return coreHelpService.getMethodHelp(methodName);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Account Service
///////////////////////////////////////////////////////////////////////////////////////////
public boolean accountExists() {
return coreAccountService.accountExists();
}
public boolean isAccountOpen() {
return coreAccountService.isAccountOpen();
}
public void createAccount(String password) {
coreAccountService.createAccount(password);
}
public void openAccount(String password) throws IncorrectPasswordException {
coreAccountService.openAccount(password);
}
public boolean isAppInitialized() {
return appStartupState.isApplicationFullyInitialized();
}
public void changePassword(String password) {
coreAccountService.changePassword(password);
}
public void closeAccount() {
coreAccountService.closeAccount();
}
public void deleteAccount(Runnable onShutdown) {
coreAccountService.deleteAccount(onShutdown);
}
public void backupAccount(int bufferSize, Consumer<InputStream> consume, Consumer<Exception> error) {
coreAccountService.backupAccount(bufferSize, consume, error);
}
public void restoreAccount(InputStream zipStream, int bufferSize, Runnable onShutdown) throws Exception {
coreAccountService.restoreAccount(zipStream, bufferSize, onShutdown);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Monero Connections
///////////////////////////////////////////////////////////////////////////////////////////
public void addMoneroConnection(MoneroRpcConnection connection) {
coreMoneroConnectionsService.addConnection(connection);
}
public void removeMoneroConnection(String connectionUri) {
coreMoneroConnectionsService.removeConnection(connectionUri);
}
public MoneroRpcConnection getMoneroConnection() {
return coreMoneroConnectionsService.getConnection();
}
public List<MoneroRpcConnection> getMoneroConnections() {
return coreMoneroConnectionsService.getConnections();
}
public void setMoneroConnection(String connectionUri) {
coreMoneroConnectionsService.setConnection(connectionUri);
}
public void setMoneroConnection(MoneroRpcConnection connection) {
coreMoneroConnectionsService.setConnection(connection);
}
public MoneroRpcConnection checkMoneroConnection() {
return coreMoneroConnectionsService.checkConnection();
}
public List<MoneroRpcConnection> checkMoneroConnections() {
return coreMoneroConnectionsService.checkConnections();
}
public void startCheckingMoneroConnection(Long refreshPeriod) {
coreMoneroConnectionsService.startCheckingConnection(refreshPeriod);
}
public void stopCheckingMoneroConnection() {
coreMoneroConnectionsService.stopCheckingConnection();
}
public MoneroRpcConnection getBestAvailableMoneroConnection() {
return coreMoneroConnectionsService.getBestAvailableConnection();
}
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
public BalancesInfo getBalances(String currencyCode) {
return walletsService.getBalances(currencyCode);
}
public String getNewDepositSubaddress() {
return walletsService.getNewDepositSubaddress();
}
public List<MoneroTxWallet> getXmrTxs() {
return walletsService.getXmrTxs();
}
public MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
return walletsService.createXmrTx(destinations);
}
public String relayXmrTx(String metadata) {
return walletsService.relayXmrTx(metadata);
}
public long getAddressBalance(String addressString) {
return walletsService.getAddressBalance(addressString);
}
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
return walletsService.getAddressBalanceInfo(addressString);
}
public List<AddressBalanceInfo> getFundingAddresses() {
return walletsService.getFundingAddresses();
}
public void sendBtc(String address,
String amount,
String txFeeRate,
String memo,
FutureCallback<Transaction> callback) {
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
}
public void getTxFeeRate(ResultHandler resultHandler) {
walletsService.getTxFeeRate(resultHandler);
}
public void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
}
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
walletsService.unsetTxFeeRatePreference(resultHandler);
}
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return walletsService.getMostRecentTxFeeRateInfo();
}
public Transaction getTransaction(String txId) {
return walletsService.getTransaction(txId);
}
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}
public void lockWallet() {
walletsService.lockWallet();
}
public void unlockWallet(String password, long timeout) {
walletsService.unlockWallet(password, timeout);
}
public void removeWalletPassword(String password) {
walletsService.removeWalletPassword(password);
}
public List<TradeStatistics3> getTradeStatistics() {
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
}
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -139,11 +334,11 @@ public class CoreApi {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Help // Dispute Agents
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public String getMethodHelp(String methodName) { public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
return coreHelpService.getMethodHelp(methodName); coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -312,146 +507,4 @@ public class CoreApi {
public String getTradeRole(String tradeId) { public String getTradeRole(String tradeId) {
return coreTradesService.getTradeRole(tradeId); return coreTradesService.getTradeRole(tradeId);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Wallets
///////////////////////////////////////////////////////////////////////////////////////////
public BalancesInfo getBalances(String currencyCode) {
return walletsService.getBalances(currencyCode);
}
public String getNewDepositSubaddress() {
return walletsService.getNewDepositSubaddress();
}
public List<MoneroTxWallet> getXmrTxs() {
return walletsService.getXmrTxs();
}
public MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
return walletsService.createXmrTx(destinations);
}
public String relayXmrTx(String metadata) {
return walletsService.relayXmrTx(metadata);
}
public long getAddressBalance(String addressString) {
return walletsService.getAddressBalance(addressString);
}
public AddressBalanceInfo getAddressBalanceInfo(String addressString) {
return walletsService.getAddressBalanceInfo(addressString);
}
public List<AddressBalanceInfo> getFundingAddresses() {
return walletsService.getFundingAddresses();
}
public void sendBtc(String address,
String amount,
String txFeeRate,
String memo,
FutureCallback<Transaction> callback) {
walletsService.sendBtc(address, amount, txFeeRate, memo, callback);
}
public void getTxFeeRate(ResultHandler resultHandler) {
walletsService.getTxFeeRate(resultHandler);
}
public void setTxFeeRatePreference(long txFeeRate,
ResultHandler resultHandler) {
walletsService.setTxFeeRatePreference(txFeeRate, resultHandler);
}
public void unsetTxFeeRatePreference(ResultHandler resultHandler) {
walletsService.unsetTxFeeRatePreference(resultHandler);
}
public TxFeeRateInfo getMostRecentTxFeeRateInfo() {
return walletsService.getMostRecentTxFeeRateInfo();
}
public Transaction getTransaction(String txId) {
return walletsService.getTransaction(txId);
}
public void setWalletPassword(String password, String newPassword) {
walletsService.setWalletPassword(password, newPassword);
}
public void lockWallet() {
walletsService.lockWallet();
}
public void unlockWallet(String password, long timeout) {
walletsService.unlockWallet(password, timeout);
}
public void removeWalletPassword(String password) {
walletsService.removeWalletPassword(password);
}
public List<TradeStatistics3> getTradeStatistics() {
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
}
public int getNumConfirmationsForMostRecentTransaction(String addressString) {
return walletsService.getNumConfirmationsForMostRecentTransaction(addressString);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Monero Connections
///////////////////////////////////////////////////////////////////////////////////////////
public void addMoneroConnection(MoneroRpcConnection connection) {
coreMoneroConnectionsService.addConnection(connection);
}
public void removeMoneroConnection(String connectionUri) {
coreMoneroConnectionsService.removeConnection(connectionUri);
}
public MoneroRpcConnection getMoneroConnection() {
return coreMoneroConnectionsService.getConnection();
}
public List<MoneroRpcConnection> getMoneroConnections() {
return coreMoneroConnectionsService.getConnections();
}
public void setMoneroConnection(String connectionUri) {
coreMoneroConnectionsService.setConnection(connectionUri);
}
public void setMoneroConnection(MoneroRpcConnection connection) {
coreMoneroConnectionsService.setConnection(connection);
}
public MoneroRpcConnection checkMoneroConnection() {
return coreMoneroConnectionsService.checkConnection();
}
public List<MoneroRpcConnection> checkMoneroConnections() {
return coreMoneroConnectionsService.checkConnections();
}
public void startCheckingMoneroConnection(Long refreshPeriod) {
coreMoneroConnectionsService.startCheckingConnection(refreshPeriod);
}
public void stopCheckingMoneroConnection() {
coreMoneroConnectionsService.stopCheckingConnection();
}
public MoneroRpcConnection getBestAvailableMoneroConnection() {
return coreMoneroConnectionsService.getBestAvailableConnection();
}
public void setMoneroConnectionAutoSwitch(boolean autoSwitch) {
coreMoneroConnectionsService.setAutoSwitch(autoSwitch);
}
} }

View File

@ -1,21 +1,38 @@
package bisq.core.api; package bisq.core.api;
import bisq.common.UserThread;
import bisq.core.btc.model.EncryptedConnectionList; import bisq.core.btc.model.EncryptedConnectionList;
import bisq.core.btc.setup.DownloadListener;
import bisq.core.btc.setup.WalletsSetup;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroConnectionManager; import monero.common.MoneroConnectionManager;
import monero.common.MoneroConnectionManagerListener; import monero.common.MoneroConnectionManagerListener;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc;
import monero.daemon.model.MoneroPeer;
@Slf4j @Slf4j
@Singleton @Singleton
public class CoreMoneroConnectionsService { public final class CoreMoneroConnectionsService {
// TODO: this connection manager should update app status, don't poll in WalletsSetup every 30 seconds private static final int MIN_BROADCAST_CONNECTIONS = 0; // TODO: 0 for stagenet, 5+ for mainnet
private static final long DEFAULT_REFRESH_PERIOD = 15_000L; // check the connection every 15 seconds per default private static final long DAEMON_REFRESH_PERIOD_MS = 15000L; // check connection periodically in ms
private static final long DAEMON_INFO_POLL_PERIOD_MS = 20000L; // collect daemon info periodically in ms
// TODO (woodser): support each network type, move to config, remove localhost authentication // TODO (woodser): support each network type, move to config, remove localhost authentication
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList( private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
@ -24,21 +41,222 @@ public class CoreMoneroConnectionsService {
); );
private final Object lock = new Object(); private final Object lock = new Object();
private final CoreAccountService accountService;
private final MoneroConnectionManager connectionManager; private final MoneroConnectionManager connectionManager;
private final EncryptedConnectionList connectionList; private final EncryptedConnectionList connectionList;
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final LongProperty chainHeight = new SimpleLongProperty(0);
private final DownloadListener downloadListener = new DownloadListener();
private MoneroDaemon daemon;
private boolean isInitialized = false;
@Inject @Inject
public CoreMoneroConnectionsService(MoneroConnectionManager connectionManager, public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
CoreAccountService accountService,
MoneroConnectionManager connectionManager,
EncryptedConnectionList connectionList) { EncryptedConnectionList connectionList) {
this.accountService = accountService;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.connectionList = connectionList; this.connectionList = connectionList;
// initialize after account open and basic setup
walletsSetup.addSetupTaskHandler(() -> { // TODO: use something better than legacy WalletSetup for notification to initialize
// initialize from connections read from disk
initialize();
// listen for account to be opened or password changed
accountService.addListener(new AccountServiceListener() {
@Override
public void onAccountOpened() {
try {
log.info(getClass() + ".onAccountOpened() called");
initialize();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
} }
public void initialize() { @Override
public void onPasswordChanged(String oldPassword, String newPassword) {
log.info(getClass() + ".onPasswordChanged({}, {}) called", oldPassword, newPassword);
connectionList.changePassword(oldPassword, newPassword);
}
});
});
}
// ------------------------ CONNECTION MANAGEMENT -------------------------
public MoneroDaemon getDaemon() {
accountService.checkAccountOpen();
return this.daemon;
}
public void addListener(MoneroConnectionManagerListener listener) {
synchronized (lock) { synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.addListener(listener);
}
}
public void addConnection(MoneroRpcConnection connection) {
synchronized (lock) {
accountService.checkAccountOpen();
connectionList.addConnection(connection);
connectionManager.addConnection(connection);
}
}
public void removeConnection(String uri) {
synchronized (lock) {
accountService.checkAccountOpen();
connectionList.removeConnection(uri);
connectionManager.removeConnection(uri);
}
}
public MoneroRpcConnection getConnection() {
synchronized (lock) {
accountService.checkAccountOpen();
return connectionManager.getConnection();
}
}
public List<MoneroRpcConnection> getConnections() {
synchronized (lock) {
accountService.checkAccountOpen();
return connectionManager.getConnections();
}
}
public void setConnection(String connectionUri) {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.setConnection(connectionUri); // listener will update connection list
}
}
public void setConnection(MoneroRpcConnection connection) {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.setConnection(connection); // listener will update connection list
}
}
public MoneroRpcConnection checkConnection() {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.checkConnection();
return getConnection();
}
}
public List<MoneroRpcConnection> checkConnections() {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.checkConnections();
return getConnections();
}
}
public void startCheckingConnection(Long refreshPeriod) {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.startCheckingConnection(refreshPeriod == null ? DAEMON_REFRESH_PERIOD_MS : refreshPeriod);
connectionList.setRefreshPeriod(refreshPeriod);
}
}
public void stopCheckingConnection() {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.stopCheckingConnection();
connectionList.setRefreshPeriod(-1L);
}
}
public MoneroRpcConnection getBestAvailableConnection() {
synchronized (lock) {
accountService.checkAccountOpen();
return connectionManager.getBestAvailableConnection();
}
}
public void setAutoSwitch(boolean autoSwitch) {
synchronized (lock) {
accountService.checkAccountOpen();
connectionManager.setAutoSwitch(autoSwitch);
connectionList.setAutoSwitch(autoSwitch);
}
}
// ----------------------------- APP METHODS ------------------------------
public boolean isChainHeightSyncedWithinTolerance() {
if (daemon == null) return false;
Long targetHeight = daemon.getSyncInfo().getTargetHeight();
if (targetHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
long currentHeight = chainHeight.get();
if (Math.abs(targetHeight - currentHeight) <= 3) {
return true;
}
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), targetHeight);
return false;
}
public ReadOnlyIntegerProperty numPeersProperty() {
return numPeers;
}
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
return peers;
}
public boolean hasSufficientPeersForBroadcast() {
return numPeers.get() >= getMinBroadcastConnections();
}
public LongProperty chainHeightProperty() {
return chainHeight;
}
public ReadOnlyDoubleProperty downloadPercentageProperty() {
return downloadListener.percentageProperty();
}
public int getMinBroadcastConnections() {
return MIN_BROADCAST_CONNECTIONS;
}
public boolean isDownloadComplete() {
return downloadPercentageProperty().get() == 1d;
}
/**
* Signals that both the daemon and wallet have synced.
*
* TODO: separate daemon and wallet download/done listeners
*/
public void doneDownload() {
downloadListener.doneDownload();
}
// ------------------------------- HELPERS --------------------------------
private void initialize() {
synchronized (lock) {
// reset connection manager's connections and listeners
connectionManager.reset();
// load connections // load connections
connectionList.getConnections().forEach(connectionManager::addConnection); connectionList.getConnections().forEach(connectionManager::addConnection);
log.info("Read " + connectionList.getConnections().size() + " connections from disk");
// add default connections // add default connections
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) { for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) {
@ -51,23 +269,37 @@ public class CoreMoneroConnectionsService {
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
}); });
// initialize daemon
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
updateDaemonInfo();
// restore configuration // restore configuration
connectionManager.setAutoSwitch(connectionList.getAutoSwitch()); connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
long refreshPeriod = connectionList.getRefreshPeriod(); long refreshPeriod = connectionList.getRefreshPeriod();
if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod); if (refreshPeriod > 0) connectionManager.startCheckingConnection(refreshPeriod);
else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DEFAULT_REFRESH_PERIOD); else if (refreshPeriod == 0) connectionManager.startCheckingConnection(DAEMON_REFRESH_PERIOD_MS);
else checkConnection(); else checkConnection();
// run once
if (!isInitialized) {
// register connection change listener // register connection change listener
connectionManager.addListener(this::onConnectionChanged); connectionManager.addListener(this::onConnectionChanged);
// poll daemon periodically
startPollingDaemon();
isInitialized = true;
}
} }
} }
private void onConnectionChanged(MoneroRpcConnection currentConnection) { private void onConnectionChanged(MoneroRpcConnection currentConnection) {
synchronized (lock) { synchronized (lock) {
if (currentConnection == null) { if (currentConnection == null) {
daemon = null;
connectionList.setCurrentConnectionUri(null); connectionList.setCurrentConnectionUri(null);
} else { } else {
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
connectionList.removeConnection(currentConnection.getUri()); connectionList.removeConnection(currentConnection.getUri());
connectionList.addConnection(currentConnection); connectionList.addConnection(currentConnection);
connectionList.setCurrentConnectionUri(currentConnection.getUri()); connectionList.setCurrentConnectionUri(currentConnection.getUri());
@ -75,88 +307,26 @@ public class CoreMoneroConnectionsService {
} }
} }
public void addConnectionListener(MoneroConnectionManagerListener listener) { private void startPollingDaemon() {
synchronized (lock) { UserThread.runPeriodically(() -> {
connectionManager.addListener(listener); updateDaemonInfo();
}, DAEMON_INFO_POLL_PERIOD_MS / 1000l);
}
private void updateDaemonInfo() {
try {
if (daemon == null) throw new RuntimeException("No daemon connection");
peers.set(getOnlinePeers());
numPeers.set(peers.get().size());
chainHeight.set(daemon.getHeight());
} catch (Exception e) {
log.warn("Could not update daemon info: " + e.getMessage());
} }
} }
public void addConnection(MoneroRpcConnection connection) { private List<MoneroPeer> getOnlinePeers() {
synchronized (lock) { return daemon.getPeers().stream()
connectionList.addConnection(connection); .filter(peer -> peer.isOnline())
connectionManager.addConnection(connection); .collect(Collectors.toList());
}
}
public void removeConnection(String uri) {
synchronized (lock) {
connectionList.removeConnection(uri);
connectionManager.removeConnection(uri);
}
}
public MoneroRpcConnection getConnection() {
synchronized (lock) {
return connectionManager.getConnection();
}
}
public List<MoneroRpcConnection> getConnections() {
synchronized (lock) {
return connectionManager.getConnections();
}
}
public void setConnection(String connectionUri) {
synchronized (lock) {
connectionManager.setConnection(connectionUri); // listener will update connection list
}
}
public void setConnection(MoneroRpcConnection connection) {
synchronized (lock) {
connectionManager.setConnection(connection); // listener will update connection list
}
}
public MoneroRpcConnection checkConnection() {
synchronized (lock) {
connectionManager.checkConnection();
return getConnection();
}
}
public List<MoneroRpcConnection> checkConnections() {
synchronized (lock) {
connectionManager.checkConnections();
return getConnections();
}
}
public void startCheckingConnection(Long refreshPeriod) {
synchronized (lock) {
connectionManager.startCheckingConnection(refreshPeriod == null ? DEFAULT_REFRESH_PERIOD : refreshPeriod);
connectionList.setRefreshPeriod(refreshPeriod);
}
}
public void stopCheckingConnection() {
synchronized (lock) {
connectionManager.stopCheckingConnection();
connectionList.setRefreshPeriod(-1L);
}
}
public MoneroRpcConnection getBestAvailableConnection() {
synchronized (lock) {
return connectionManager.getBestAvailableConnection();
}
}
public void setAutoSwitch(boolean autoSwitch) {
synchronized (lock) {
connectionManager.setAutoSwitch(autoSwitch);
connectionList.setAutoSwitch(autoSwitch);
}
} }
} }

View File

@ -41,6 +41,13 @@ public class CoreNotificationService {
} }
} }
public void sendAppInitializedNotification() {
sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.APP_INITIALIZED)
.setTimestamp(System.currentTimeMillis())
.build());
}
public void sendTradeNotification(Trade trade, String title, String message) { public void sendTradeNotification(Trade trade, String title, String message) {
sendNotification(NotificationMessage.newBuilder() sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.TRADE_UPDATE) .setType(NotificationType.TRADE_UPDATE)

View File

@ -89,6 +89,7 @@ import monero.wallet.model.MoneroTxWallet;
class CoreWalletsService { class CoreWalletsService {
private final AppStartupState appStartupState; private final AppStartupState appStartupState;
private final CoreAccountService accountService;
private final CoreContext coreContext; private final CoreContext coreContext;
private final Balances balances; private final Balances balances;
private final WalletsManager walletsManager; private final WalletsManager walletsManager;
@ -110,6 +111,7 @@ class CoreWalletsService {
@Inject @Inject
public CoreWalletsService(AppStartupState appStartupState, public CoreWalletsService(AppStartupState appStartupState,
CoreContext coreContext, CoreContext coreContext,
CoreAccountService accountService,
Balances balances, Balances balances,
WalletsManager walletsManager, WalletsManager walletsManager,
WalletsSetup walletsSetup, WalletsSetup walletsSetup,
@ -120,6 +122,7 @@ class CoreWalletsService {
Preferences preferences) { Preferences preferences) {
this.appStartupState = appStartupState; this.appStartupState = appStartupState;
this.coreContext = coreContext; this.coreContext = coreContext;
this.accountService = accountService;
this.balances = balances; this.balances = balances;
this.walletsManager = walletsManager; this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup; this.walletsSetup = walletsSetup;
@ -141,6 +144,7 @@ class CoreWalletsService {
} }
BalancesInfo getBalances(String currencyCode) { BalancesInfo getBalances(String currencyCode) {
accountService.checkAccountOpen();
verifyWalletCurrencyCodeIsValid(currencyCode); verifyWalletCurrencyCodeIsValid(currencyCode);
verifyWalletsAreAvailable(); verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked(); verifyEncryptedWalletIsUnlocked();
@ -158,14 +162,17 @@ class CoreWalletsService {
} }
String getNewDepositSubaddress() { String getNewDepositSubaddress() {
accountService.checkAccountOpen();
return xmrWalletService.getWallet().createSubaddress(0).getAddress(); return xmrWalletService.getWallet().createSubaddress(0).getAddress();
} }
List<MoneroTxWallet> getXmrTxs(){ List<MoneroTxWallet> getXmrTxs() {
accountService.checkAccountOpen();
return xmrWalletService.getWallet().getTxs(); return xmrWalletService.getWallet().getTxs();
} }
MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) { MoneroTxWallet createXmrTx(List<MoneroDestination> destinations) {
accountService.checkAccountOpen();
verifyWalletsAreAvailable(); verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked(); verifyEncryptedWalletIsUnlocked();
try { try {
@ -177,6 +184,7 @@ class CoreWalletsService {
} }
String relayXmrTx(String metadata) { String relayXmrTx(String metadata) {
accountService.checkAccountOpen();
verifyWalletsAreAvailable(); verifyWalletsAreAvailable();
verifyEncryptedWalletIsUnlocked(); verifyEncryptedWalletIsUnlocked();
try { try {

View File

@ -12,7 +12,7 @@ import lombok.Value;
@Builder(toBuilder = true) @Builder(toBuilder = true)
public class EncryptedConnection implements PersistablePayload { public class EncryptedConnection implements PersistablePayload {
String uri; String url;
String username; String username;
byte[] encryptedPassword; byte[] encryptedPassword;
byte[] encryptionSalt; byte[] encryptionSalt;
@ -21,7 +21,7 @@ public class EncryptedConnection implements PersistablePayload {
@Override @Override
public protobuf.EncryptedConnection toProtoMessage() { public protobuf.EncryptedConnection toProtoMessage() {
return protobuf.EncryptedConnection.newBuilder() return protobuf.EncryptedConnection.newBuilder()
.setUri(uri) .setUrl(url)
.setUsername(username) .setUsername(username)
.setEncryptedPassword(ByteString.copyFrom(encryptedPassword)) .setEncryptedPassword(ByteString.copyFrom(encryptedPassword))
.setEncryptionSalt(ByteString.copyFrom(encryptionSalt)) .setEncryptionSalt(ByteString.copyFrom(encryptionSalt))
@ -31,7 +31,7 @@ public class EncryptedConnection implements PersistablePayload {
public static EncryptedConnection fromProto(protobuf.EncryptedConnection encryptedConnection) { public static EncryptedConnection fromProto(protobuf.EncryptedConnection encryptedConnection) {
return new EncryptedConnection( return new EncryptedConnection(
encryptedConnection.getUri(), encryptedConnection.getUrl(),
encryptedConnection.getUsername(), encryptedConnection.getUsername(),
encryptedConnection.getEncryptedPassword().toByteArray(), encryptedConnection.getEncryptedPassword().toByteArray(),
encryptedConnection.getEncryptionSalt().toByteArray(), encryptedConnection.getEncryptionSalt().toByteArray(),

View File

@ -17,22 +17,18 @@
package bisq.core.app; package bisq.core.app;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.api.CoreNotificationService;
import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.monadic.MonadicBinding;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javax.inject.Inject;
import javax.inject.Singleton;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.monadic.MonadicBinding;
/** /**
* We often need to wait until network and wallet is ready or other combination of startup states. * We often need to wait until network and wallet is ready or other combination of startup states.
@ -53,7 +49,9 @@ public class AppStartupState {
private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty(); private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty();
@Inject @Inject
public AppStartupState(WalletsSetup walletsSetup, P2PService p2PService) { public AppStartupState(CoreNotificationService notificationService,
CoreMoneroConnectionsService connectionsService,
P2PService p2PService) {
p2PService.addP2PServiceListener(new BootstrapListener() { p2PService.addP2PServiceListener(new BootstrapListener() {
@Override @Override
@ -62,13 +60,13 @@ public class AppStartupState {
} }
}); });
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> { connectionsService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
if (walletsSetup.isDownloadComplete()) if (connectionsService.isDownloadComplete())
isBlockDownloadComplete.set(true); isBlockDownloadComplete.set(true);
}); });
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> { connectionsService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
if (walletsSetup.hasSufficientPeersForBroadcast()) if (connectionsService.hasSufficientPeersForBroadcast())
hasSufficientPeersForBroadcast.set(true); hasSufficientPeersForBroadcast.set(true);
}); });
@ -77,6 +75,7 @@ public class AppStartupState {
hasSufficientPeersForBroadcast, hasSufficientPeersForBroadcast,
allDomainServicesInitialized, allDomainServicesInitialized,
(a, b, c, d) -> { (a, b, c, d) -> {
log.info("p2pNetworkAndWalletInitialized = {} = updatedDataReceived={} && isBlockDownloadComplete={} && hasSufficientPeersForBroadcast={} && allDomainServicesInitialized={}", (a && b && c && d), updatedDataReceived.get(), isBlockDownloadComplete.get(), hasSufficientPeersForBroadcast.get(), allDomainServicesInitialized.get());
if (a && b && c) { if (a && b && c) {
walletAndNetworkReady.set(true); walletAndNetworkReady.set(true);
} }
@ -85,6 +84,7 @@ public class AppStartupState {
p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> { p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> {
if (newValue) { if (newValue) {
applicationFullyInitialized.set(true); applicationFullyInitialized.set(true);
notificationService.sendAppInitializedNotification();
log.info("Application fully initialized"); log.info("Application fully initialized");
} }
}); });

View File

@ -41,8 +41,6 @@ import bisq.network.p2p.seed.SeedNodeRepository;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.proto.network.NetworkProtoResolver; import bisq.common.proto.network.NetworkProtoResolver;
import bisq.common.proto.persistable.PersistenceProtoResolver; import bisq.common.proto.persistable.PersistenceProtoResolver;
@ -93,6 +91,5 @@ public class CoreModule extends AppModule {
install(new FilterModule(config)); install(new FilterModule(config));
install(new CorePresentationModule(config)); install(new CorePresentationModule(config));
install(new MoneroConnectionModule(config)); install(new MoneroConnectionModule(config));
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
} }
} }

View File

@ -230,14 +230,14 @@ public class DomainInitialisation {
triggerPriceService.onAllServicesInitialized(); triggerPriceService.onAllServicesInitialized();
mempoolService.onAllServicesInitialized(); mempoolService.onAllServicesInitialized();
if (revolutAccountsUpdateHandler != null) { if (revolutAccountsUpdateHandler != null && user.getPaymentAccountsAsObservable() != null) {
revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream() revolutAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
.filter(paymentAccount -> paymentAccount instanceof RevolutAccount) .filter(paymentAccount -> paymentAccount instanceof RevolutAccount)
.map(paymentAccount -> (RevolutAccount) paymentAccount) .map(paymentAccount -> (RevolutAccount) paymentAccount)
.filter(RevolutAccount::userNameNotSet) .filter(RevolutAccount::userNameNotSet)
.collect(Collectors.toList())); .collect(Collectors.toList()));
} }
if (amazonGiftCardAccountsUpdateHandler != null) { if (amazonGiftCardAccountsUpdateHandler != null && user.getPaymentAccountsAsObservable() != null) {
amazonGiftCardAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream() amazonGiftCardAccountsUpdateHandler.accept(user.getPaymentAccountsAsObservable().stream()
.filter(paymentAccount -> paymentAccount instanceof AmazonGiftCardAccount) .filter(paymentAccount -> paymentAccount instanceof AmazonGiftCardAccount)
.map(paymentAccount -> (AmazonGiftCardAccount) paymentAccount) .map(paymentAccount -> (AmazonGiftCardAccount) paymentAccount)

View File

@ -17,6 +17,8 @@
package bisq.core.app; package bisq.core.app;
import bisq.core.api.AccountServiceListener;
import bisq.core.api.CoreAccountService;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
@ -27,7 +29,6 @@ import bisq.core.setup.CoreSetup;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
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;
@ -35,6 +36,7 @@ import bisq.common.app.AppModule;
import bisq.common.config.HavenoHelpFormatter; 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.crypto.IncorrectPasswordException;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
import bisq.common.persistence.PersistenceManager; import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.proto.persistable.PersistedDataHost;
@ -45,7 +47,6 @@ import bisq.common.util.Utilities;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -64,11 +65,12 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
private final String appName; private final String appName;
private final String version; private final String version;
protected CoreAccountService accountService;
protected Injector injector; protected Injector injector;
protected AppModule module; protected AppModule module;
protected Config config; protected Config config;
private boolean isShutdownInProgress; private boolean isShutdownInProgress;
private boolean hasDowngraded; private boolean isReadOnly;
public HavenoExecutable(String fullName, String scriptName, String appName, String version) { public HavenoExecutable(String fullName, String scriptName, String appName, String version) {
this.fullName = fullName; this.fullName = fullName;
@ -136,17 +138,61 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
setupGuice(); setupGuice();
setupAvoidStandbyMode(); setupAvoidStandbyMode();
hasDowngraded = HavenoSetup.hasDowngraded();
if (hasDowngraded) {
// If user tried to downgrade we do not read the persisted data to avoid data corruption // If user tried to downgrade we do not read the persisted data to avoid data corruption
// We call startApplication to enable UI to show popup. We prevent in HavenoSetup to go further // We call startApplication to enable UI to show popup. We prevent in HavenoSetup to go further
// in the process and require a shut down. // in the process and require a shut down.
startApplication(); isReadOnly = HavenoSetup.hasDowngraded();
} else {
// Account service should be available before attempting to login.
accountService = injector.getInstance(CoreAccountService.class);
// Application needs to restart on delete and restore of account.
accountService.addListener(new AccountServiceListener() {
@Override public void onAccountDeleted(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
@Override public void onAccountRestored(Runnable onShutdown) { shutDownNoPersist(onShutdown); }
});
// Attempt to login, subclasses should implement interactive login and or rpc login.
if (!isReadOnly && loginAccount()) {
readAllPersisted(this::startApplication); readAllPersisted(this::startApplication);
} else {
log.warn("Running application in readonly mode");
startApplication();
} }
} }
/**
* Do not persist when shutting down after account restore and restarts since
* that causes the current persistables to overwrite the restored or deleted state.
*/
protected void shutDownNoPersist(Runnable onShutdown) {
this.isReadOnly = true;
gracefulShutDown(() -> {
log.info("Shutdown without persisting");
if (onShutdown != null) onShutdown.run();
});
}
/**
* Attempt to login. TODO: supply a password in config or args
*
* @return true if account is opened successfully.
*/
protected boolean loginAccount() {
if (accountService.accountExists()) {
log.info("Account already exists, attempting to open");
try {
accountService.openAccount(null);
} catch (IncorrectPasswordException ipe) {
log.info("Account password protected, password required");
}
} else if (!config.passwordRequired) {
log.info("Creating Haveno account with null password");
accountService.createAccount(null);
}
return accountService.isAccountOpen();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// We continue with a series of synchronous execution tasks // We continue with a series of synchronous execution tasks
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -198,9 +244,9 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
} }
protected void runHavenoSetup() { protected void runHavenoSetup() {
HavenoSetup bisqSetup = injector.getInstance(HavenoSetup.class); HavenoSetup havenoSetup = injector.getInstance(HavenoSetup.class);
bisqSetup.addHavenoSetupListener(this); havenoSetup.addHavenoSetupListener(this);
bisqSetup.start(); havenoSetup.start();
} }
@Override @Override
@ -233,7 +279,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
injector.getInstance(TradeStatisticsManager.class).shutDown(); injector.getInstance(TradeStatisticsManager.class).shutDown();
injector.getInstance(XmrTxProofService.class).shutDown(); injector.getInstance(XmrTxProofService.class).shutDown();
injector.getInstance(AvoidStandbyModeService.class).shutDown(); injector.getInstance(AvoidStandbyModeService.class).shutDown();
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService
log.info("OpenOfferManager shutdown started"); log.info("OpenOfferManager shutdown started");
injector.getInstance(OpenOfferManager.class).shutDown(() -> { injector.getInstance(OpenOfferManager.class).shutDown(() -> {
log.info("OpenOfferManager shutdown completed"); log.info("OpenOfferManager shutdown completed");
@ -248,16 +294,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
injector.getInstance(P2PService.class).shutDown(() -> { injector.getInstance(P2PService.class).shutDown(() -> {
log.info("P2PService shutdown completed"); log.info("P2PService shutdown completed");
module.close(injector); module.close(injector);
if (!hasDowngraded) { completeShutdown(resultHandler, EXIT_SUCCESS);
// If user tried to downgrade we do not write the persistable data to avoid data corruption
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
log.info("Graceful shutdown completed. Exiting now.");
resultHandler.handleResult();
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
});
} else {
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
}
}); });
}); });
walletsSetup.shutDown(); walletsSetup.shutDown();
@ -267,31 +304,26 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
// Wait max 20 sec. // Wait max 20 sec.
UserThread.runAfter(() -> { UserThread.runAfter(() -> {
log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler."); log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler.");
if (!hasDowngraded) { completeShutdown(resultHandler, EXIT_SUCCESS);
// If user tried to downgrade we do not write the persistable data to avoid data corruption
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
log.info("Graceful shutdown resulted in a timeout. Exiting now.");
resultHandler.handleResult();
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
});
} else {
UserThread.runAfter(() -> System.exit(EXIT_SUCCESS), 1);
}
}, 20); }, 20);
} catch (Throwable t) { } catch (Throwable t) {
log.error("App shutdown failed with exception {}", t.toString()); log.error("App shutdown failed with exception {}", t.toString());
t.printStackTrace(); t.printStackTrace();
if (!hasDowngraded) { completeShutdown(resultHandler, EXIT_FAILURE);
}
}
private void completeShutdown(ResultHandler resultHandler, int exitCode) {
if (!isReadOnly) {
// If user tried to downgrade we do not write the persistable data to avoid data corruption // If user tried to downgrade we do not write the persistable data to avoid data corruption
PersistenceManager.flushAllDataToDiskAtShutdown(() -> { PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
log.info("Graceful shutdown resulted in an error. Exiting now."); log.info("Graceful shutdown flushed persistence. Exiting now.");
resultHandler.handleResult(); resultHandler.handleResult();
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1); UserThread.runAfter(() -> System.exit(exitCode), 1);
}); });
} else { } else {
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1); resultHandler.handleResult();
} UserThread.runAfter(() -> System.exit(exitCode), 1);
} }
} }

View File

@ -36,6 +36,8 @@ import lombok.extern.slf4j.Slf4j;
public class HavenoHeadlessApp implements HeadlessApp { public class HavenoHeadlessApp implements HeadlessApp {
@Getter @Getter
private static Runnable shutDownHandler; private static Runnable shutDownHandler;
@Setter
public static Runnable onGracefulShutDownHandler;
@Setter @Setter
protected Injector injector; protected Injector injector;
@ -50,6 +52,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
shutDownHandler = this::stop; shutDownHandler = this::stop;
} }
@Override
public void startApplication() { public void startApplication() {
try { try {
bisqSetup = injector.getInstance(HavenoSetup.class); bisqSetup = injector.getInstance(HavenoSetup.class);
@ -103,13 +106,13 @@ public class HavenoHeadlessApp implements HeadlessApp {
UserThread.runAfter(() -> { UserThread.runAfter(() -> {
gracefulShutDownHandler.gracefulShutDown(() -> { gracefulShutDownHandler.gracefulShutDown(() -> {
log.debug("App shutdown complete"); log.debug("App shutdown complete");
if (onGracefulShutDownHandler != null) onGracefulShutDownHandler.run();
}); });
}, 200, TimeUnit.MILLISECONDS); }, 200, TimeUnit.MILLISECONDS);
shutDownRequested = true; shutDownRequested = true;
} }
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// UncaughtExceptionHandler implementation // UncaughtExceptionHandler implementation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View File

@ -115,6 +115,7 @@ public class HavenoHeadlessAppMain extends HavenoExecutable {
onApplicationStarted(); onApplicationStarted();
} }
// TODO: implement interactive console which allows user to input commands; login, logoff, exit
private void keepRunning() { private void keepRunning() {
while (true) { while (true) {
try { try {

View File

@ -195,7 +195,7 @@ public class HavenoSetup {
private boolean allBasicServicesInitialized; private boolean allBasicServicesInitialized;
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized; private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
private final List<HavenoSetupListener> bisqSetupListeners = new ArrayList<>(); private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
@Inject @Inject
public HavenoSetup(DomainInitialisation domainInitialisation, public HavenoSetup(DomainInitialisation domainInitialisation,
@ -274,7 +274,7 @@ public class HavenoSetup {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addHavenoSetupListener(HavenoSetupListener listener) { public void addHavenoSetupListener(HavenoSetupListener listener) {
bisqSetupListeners.add(listener); havenoSetupListeners.add(listener);
} }
public void start() { public void start() {
@ -284,7 +284,7 @@ public class HavenoSetup {
return; return;
} }
persistBisqVersion(); persistHavenoVersion();
maybeReSyncSPVChain(); maybeReSyncSPVChain();
maybeShowTac(this::step2); maybeShowTac(this::step2);
} }
@ -303,7 +303,7 @@ public class HavenoSetup {
private void step4() { private void step4() {
initDomainServices(); initDomainServices();
bisqSetupListeners.forEach(HavenoSetupListener::onSetupComplete); havenoSetupListeners.forEach(HavenoSetupListener::onSetupComplete);
// We set that after calling the setupCompleteHandler to not trigger a popup from the dev dummy accounts // We set that after calling the setupCompleteHandler to not trigger a popup from the dev dummy accounts
// in MainViewModel // in MainViewModel
@ -373,7 +373,7 @@ public class HavenoSetup {
}, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES); }, STARTUP_TIMEOUT_MINUTES, TimeUnit.MINUTES);
log.info("Init P2P network"); log.info("Init P2P network");
bisqSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork); havenoSetupListeners.forEach(HavenoSetupListener::onInitP2pNetwork);
p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler); p2pNetworkReady = p2PNetworkSetup.init(this::initWallet, displayTorNetworkSettingsHandler);
// We only init wallet service here if not using Tor for bitcoinj. // We only init wallet service here if not using Tor for bitcoinj.
@ -402,10 +402,10 @@ public class HavenoSetup {
private void initWallet() { private void initWallet() {
log.info("Init wallet"); log.info("Init wallet");
bisqSetupListeners.forEach(HavenoSetupListener::onInitWallet); havenoSetupListeners.forEach(HavenoSetupListener::onInitWallet);
Runnable walletPasswordHandler = () -> { Runnable walletPasswordHandler = () -> {
log.info("Wallet password required"); log.info("Wallet password required");
bisqSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword); havenoSetupListeners.forEach(HavenoSetupListener::onRequestWalletPassword);
if (p2pNetworkReady.get()) if (p2pNetworkReady.get())
p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true); p2PNetworkSetup.setSplashP2PNetworkAnimationVisible(true);
@ -581,7 +581,7 @@ public class HavenoSetup {
return hasDowngraded; return hasDowngraded;
} }
public static void persistBisqVersion() { public static void persistHavenoVersion() {
File versionFile = getVersionFile(); File versionFile = getVersionFile();
if (!versionFile.exists()) { if (!versionFile.exists()) {
try { try {
@ -639,6 +639,7 @@ public class HavenoSetup {
} }
private void maybeShowSecurityRecommendation() { private void maybeShowSecurityRecommendation() {
if (user.getPaymentAccountsAsObservable() == null) return;
String key = "remindPasswordAndBackup"; String key = "remindPasswordAndBackup";
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) change -> { user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) change -> {
if (!walletsManager.areWalletsEncrypted() && !user.isPaymentAccountImport() && preferences.showAgain(key) && change.wasAdded() && if (!walletsManager.areWalletsEncrypted() && !user.isPaymentAccountImport() && preferences.showAgain(key) && change.wasAdded() &&

View File

@ -17,7 +17,7 @@
package bisq.core.app; package bisq.core.app;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
@ -51,7 +51,7 @@ import javax.annotation.Nullable;
public class P2PNetworkSetup { public class P2PNetworkSetup {
private final PriceFeedService priceFeedService; private final PriceFeedService priceFeedService;
private final P2PService p2PService; private final P2PService p2PService;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
private final Preferences preferences; private final Preferences preferences;
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
@ -75,12 +75,12 @@ public class P2PNetworkSetup {
@Inject @Inject
public P2PNetworkSetup(PriceFeedService priceFeedService, public P2PNetworkSetup(PriceFeedService priceFeedService,
P2PService p2PService, P2PService p2PService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
Preferences preferences) { Preferences preferences) {
this.priceFeedService = priceFeedService; this.priceFeedService = priceFeedService;
this.p2PService = p2PService; this.p2PService = p2PService;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.preferences = preferences; this.preferences = preferences;
} }
@ -91,7 +91,7 @@ public class P2PNetworkSetup {
BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty(); BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty();
p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(), p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(),
walletsSetup.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived, connectionService.numPeersProperty(), hiddenServicePublished, initialP2PNetworkDataReceived,
(state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> { (state, warning, numP2pPeers, numBtcPeers, hiddenService, dataReceived) -> {
String result; String result;
int p2pPeers = (int) numP2pPeers; int p2pPeers = (int) numP2pPeers;

View File

@ -18,6 +18,7 @@
package bisq.core.app; package bisq.core.app;
import bisq.core.api.CoreContext; import bisq.core.api.CoreContext;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.exceptions.InvalidHostException; import bisq.core.btc.exceptions.InvalidHostException;
import bisq.core.btc.exceptions.RejectedTxException; import bisq.core.btc.exceptions.RejectedTxException;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
@ -67,6 +68,7 @@ public class WalletAppSetup {
private final CoreContext coreContext; private final CoreContext coreContext;
private final WalletsManager walletsManager; private final WalletsManager walletsManager;
private final WalletsSetup walletsSetup; private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final FeeService feeService; private final FeeService feeService;
private final Config config; private final Config config;
private final Preferences preferences; private final Preferences preferences;
@ -91,12 +93,14 @@ public class WalletAppSetup {
public WalletAppSetup(CoreContext coreContext, public WalletAppSetup(CoreContext coreContext,
WalletsManager walletsManager, WalletsManager walletsManager,
WalletsSetup walletsSetup, WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
FeeService feeService, FeeService feeService,
Config config, Config config,
Preferences preferences) { Preferences preferences) {
this.coreContext = coreContext; this.coreContext = coreContext;
this.walletsManager = walletsManager; this.walletsManager = walletsManager;
this.walletsSetup = walletsSetup; this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.feeService = feeService; this.feeService = feeService;
this.config = config; this.config = config;
this.preferences = preferences; this.preferences = preferences;
@ -115,8 +119,8 @@ public class WalletAppSetup {
VersionMessage.BITCOINJ_VERSION, "2a80db4"); VersionMessage.BITCOINJ_VERSION, "2a80db4");
ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>(); ObjectProperty<Throwable> walletServiceException = new SimpleObjectProperty<>();
btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(), btcInfoBinding = EasyBind.combine(connectionService.downloadPercentageProperty(), // TODO (woodser): update to XMR
walletsSetup.chainHeightProperty(), connectionService.chainHeightProperty(),
feeService.feeUpdateCounterProperty(), feeService.feeUpdateCounterProperty(),
walletServiceException, walletServiceException,
(downloadPercentage, chainHeight, feeUpdate, exception) -> { (downloadPercentage, chainHeight, feeUpdate, exception) -> {
@ -124,10 +128,8 @@ public class WalletAppSetup {
if (exception == null) { if (exception == null) {
double percentage = (double) downloadPercentage; double percentage = (double) downloadPercentage;
btcSyncProgress.set(percentage); btcSyncProgress.set(percentage);
int bestChainHeight = walletsSetup.getChain() != null ? Long bestChainHeight = connectionService.getDaemon() == null ? null : connectionService.getDaemon().getInfo().getHeight();
walletsSetup.getChain().getBestChainHeight() : String chainHeightAsString = bestChainHeight != null && bestChainHeight > 0 ?
0;
String chainHeightAsString = bestChainHeight > 0 ?
String.valueOf(bestChainHeight) : String.valueOf(bestChainHeight) :
""; "";
if (percentage == 1) { if (percentage == 1) {

View File

@ -29,7 +29,7 @@ import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import bisq.core.trade.TradeModule; import bisq.core.trade.TradeModule;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.core.xmr.connection.MoneroConnectionModule;
import bisq.network.crypto.EncryptionServiceModule; import bisq.network.crypto.EncryptionServiceModule;
import bisq.network.p2p.P2PModule; import bisq.network.p2p.P2PModule;
import bisq.network.p2p.network.BridgeAddressProvider; import bisq.network.p2p.network.BridgeAddressProvider;
@ -41,8 +41,6 @@ import bisq.common.app.AppModule;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage; import bisq.common.crypto.KeyStorage;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.proto.network.NetworkProtoResolver; import bisq.common.proto.network.NetworkProtoResolver;
import bisq.common.proto.persistable.PersistenceProtoResolver; import bisq.common.proto.persistable.PersistenceProtoResolver;
@ -93,6 +91,6 @@ public class ModuleForAppWithP2p extends AppModule {
install(new BitcoinModule(config)); install(new BitcoinModule(config));
install(new AlertModule(config)); install(new AlertModule(config));
install(new FilterModule(config)); install(new FilterModule(config));
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class); install(new MoneroConnectionModule(config));
} }
} }

View File

@ -2,12 +2,14 @@ package bisq.core.btc.model;
import bisq.common.crypto.CryptoException; import bisq.common.crypto.CryptoException;
import bisq.common.crypto.Encryption; import bisq.common.crypto.Encryption;
import bisq.common.crypto.ScryptUtil;
import bisq.common.persistence.PersistenceManager; import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.proto.persistable.PersistedDataHost; import bisq.common.proto.persistable.PersistedDataHost;
import bisq.core.api.CoreAccountService; import bisq.core.api.CoreAccountService;
import bisq.core.api.model.EncryptedConnection; import bisq.core.api.model.EncryptedConnection;
import bisq.core.crypto.ScryptUtil; import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.HashMap; import java.util.HashMap;
@ -22,8 +24,6 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.inject.Inject; import javax.inject.Inject;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import lombok.NonNull; import lombok.NonNull;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.KeyCrypterScrypt;
@ -60,7 +60,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
transient private PersistenceManager<EncryptedConnectionList> persistenceManager; transient private PersistenceManager<EncryptedConnectionList> persistenceManager;
private final Map<String, EncryptedConnection> items = new HashMap<>(); private final Map<String, EncryptedConnection> items = new HashMap<>();
private @NonNull String currentConnectionUri = ""; private @NonNull String currentConnectionUrl = "";
private long refreshPeriod; private long refreshPeriod;
private boolean autoSwitch; private boolean autoSwitch;
@ -70,17 +70,16 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
this.accountService = accountService; this.accountService = accountService;
this.persistenceManager = persistenceManager; this.persistenceManager = persistenceManager;
this.persistenceManager.initialize(this, "EncryptedConnectionList", PersistenceManager.Source.PRIVATE); this.persistenceManager.initialize(this, "EncryptedConnectionList", PersistenceManager.Source.PRIVATE);
this.accountService.addPasswordChangeListener(this::onPasswordChange);
} }
private EncryptedConnectionList(byte[] salt, private EncryptedConnectionList(byte[] salt,
List<EncryptedConnection> items, List<EncryptedConnection> items,
@NonNull String currentConnectionUri, @NonNull String currentConnectionUrl,
long refreshPeriod, long refreshPeriod,
boolean autoSwitch) { boolean autoSwitch) {
this.keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(salt); this.keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(salt);
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUri, Function.identity()))); this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUrl, Function.identity())));
this.currentConnectionUri = currentConnectionUri; this.currentConnectionUrl = currentConnectionUrl;
this.refreshPeriod = refreshPeriod; this.refreshPeriod = refreshPeriod;
this.autoSwitch = autoSwitch; this.autoSwitch = autoSwitch;
} }
@ -93,9 +92,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
initializeEncryption(persistedEncryptedConnectionList.keyCrypterScrypt); initializeEncryption(persistedEncryptedConnectionList.keyCrypterScrypt);
items.clear(); items.clear();
items.putAll(persistedEncryptedConnectionList.items); items.putAll(persistedEncryptedConnectionList.items);
currentConnectionUri = persistedEncryptedConnectionList.currentConnectionUri; currentConnectionUrl = persistedEncryptedConnectionList.currentConnectionUrl;
refreshPeriod = persistedEncryptedConnectionList.refreshPeriod; refreshPeriod = persistedEncryptedConnectionList.refreshPeriod;
autoSwitch = persistedEncryptedConnectionList.autoSwitch; autoSwitch = persistedEncryptedConnectionList.autoSwitch;
} catch (Exception e) {
e.printStackTrace();
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }
@ -104,6 +105,8 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
writeLock.lock(); writeLock.lock();
try { try {
initializeEncryption(ScryptUtil.getKeyCrypterScrypt()); initializeEncryption(ScryptUtil.getKeyCrypterScrypt());
} catch (Exception e) {
e.printStackTrace();
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }
@ -203,11 +206,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
} }
} }
public void setCurrentConnectionUri(String currentConnectionUri) { public void setCurrentConnectionUri(String currentConnectionUrl) {
boolean changed; boolean changed;
writeLock.lock(); writeLock.lock();
try { try {
changed = !this.currentConnectionUri.equals(this.currentConnectionUri = currentConnectionUri == null ? "" : currentConnectionUri); changed = !this.currentConnectionUrl.equals(this.currentConnectionUrl = currentConnectionUrl == null ? "" : currentConnectionUrl);
} finally { } finally {
writeLock.unlock(); writeLock.unlock();
} }
@ -219,17 +222,54 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
public Optional<String> getCurrentConnectionUri() { public Optional<String> getCurrentConnectionUri() {
readLock.lock(); readLock.lock();
try { try {
return Optional.of(currentConnectionUri).filter(s -> !s.isEmpty()); return Optional.of(currentConnectionUrl).filter(s -> !s.isEmpty());
} finally { } finally {
readLock.unlock(); readLock.unlock();
} }
} }
private void requestPersistence() { public void requestPersistence() {
persistenceManager.requestPersistence(); persistenceManager.requestPersistence();
} }
private void onPasswordChange(String oldPassword, String newPassword) { @Override
public Message toProtoMessage() {
List<protobuf.EncryptedConnection> connections;
ByteString saltString;
String currentConnectionUrl;
boolean autoSwitchEnabled;
long refreshPeriod;
readLock.lock();
try {
connections = items.values().stream()
.map(EncryptedConnection::toProtoMessage).collect(Collectors.toList());
saltString = keyCrypterScrypt.getScryptParameters().getSalt();
currentConnectionUrl = this.currentConnectionUrl;
autoSwitchEnabled = this.autoSwitch;
refreshPeriod = this.refreshPeriod;
} finally {
readLock.unlock();
}
return protobuf.PersistableEnvelope.newBuilder()
.setEncryptedConnectionList(protobuf.EncryptedConnectionList.newBuilder()
.setSalt(saltString)
.addAllItems(connections)
.setCurrentConnectionUrl(currentConnectionUrl)
.setRefreshPeriod(refreshPeriod)
.setAutoSwitch(autoSwitchEnabled))
.build();
}
public static EncryptedConnectionList fromProto(protobuf.EncryptedConnectionList proto) {
List<EncryptedConnection> items = proto.getItemsList().stream()
.map(EncryptedConnection::fromProto)
.collect(Collectors.toList());
return new EncryptedConnectionList(proto.getSalt().toByteArray(), items, proto.getCurrentConnectionUrl(), proto.getRefreshPeriod(), proto.getAutoSwitch());
}
// ----------------------------- HELPERS ----------------------------------
public void changePassword(String oldPassword, String newPassword) {
writeLock.lock(); writeLock.lock();
try { try {
SecretKey oldSecret = encryptionKey; SecretKey oldSecret = encryptionKey;
@ -243,9 +283,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
} }
private SecretKey toSecretKey(String password) { private SecretKey toSecretKey(String password) {
if (password == null) { if (password == null) return null;
return null;
}
return Encryption.getSecretKeyFromBytes(keyCrypterScrypt.deriveKey(password).getKey()); return Encryption.getSecretKeyFromBytes(keyCrypterScrypt.deriveKey(password).getKey());
} }
@ -265,6 +303,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
} }
private static byte[] decrypt(byte[] encrypted, SecretKey secret) { private static byte[] decrypt(byte[] encrypted, SecretKey secret) {
if (secret == null) return encrypted; // no encryption
try { try {
return Encryption.decrypt(encrypted, secret); return Encryption.decrypt(encrypted, secret);
} catch (CryptoException e) { } catch (CryptoException e) {
@ -273,6 +312,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
} }
private static byte[] encrypt(byte[] unencrypted, SecretKey secretKey) { private static byte[] encrypt(byte[] unencrypted, SecretKey secretKey) {
if (secretKey == null) return unencrypted; // no encryption
try { try {
return Encryption.encrypt(unencrypted, secretKey); return Encryption.encrypt(unencrypted, secretKey);
} catch (CryptoException e) { } catch (CryptoException e) {
@ -286,7 +326,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
byte[] passwordSalt = generateSalt(passwordBytes); byte[] passwordSalt = generateSalt(passwordBytes);
byte[] encryptedPassword = encryptPassword(passwordBytes, passwordSalt); byte[] encryptedPassword = encryptPassword(passwordBytes, passwordSalt);
return EncryptedConnection.builder() return EncryptedConnection.builder()
.uri(connection.getUri()) .url(connection.getUri())
.username(connection.getUsername() == null ? "" : connection.getUsername()) .username(connection.getUsername() == null ? "" : connection.getUsername())
.encryptedPassword(encryptedPassword) .encryptedPassword(encryptedPassword)
.encryptionSalt(passwordSalt) .encryptionSalt(passwordSalt)
@ -298,7 +338,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
byte[] decryptedPasswordBytes = decryptPassword(connection.getEncryptedPassword(), connection.getEncryptionSalt()); byte[] decryptedPasswordBytes = decryptPassword(connection.getEncryptedPassword(), connection.getEncryptionSalt());
String password = decryptedPasswordBytes == null ? null : new String(decryptedPasswordBytes, StandardCharsets.UTF_8); String password = decryptedPasswordBytes == null ? null : new String(decryptedPasswordBytes, StandardCharsets.UTF_8);
String username = connection.getUsername().isEmpty() ? null : connection.getUsername(); String username = connection.getUsername().isEmpty() ? null : connection.getUsername();
MoneroRpcConnection moneroRpcConnection = new MoneroRpcConnection(connection.getUri(), username, password); MoneroRpcConnection moneroRpcConnection = new MoneroRpcConnection(connection.getUrl(), username, password);
moneroRpcConnection.setPriority(connection.getPriority()); moneroRpcConnection.setPriority(connection.getPriority());
return moneroRpcConnection; return moneroRpcConnection;
} }
@ -357,39 +397,4 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
} }
return true; return true;
} }
@Override
public Message toProtoMessage() {
List<protobuf.EncryptedConnection> connections;
ByteString saltString;
String currentConnectionUri;
boolean autoSwitchEnabled;
long refreshPeriod;
readLock.lock();
try {
connections = items.values().stream()
.map(EncryptedConnection::toProtoMessage).collect(Collectors.toList());
saltString = keyCrypterScrypt.getScryptParameters().getSalt();
currentConnectionUri = this.currentConnectionUri;
autoSwitchEnabled = this.autoSwitch;
refreshPeriod = this.refreshPeriod;
} finally {
readLock.unlock();
}
return protobuf.PersistableEnvelope.newBuilder()
.setEncryptedConnectionList(protobuf.EncryptedConnectionList.newBuilder()
.setSalt(saltString)
.addAllItems(connections)
.setCurrentConnectionUri(currentConnectionUri)
.setRefreshPeriod(refreshPeriod)
.setAutoSwitch(autoSwitchEnabled))
.build();
}
public static EncryptedConnectionList fromProto(protobuf.EncryptedConnectionList proto) {
List<EncryptedConnection> items = proto.getItemsList().stream()
.map(EncryptedConnection::fromProto)
.collect(Collectors.toList());
return new EncryptedConnectionList(proto.getSalt().toByteArray(), items, proto.getCurrentConnectionUri(), proto.getRefreshPeriod(), proto.getAutoSwitch());
}
} }

View File

@ -27,8 +27,6 @@ import com.google.inject.Inject;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.math.BigInteger;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -37,10 +35,6 @@ import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroWalletListener;
/** /**
* The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the * The AddressEntries was previously stored as list, now as hashSet. We still keep the old name to reflect the
* associated protobuf message. * associated protobuf message.
@ -48,7 +42,6 @@ import monero.wallet.model.MoneroWalletListener;
@Slf4j @Slf4j
public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost { public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost {
transient private PersistenceManager<XmrAddressEntryList> persistenceManager; transient private PersistenceManager<XmrAddressEntryList> persistenceManager;
transient private MoneroWallet wallet;
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>(); private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
@Inject @Inject
@ -100,61 +93,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
// API // API
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void onWalletReady(MoneroWallet wallet) {
this.wallet = wallet;
if (!entrySet.isEmpty()) {
// Set<XmrAddressEntry> toBeRemoved = new HashSet<>();
// entrySet.forEach(addressEntry -> {
// DeterministicKey keyFromPubHash = (DeterministicKey) wallet.findKeyFromPubKeyHash(
// addressEntry.getPubKeyHash(),
// Script.ScriptType.P2PKH);
// if (keyFromPubHash != null) {
// Address addressFromKey = LegacyAddress.fromKey(Config.baseCurrencyNetworkParameters(), keyFromPubHash);
// // We want to ensure key and address matches in case we have address in entry available already
// if (addressEntry.getAddress() == null || addressFromKey.equals(addressEntry.getAddress())) {
// addressEntry.setDeterministicKey(keyFromPubHash);
// } else {
// log.error("We found an address entry without key but cannot apply the key as the address " +
// "is not matching. " +
// "We remove that entry as it seems it is not compatible with our wallet. " +
// "addressFromKey={}, addressEntry.getAddress()={}",
// addressFromKey, addressEntry.getAddress());
// toBeRemoved.add(addressEntry);
// }
// } else {
// log.error("Key from addressEntry {} not found in that wallet. We remove that entry. " +
// "This is expected at restore from seeds.", addressEntry.toString());
// toBeRemoved.add(addressEntry);
// }
// });
//
// toBeRemoved.forEach(entrySet::remove);
}
// In case we restore from seed words and have balance we need to add the relevant addresses to our list.
// IssuedReceiveAddresses does not contain all addresses where we expect balance so we need to listen to
// incoming txs at blockchain sync to add the rest.
if (wallet.getBalance().compareTo(new BigInteger("0")) > 0) {
wallet.getAccounts().forEach(acct -> {
log.info("Create XmrAddressEntry for IssuedReceiveAddress. address={}", acct.getPrimaryAddress());
if (acct.getIndex() != 0) entrySet.add(new XmrAddressEntry(acct.getIndex(), acct.getPrimaryAddress(), XmrAddressEntry.Context.AVAILABLE));
});
}
// We add those listeners to get notified about potential new transactions and
// add an address entry list in case it does not exist yet. This is mainly needed for restore from seed words
// but can help as well in case the addressEntry list would miss an address where the wallet was received
// funds (e.g. if the user sends funds to an address which has not been provided in the main UI - like from the
// wallet details window).
wallet.addListener(new MoneroWalletListener() {
@Override public void onOutputReceived(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
@Override public void onOutputSpent(MoneroOutputWallet output) { maybeAddNewAddressEntry(output); }
});
requestPersistence();
}
public ImmutableList<XmrAddressEntry> getAddressEntriesAsListImmutable() { public ImmutableList<XmrAddressEntry> getAddressEntriesAsListImmutable() {
return ImmutableList.copyOf(entrySet); return ImmutableList.copyOf(entrySet);
} }
@ -202,25 +140,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
persistenceManager.requestPersistence(); persistenceManager.requestPersistence();
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): this should be removed since only using account 0
private void maybeAddNewAddressEntry(MoneroOutputWallet output) {
if (output.getAccountIndex() == 0) return;
String address = wallet.getAddress(output.getAccountIndex(), output.getSubaddressIndex());
if (!isAddressInEntries(address)) addAddressEntry(new XmrAddressEntry(output.getAccountIndex(), address, XmrAddressEntry.Context.AVAILABLE));
}
private boolean isAddressInEntries(String address) {
for (XmrAddressEntry entry : entrySet) {
if (entry.getAddressString().equals(address)) return true;
}
return false;
}
@Override @Override
public String toString() { public String toString() {
return "XmrAddressEntryList{" + return "XmrAddressEntryList{" +

View File

@ -8,14 +8,14 @@ import javafx.beans.property.SimpleDoubleProperty;
import java.util.Date; import java.util.Date;
class DownloadListener { public class DownloadListener {
private final DoubleProperty percentage = new SimpleDoubleProperty(-1); private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
protected void progress(double percentage, int blocksLeft, Date date) { public void progress(double percentage, int blocksLeft, Date date) {
UserThread.execute(() -> this.percentage.set(percentage / 100d)); UserThread.execute(() -> this.percentage.set(percentage / 100d));
} }
protected void doneDownload() { public void doneDownload() {
UserThread.execute(() -> this.percentage.set(1d)); UserThread.execute(() -> this.percentage.set(1d));
} }

View File

@ -17,7 +17,6 @@
package bisq.core.btc.setup; package bisq.core.btc.setup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.nodes.ProxySocketFactory; import bisq.core.btc.nodes.ProxySocketFactory;
import bisq.core.btc.wallet.HavenoRiskAnalysis; import bisq.core.btc.wallet.HavenoRiskAnalysis;
@ -72,9 +71,6 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -89,15 +85,6 @@ import static bisq.common.util.Preconditions.checkDir;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import monero.common.MoneroRpcConnection;
import monero.common.MoneroUtils;
import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc;
import monero.daemon.model.MoneroNetworkType;
import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletRpc;
import monero.wallet.model.MoneroWalletConfig;
/** /**
* <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory * <p>Utility class that wraps the boilerplate needed to set up a new SPV bitcoinj app. Instantiate it with a directory
* and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will * and file prefix, optionally configure a few things, then use startAsync and optionally awaitRunning. The object will
@ -125,27 +112,13 @@ public class WalletConfig extends AbstractIdleService {
protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class); protected static final Logger log = LoggerFactory.getLogger(WalletConfig.class);
// Monero configuration
// TODO: don't hard code configuration, inject into classes?
private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
private static final String MONERO_WALLET_RPC_PATH = MONERO_WALLET_RPC_DIR + File.separator + "monero-wallet-rpc";
private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user";
private static final String MONERO_WALLET_RPC_PASSWORD = "abc123";
private static final long MONERO_WALLET_SYNC_RATE = 5000l;
protected final NetworkParameters params; protected final NetworkParameters params;
protected final String filePrefix; protected final String filePrefix;
protected final CoreMoneroConnectionsService moneroConnectionsManager;
protected volatile BlockChain vChain; protected volatile BlockChain vChain;
protected volatile SPVBlockStore vStore; protected volatile SPVBlockStore vStore;
protected volatile MoneroDaemonRpc vXmrDaemon;
protected volatile MoneroWalletRpc vXmrWallet;
protected volatile Wallet vBtcWallet; protected volatile Wallet vBtcWallet;
protected volatile PeerGroup vPeerGroup; protected volatile PeerGroup vPeerGroup;
protected final int rpcBindPort;
protected final File directory; protected final File directory;
protected volatile File vXmrWalletFile; protected volatile File vXmrWalletFile;
protected volatile File vBtcWalletFile; protected volatile File vBtcWalletFile;
@ -176,21 +149,17 @@ public class WalletConfig extends AbstractIdleService {
*/ */
public WalletConfig(NetworkParameters params, public WalletConfig(NetworkParameters params,
File directory, File directory,
int rpcBindPort,
CoreMoneroConnectionsService connectionsManager,
String filePrefix) { String filePrefix) {
this(new Context(params), directory, rpcBindPort, connectionsManager, filePrefix); this(new Context(params), directory, filePrefix);
} }
/** /**
* Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory. * Creates a new WalletConfig, with the given {@link Context}. Files will be stored in the given directory.
*/ */
private WalletConfig(Context context, File directory, int rpcBindPort, CoreMoneroConnectionsService connectionsManager, String filePrefix) { private WalletConfig(Context context, File directory, String filePrefix) {
this.context = context; this.context = context;
this.params = checkNotNull(context.getParams()); this.params = checkNotNull(context.getParams());
this.directory = checkDir(directory); this.directory = checkDir(directory);
this.rpcBindPort = rpcBindPort;
this.moneroConnectionsManager = connectionsManager;
this.filePrefix = checkNotNull(filePrefix); this.filePrefix = checkNotNull(filePrefix);
} }
@ -293,85 +262,6 @@ public class WalletConfig extends AbstractIdleService {
// Meant to be overridden by subclasses // Meant to be overridden by subclasses
} }
public boolean walletExists(String walletName) {
String path = directory.toString() + File.separator + walletName;
return new File(path + ".keys").exists();
}
public MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
// create wallet
try {
walletRpc.createWallet(config);
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
throw e;
}
}
public MoneroWalletRpc openWallet(MoneroWalletConfig config, Integer port) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
// open wallet
try {
walletRpc.openWallet(config);
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
throw e;
}
}
private MoneroWalletRpc startWalletRpcInstance(Integer port) {
// check if monero-wallet-rpc exists
if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH + "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system");
// get app's current daemon connection
MoneroRpcConnection connection = moneroConnectionsManager.getConnection();
// start monero-wallet-rpc instance and return connected client
List<String> cmd = new ArrayList<>(Arrays.asList( // modifiable list
MONERO_WALLET_RPC_PATH,
"--" + MONERO_NETWORK_TYPE.toString().toLowerCase(),
"--daemon-address", connection.getUri(),
"--rpc-login", MONERO_WALLET_RPC_USERNAME + ":" + MONERO_WALLET_RPC_PASSWORD,
"--wallet-dir", directory.toString()
));
if (connection.getUsername() != null) {
cmd.add("--daemon-login");
cmd.add(connection.getUsername() + ":" + connection.getPassword());
}
if (port != null && port > 0) {
cmd.add("--rpc-bind-port");
cmd.add(Integer.toString(port));
}
return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
}
public void closeWallet(MoneroWallet walletRpc, boolean save) {
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc, save);
}
public void deleteWallet(String walletName) {
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
String path = directory.toString() + File.separator + walletName;
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
//WalletsSetup.deleteRollingBackup(walletName); // TODO (woodser): necessary to delete rolling backup?
}
@Override @Override
protected void startUp() throws Exception { protected void startUp() throws Exception {
// Runs in a separate thread. // Runs in a separate thread.
@ -380,31 +270,6 @@ public class WalletConfig extends AbstractIdleService {
File chainFile = new File(directory, filePrefix + ".spvchain"); File chainFile = new File(directory, filePrefix + ".spvchain");
boolean chainFileExists = chainFile.exists(); boolean chainFileExists = chainFile.exists();
// set XMR daemon and listen for updates
vXmrDaemon = new MoneroDaemonRpc(moneroConnectionsManager.getConnection());
moneroConnectionsManager.addConnectionListener(newConnection -> {
vXmrDaemon = newConnection == null ? null : new MoneroDaemonRpc(newConnection);
});
// XMR wallet
String xmrPrefix = "_XMR";
vXmrWalletFile = new File(directory, filePrefix + xmrPrefix);
if (MoneroUtils.walletExists(vXmrWalletFile.getPath())) {
vXmrWallet = openWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"), rpcBindPort);
} else {
vXmrWallet = createWallet(new MoneroWalletConfig().setPath(filePrefix + xmrPrefix).setPassword("abctesting123"), rpcBindPort);
}
System.out.println("Monero wallet path: " + vXmrWallet.getPath());
System.out.println("Monero wallet address: " + vXmrWallet.getPrimaryAddress());
System.out.println("Monero wallet uri: " + vXmrWallet.getRpcConnection().getUri());
// vXmrWallet.rescanSpent();
// vXmrWallet.rescanBlockchain();
vXmrWallet.sync(); // blocking
downloadListener.doneDownload();
vXmrWallet.save();
System.out.println("Loaded wallet balance: " + vXmrWallet.getBalance(0));
System.out.println("Loaded wallet unlocked balance: " + vXmrWallet.getUnlockedBalance(0));
String btcPrefix = "_BTC"; String btcPrefix = "_BTC";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet"); vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null; boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
@ -647,16 +512,6 @@ public class WalletConfig extends AbstractIdleService {
return vBtcWallet; return vBtcWallet;
} }
public MoneroDaemon getXmrDaemon() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vXmrDaemon;
}
public MoneroWallet getXmrWallet() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vXmrWallet;
}
public PeerGroup peerGroup() { public PeerGroup peerGroup() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete"); checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vPeerGroup; return vPeerGroup;

View File

@ -17,12 +17,10 @@
package bisq.core.btc.setup; package bisq.core.btc.setup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.exceptions.InvalidHostException; import bisq.core.btc.exceptions.InvalidHostException;
import bisq.core.btc.exceptions.RejectedTxException; import bisq.core.btc.exceptions.RejectedTxException;
import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList; import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.nodes.BtcNetworkConfig; import bisq.core.btc.nodes.BtcNetworkConfig;
import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.BtcNodes.BtcNode; import bisq.core.btc.nodes.BtcNodes.BtcNode;
@ -66,14 +64,11 @@ import org.apache.commons.lang3.StringUtils;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty; import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty; import javafx.beans.property.LongProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@ -100,9 +95,6 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import monero.daemon.MoneroDaemon;
import monero.daemon.model.MoneroPeer;
import monero.wallet.MoneroWallet;
// Setup wallets and use WalletConfig for BitcoinJ wiring. // Setup wallets and use WalletConfig for BitcoinJ wiring.
// Other like WalletConfig we are here always on the user thread. That is one reason why we do not // Other like WalletConfig we are here always on the user thread. That is one reason why we do not
@ -111,8 +103,7 @@ import monero.wallet.MoneroWallet;
public class WalletsSetup { public class WalletsSetup {
public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_haveno_BTC.wallet.backup"; public static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_haveno_BTC.wallet.backup";
private static final int MIN_BROADCAST_CONNECTIONS = 2; private static final int MIN_BROADCAST_CONNECTIONS = 0;
private static final long DAEMON_POLL_INTERVAL_SECONDS = 20;
@Getter @Getter
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty(); public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
@ -122,25 +113,20 @@ public class WalletsSetup {
private final RegTestHost regTestHost; private final RegTestHost regTestHost;
private final AddressEntryList addressEntryList; private final AddressEntryList addressEntryList;
private final XmrAddressEntryList xmrAddressEntryList;
private final Preferences preferences; private final Preferences preferences;
private final Socks5ProxyProvider socks5ProxyProvider; private final Socks5ProxyProvider socks5ProxyProvider;
private final Config config; private final Config config;
private final LocalBitcoinNode localBitcoinNode; private final LocalBitcoinNode localBitcoinNode;
private final BtcNodes btcNodes; private final BtcNodes btcNodes;
@Getter
private final CoreMoneroConnectionsService moneroConnectionsManager;
private final String xmrWalletFileName;
private final int numConnectionsForBtc; private final int numConnectionsForBtc;
private final String userAgent; private final String userAgent;
private final NetworkParameters params; private final NetworkParameters params;
private final File walletDir; private final File walletDir;
private final int walletRpcBindPort;
private final int socks5DiscoverMode; private final int socks5DiscoverMode;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0); private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final LongProperty chainHeight = new SimpleLongProperty(0); private final LongProperty chainHeight = new SimpleLongProperty(0);
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
private final DownloadListener downloadListener = new DownloadListener(); private final DownloadListener downloadListener = new DownloadListener();
private final List<Runnable> setupTaskHandlers = new ArrayList<>();
private final List<Runnable> setupCompletedHandlers = new ArrayList<>(); private final List<Runnable> setupCompletedHandlers = new ArrayList<>();
public final BooleanProperty shutDownComplete = new SimpleBooleanProperty(); public final BooleanProperty shutDownComplete = new SimpleBooleanProperty();
private final boolean useAllProvidedNodes; private final boolean useAllProvidedNodes;
@ -153,36 +139,29 @@ public class WalletsSetup {
@Inject @Inject
public WalletsSetup(RegTestHost regTestHost, public WalletsSetup(RegTestHost regTestHost,
AddressEntryList addressEntryList, AddressEntryList addressEntryList,
XmrAddressEntryList xmrAddressEntryList,
Preferences preferences, Preferences preferences,
Socks5ProxyProvider socks5ProxyProvider, Socks5ProxyProvider socks5ProxyProvider,
Config config, Config config,
LocalBitcoinNode localBitcoinNode, LocalBitcoinNode localBitcoinNode,
BtcNodes btcNodes, BtcNodes btcNodes,
CoreMoneroConnectionsService moneroConnectionsManager,
@Named(Config.USER_AGENT) String userAgent, @Named(Config.USER_AGENT) String userAgent,
@Named(Config.WALLET_DIR) File walletDir, @Named(Config.WALLET_DIR) File walletDir,
@Named(Config.WALLET_RPC_BIND_PORT) int walletRpcBindPort,
@Named(Config.USE_ALL_PROVIDED_NODES) boolean useAllProvidedNodes, @Named(Config.USE_ALL_PROVIDED_NODES) boolean useAllProvidedNodes,
@Named(Config.NUM_CONNECTIONS_FOR_BTC) int numConnectionsForBtc, @Named(Config.NUM_CONNECTIONS_FOR_BTC) int numConnectionsForBtc,
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) { @Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
this.regTestHost = regTestHost; this.regTestHost = regTestHost;
this.addressEntryList = addressEntryList; this.addressEntryList = addressEntryList;
this.xmrAddressEntryList = xmrAddressEntryList;
this.preferences = preferences; this.preferences = preferences;
this.socks5ProxyProvider = socks5ProxyProvider; this.socks5ProxyProvider = socks5ProxyProvider;
this.config = config; this.config = config;
this.localBitcoinNode = localBitcoinNode; this.localBitcoinNode = localBitcoinNode;
this.btcNodes = btcNodes; this.btcNodes = btcNodes;
this.moneroConnectionsManager = moneroConnectionsManager;
this.numConnectionsForBtc = numConnectionsForBtc; this.numConnectionsForBtc = numConnectionsForBtc;
this.useAllProvidedNodes = useAllProvidedNodes; this.useAllProvidedNodes = useAllProvidedNodes;
this.userAgent = userAgent; this.userAgent = userAgent;
this.socks5DiscoverMode = evaluateMode(socks5DiscoverModeString); this.socks5DiscoverMode = evaluateMode(socks5DiscoverModeString);
this.walletDir = walletDir; this.walletDir = walletDir;
this.walletRpcBindPort = walletRpcBindPort;
xmrWalletFileName = "haveno_" + config.baseCurrencyNetwork.getCurrencyCode();
params = Config.baseCurrencyNetworkParameters(); params = Config.baseCurrencyNetworkParameters();
PeerGroup.setIgnoreHttpSeeds(true); PeerGroup.setIgnoreHttpSeeds(true);
} }
@ -206,31 +185,23 @@ public class WalletsSetup {
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " + exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT); STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT);
// initialize Monero connection manager
moneroConnectionsManager.initialize();
backupWallets(); backupWallets();
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null; final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy); log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
walletConfig = new WalletConfig(params, walletDir, walletRpcBindPort, moneroConnectionsManager, "haveno") { walletConfig = new WalletConfig(params, walletDir, "haveno") {
@Override @Override
protected void onSetupCompleted() { protected void onSetupCompleted() {
//We are here in the btcj thread Thread[ STARTING,5,main] //We are here in the btcj thread Thread[ STARTING,5,main]
super.onSetupCompleted(); super.onSetupCompleted();
final PeerGroup peerGroup = walletConfig.peerGroup(); final PeerGroup peerGroup = walletConfig.peerGroup();
final BlockChain chain = walletConfig.chain();
// We don't want to get our node white list polluted with nodes from AddressMessage calls. // We don't want to get our node white list polluted with nodes from AddressMessage calls.
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty()) if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
peerGroup.setAddPeersFromAddressMessage(false); peerGroup.setAddPeersFromAddressMessage(false);
UserThread.runPeriodically(() -> {
updateDaemonInfo();
}, DAEMON_POLL_INTERVAL_SECONDS);
// Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener // Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener
peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> { peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> {
if (message instanceof RejectMessage) { if (message instanceof RejectMessage) {
@ -244,11 +215,12 @@ public class WalletsSetup {
return message; return message;
}); });
// run external startup handlers
setupTaskHandlers.forEach(Runnable::run);
// Map to user thread // Map to user thread
UserThread.execute(() -> { UserThread.execute(() -> {
updateDaemonInfo();
addressEntryList.onWalletReady(walletConfig.btcWallet()); addressEntryList.onWalletReady(walletConfig.btcWallet());
xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet());
timeoutTimer.stop(); timeoutTimer.stop();
setupCompletedHandlers.forEach(Runnable::run); setupCompletedHandlers.forEach(Runnable::run);
}); });
@ -256,23 +228,6 @@ public class WalletsSetup {
// onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay // onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay
UserThread.runAfter(resultHandler::handleResult, 100, TimeUnit.MILLISECONDS); UserThread.runAfter(resultHandler::handleResult, 100, TimeUnit.MILLISECONDS);
} }
private void updateDaemonInfo() {
try {
if (vXmrDaemon == null) throw new RuntimeException("No daemon connection");
peers.set(getOnlinePeers());
numPeers.set(peers.get().size());
chainHeight.set(vXmrDaemon.getHeight());
} catch (Exception e) {
log.warn("Could not update daemon info: " + e.getMessage());
}
}
private List<MoneroPeer> getOnlinePeers() {
return vXmrDaemon.getPeers().stream()
.filter(peer -> peer.isOnline())
.collect(Collectors.toList());
}
}; };
walletConfig.setSocks5Proxy(socks5Proxy); walletConfig.setSocks5Proxy(socks5Proxy);
walletConfig.setConfig(config); walletConfig.setConfig(config);
@ -427,9 +382,7 @@ public class WalletsSetup {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void backupWallets() { public void backupWallets() {
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20); // TODO: remove?
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
} }
public void clearBackups() { public void clearBackups() {
@ -479,6 +432,10 @@ public class WalletsSetup {
// Handlers // Handlers
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void addSetupTaskHandler(Runnable handler) {
setupTaskHandlers.add(handler);
}
public void addSetupCompletedHandler(Runnable handler) { public void addSetupCompletedHandler(Runnable handler) {
setupCompletedHandlers.add(handler); setupCompletedHandlers.add(handler);
} }
@ -492,14 +449,6 @@ public class WalletsSetup {
return walletConfig.btcWallet(); return walletConfig.btcWallet();
} }
public MoneroDaemon getXmrDaemon() {
return walletConfig.getXmrDaemon();
}
public MoneroWallet getXmrWallet() {
return walletConfig.getXmrWallet();
}
public NetworkParameters getParams() { public NetworkParameters getParams() {
return params; return params;
} }
@ -521,10 +470,6 @@ public class WalletsSetup {
return numPeers; return numPeers;
} }
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
return peers;
}
public LongProperty chainHeightProperty() { public LongProperty chainHeightProperty() {
return chainHeight; return chainHeight;
} }
@ -538,14 +483,7 @@ public class WalletsSetup {
} }
public boolean isChainHeightSyncedWithinTolerance() { public boolean isChainHeightSyncedWithinTolerance() {
Long peersChainHeight = walletConfig.vXmrDaemon.getSyncInfo().getTargetHeight(); throw new RuntimeException("WalletsSetup.isChainHeightSyncedWithinTolerance() not implemented for BTC");
if (peersChainHeight == 0) return true; // monero-daemon-rpc sync_info's target_height returns 0 when node is fully synced
long bestChainHeight = chainHeight.get();
if (Math.abs(peersChainHeight - bestChainHeight) <= 3) {
return true;
}
log.warn("Our chain height: {} is out of sync with peer nodes chain height: {}", chainHeight.get(), peersChainHeight);
return false;
} }
public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) { public Set<Address> getAddressesByContext(@SuppressWarnings("SameParameterValue") AddressEntry.Context context) {

View File

@ -20,15 +20,12 @@ package bisq.core.btc.wallet;
import bisq.core.btc.exceptions.SigningException; import bisq.core.btc.exceptions.SigningException;
import bisq.core.btc.exceptions.TransactionVerificationException; import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.InputsAndChangeOutput; import bisq.core.btc.model.InputsAndChangeOutput;
import bisq.core.btc.model.PreparedDepositTxAndMakerInputs; import bisq.core.btc.model.PreparedDepositTxAndMakerInputs;
import bisq.core.btc.model.RawTransactionInput; import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletConfig; import bisq.core.btc.setup.WalletConfig;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Res;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.util.ParsingUtils;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
@ -37,7 +34,6 @@ import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.SegwitAddress; import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Sha256Hash;
@ -78,11 +74,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.MoneroWallet;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroTxConfig;
import monero.wallet.model.MoneroTxWallet;
public class TradeWalletService { public class TradeWalletService {
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class); private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000); private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
@ -94,8 +85,6 @@ public class TradeWalletService {
@Nullable @Nullable
private Wallet wallet; private Wallet wallet;
@Nullable @Nullable
private MoneroWallet xmrWallet;
@Nullable
private WalletConfig walletConfig; private WalletConfig walletConfig;
@Nullable @Nullable
private KeyParameter aesKey; private KeyParameter aesKey;
@ -113,7 +102,6 @@ public class TradeWalletService {
walletsSetup.addSetupCompletedHandler(() -> { walletsSetup.addSetupCompletedHandler(() -> {
walletConfig = walletsSetup.getWalletConfig(); walletConfig = walletsSetup.getWalletConfig();
wallet = walletsSetup.getBtcWallet(); wallet = walletsSetup.getBtcWallet();
xmrWallet = walletsSetup.getXmrWallet();
}); });
} }
@ -132,25 +120,6 @@ public class TradeWalletService {
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Trade fee
///////////////////////////////////////////////////////////////////////////////////////////
public MoneroTxWallet createXmrTradingFeeTx(
String reservedForTradeAddress,
Coin reservedFundsForOffer,
Coin makerFee,
Coin txFee,
String feeReceiver,
boolean broadcastTx) {
return xmrWallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setDestinations(
new MoneroDestination(feeReceiver, ParsingUtils.coinToAtomicUnits(makerFee)),
new MoneroDestination(reservedForTradeAddress, ParsingUtils.coinToAtomicUnits(reservedFundsForOffer)))
.setRelay(broadcastTx));
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Deposit tx // Deposit tx
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -1061,16 +1030,6 @@ public class TradeWalletService {
// Misc // Misc
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
*
* @param txHash the transaction hash of the transaction we want to lookup
*/
public MoneroTxWallet getWalletTx(String txHash) {
checkNotNull(xmrWallet);
return xmrWallet.getTx(txHash);
}
/** /**
* Returns the local existing wallet transaction with the given ID, or {@code null} if missing. * Returns the local existing wallet transaction with the given ID, or {@code null} if missing.
* *

View File

@ -18,9 +18,8 @@
package bisq.core.btc.wallet; package bisq.core.btc.wallet;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.crypto.ScryptUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
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;

View File

@ -1,40 +1,43 @@
package bisq.core.btc.wallet; package bisq.core.btc.wallet;
import static com.google.common.base.Preconditions.checkState;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.core.btc.exceptions.AddressEntryException; import bisq.common.config.Config;
import bisq.common.file.FileUtil;
import bisq.core.api.AccountServiceListener;
import bisq.core.api.CoreAccountService;
import bisq.core.api.CoreMoneroConnectionsService;
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;
import bisq.core.btc.setup.MoneroWalletRpcManager;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
import bisq.core.util.ParsingUtils; import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import org.bitcoinj.core.AddressFormatException; import com.google.common.util.concurrent.Service.State;
import org.bitcoinj.core.Coin; import com.google.inject.name.Named;
import org.bitcoinj.core.InsufficientMoneyException; import java.io.File;
import javax.inject.Inject;
import com.google.common.util.concurrent.FutureCallback;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter;
import monero.common.MoneroRpcConnection; import monero.common.MoneroRpcConnection;
import monero.common.MoneroUtils;
import monero.daemon.MoneroDaemon; import monero.daemon.MoneroDaemon;
import monero.daemon.model.MoneroNetworkType;
import monero.wallet.MoneroWallet; import monero.wallet.MoneroWallet;
import monero.wallet.MoneroWalletRpc;
import monero.wallet.model.MoneroDestination; import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroOutputWallet; import monero.wallet.model.MoneroOutputWallet;
import monero.wallet.model.MoneroSubaddress; import monero.wallet.model.MoneroSubaddress;
@ -44,123 +47,401 @@ import monero.wallet.model.MoneroTxWallet;
import monero.wallet.model.MoneroWalletConfig; import monero.wallet.model.MoneroWalletConfig;
import monero.wallet.model.MoneroWalletListener; import monero.wallet.model.MoneroWalletListener;
import monero.wallet.model.MoneroWalletListenerI; import monero.wallet.model.MoneroWalletListenerI;
import org.bitcoinj.core.Coin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class XmrWalletService { public class XmrWalletService {
private static final Logger log = LoggerFactory.getLogger(XmrWalletService.class); private static final Logger log = LoggerFactory.getLogger(XmrWalletService.class);
private WalletsSetup walletsSetup; // Monero configuration
private final XmrAddressEntryList addressEntryList; // TODO: don't hard code configuration, inject into classes?
private static final MoneroNetworkType MONERO_NETWORK_TYPE = MoneroNetworkType.STAGENET;
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
private static final String MONERO_WALLET_RPC_PATH = MONERO_WALLET_RPC_DIR + File.separator + "monero-wallet-rpc";
private static final String MONERO_WALLET_RPC_USERNAME = "haveno_user";
private static final String MONERO_WALLET_RPC_DEFAULT_PASSWORD = "password"; // only used if account password is null
private static final String MONERO_WALLET_NAME = "haveno_XMR";
private static final long MONERO_WALLET_SYNC_RATE = 5000l;
private final CoreAccountService accountService;
private final CoreMoneroConnectionsService connectionsService;
private final XmrAddressEntryList xmrAddressEntryList;
private final WalletsSetup walletsSetup;
private final File walletDir;
private final File xmrWalletFile;
private final int rpcBindPort;
protected final CopyOnWriteArraySet<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>(); protected final CopyOnWriteArraySet<XmrBalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>(); protected final CopyOnWriteArraySet<MoneroWalletListenerI> walletListeners = new CopyOnWriteArraySet<>();
private TradeManager tradeManager;
private MoneroWallet wallet;
private Map<String, MoneroWallet> multisigWallets; private Map<String, MoneroWallet> multisigWallets;
@Getter
private MoneroWallet wallet;
@Inject @Inject
XmrWalletService(WalletsSetup walletsSetup, XmrWalletService(CoreAccountService accountService,
XmrAddressEntryList addressEntryList) { CoreMoneroConnectionsService connectionsService,
WalletsSetup walletsSetup,
XmrAddressEntryList xmrAddressEntryList,
@Named(Config.WALLET_DIR) File walletDir,
@Named(Config.WALLET_RPC_BIND_PORT) int rpcBindPort) {
this.accountService = accountService;
this.connectionsService = connectionsService;
this.walletsSetup = walletsSetup; this.walletsSetup = walletsSetup;
this.xmrAddressEntryList = xmrAddressEntryList;
this.addressEntryList = addressEntryList;
this.multisigWallets = new HashMap<String, MoneroWallet>(); this.multisigWallets = new HashMap<String, MoneroWallet>();
this.walletDir = walletDir;
this.rpcBindPort = rpcBindPort;
this.xmrWalletFile = new File(walletDir, MONERO_WALLET_NAME);
walletsSetup.addSetupCompletedHandler(() -> { // initialize after account open and basic setup
wallet = walletsSetup.getXmrWallet(); walletsSetup.addSetupTaskHandler(() -> { // TODO: use something better than legacy WalletSetup for notification to initialize
wallet.addListener(new MoneroWalletListener() {
@Override // initialize
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { } initialize();
// listen for account updates
accountService.addListener(new AccountServiceListener() {
@Override @Override
public void onNewBlock(long height) { } public void onAccountCreated() {
log.info(getClass() + ".accountService.onAccountCreated()");
initialize();
}
@Override @Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { public void onAccountOpened() {
notifyBalanceListeners(); log.info(getClass() + ".accountService.onAccountOpened()");
initialize();
}
@Override
public void onAccountClosed() {
log.info(getClass() + ".accountService.onAccountClosed()");
closeAllWallets();
}
@Override
public void onPasswordChanged(String oldPassword, String newPassword) {
log.info(getClass() + "accountservice.onPasswordChanged()");
changeWalletPasswords(oldPassword, newPassword);
} }
}); });
});
}
walletsSetup.getMoneroConnectionsManager().addConnectionListener(newConnection -> { // TODO (woodser): need trade manager to get trade ids to change all wallet passwords?
updateDaemonConnections(newConnection); public void setTradeManager(TradeManager tradeManager) {
}); this.tradeManager = tradeManager;
}); }
public MoneroWallet getWallet() {
State state = walletsSetup.getWalletConfig().state();
checkState(state == State.STARTING || state == State.RUNNING, "Cannot call until startup is complete");
return wallet;
} }
public MoneroDaemon getDaemon() { public MoneroDaemon getDaemon() {
return walletsSetup.getXmrDaemon(); return connectionsService.getDaemon();
} }
// TODO (woodser): wallet has single password which is passed here? public CoreMoneroConnectionsService getConnectionsService() {
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse return connectionsService;
}
public String getWalletPassword() {
return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword();
}
public boolean walletExists(String walletName) {
String path = walletDir.toString() + File.separator + walletName;
return new File(path + ".keys").exists();
}
public MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
// create wallet
try {
walletRpc.createWallet(config);
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
throw e;
}
}
public MoneroWalletRpc openWallet(MoneroWalletConfig config, Integer port) {
// start monero-wallet-rpc instance
MoneroWalletRpc walletRpc = startWalletRpcInstance(port);
// open wallet
try {
walletRpc.openWallet(config);
walletRpc.startSyncing(MONERO_WALLET_SYNC_RATE);
return walletRpc;
} catch (Exception e) {
e.printStackTrace();
MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
throw e;
}
}
private MoneroWalletRpc startWalletRpcInstance(Integer port) {
// check if monero-wallet-rpc exists
if (!new File(MONERO_WALLET_RPC_PATH).exists()) throw new Error("monero-wallet-rpc executable doesn't exist at path " + MONERO_WALLET_RPC_PATH
+ "; copy monero-wallet-rpc to the project root or set WalletConfig.java MONERO_WALLET_RPC_PATH for your system");
// get app's current daemon connection
MoneroRpcConnection connection = connectionsService.getConnection();
// start monero-wallet-rpc instance and return connected client
List<String> cmd = new ArrayList<>(Arrays.asList( // modifiable list
MONERO_WALLET_RPC_PATH, "--" + MONERO_NETWORK_TYPE.toString().toLowerCase(), "--daemon-address", connection.getUri(), "--rpc-login",
MONERO_WALLET_RPC_USERNAME + ":" + getWalletPassword(), "--wallet-dir", walletDir.toString()));
if (connection.getUsername() != null) {
cmd.add("--daemon-login");
cmd.add(connection.getUsername() + ":" + connection.getPassword());
}
if (port != null && port > 0) {
cmd.add("--rpc-bind-port");
cmd.add(Integer.toString(port));
}
return MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
}
public void closeWallet(MoneroWallet walletRpc, boolean save) {
log.info("{}.closeWallet({}, {})", getClass(), walletRpc.getPath(), save);
MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc, save);
}
public void deleteWallet(String walletName) {
log.info("{}.deleteWallet({})", getClass(), walletName);
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
String path = walletDir.toString() + File.separator + walletName;
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
// WalletsSetup.deleteRollingBackup(walletName); // TODO (woodser): necessary to delete rolling backup?
}
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
public synchronized MoneroWallet createMultisigWallet(String tradeId) { public synchronized MoneroWallet createMultisigWallet(String tradeId) {
log.info("{}.createMultisigWallet({})", getClass(), tradeId);
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
String path = "xmr_multisig_trade_" + tradeId; String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null; MoneroWallet multisigWallet = null;
multisigWallet = walletsSetup.getWalletConfig().createWallet(new MoneroWalletConfig() multisigWallet = createWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null); // auto-assign port
.setPath(path)
.setPassword("abctesting123"),
null); // auto-assign port
multisigWallets.put(tradeId, multisigWallet); multisigWallets.put(tradeId, multisigWallet);
multisigWallet.startSyncing(5000l); multisigWallet.startSyncing(5000l);
return multisigWallet; return multisigWallet;
} }
public synchronized MoneroWallet getMultisigWallet(String tradeId) { public MoneroWallet getMultisigWallet(String tradeId) { // TODO (woodser): synchronize per wallet id
log.info("{}.getMultisigWallet({})", getClass(), tradeId);
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
String path = "xmr_multisig_trade_" + tradeId; String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null; if (!walletExists(path)) return null;
multisigWallet = walletsSetup.getWalletConfig().openWallet(new MoneroWalletConfig() MoneroWallet multisigWallet = openWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null);
.setPath(path)
.setPassword("abctesting123"),
null);
multisigWallets.put(tradeId, multisigWallet); multisigWallets.put(tradeId, multisigWallet);
multisigWallet.startSyncing(5000l); // TODO (woodser): use sync period from config. apps stall if too many multisig wallets and too short sync period multisigWallet.startSyncing(5000l); // TODO (woodser): use sync period from config. apps stall if too many multisig wallets and too short sync period
return multisigWallet; return multisigWallet;
} }
public synchronized boolean deleteMultisigWallet(String tradeId) { public synchronized boolean deleteMultisigWallet(String tradeId) {
log.info("{}.deleteMultisigWallet({})", getClass(), tradeId);
String walletName = "xmr_multisig_trade_" + tradeId; String walletName = "xmr_multisig_trade_" + tradeId;
if (!walletsSetup.getWalletConfig().walletExists(walletName)) return false; if (!walletExists(walletName)) return false;
try { try {
walletsSetup.getWalletConfig().closeWallet(getMultisigWallet(tradeId), false); closeWallet(getMultisigWallet(tradeId), false);
} catch (Exception err) { } catch (Exception err) {
// multisig wallet may not be open // multisig wallet may not be open
} }
walletsSetup.getWalletConfig().deleteWallet(walletName); deleteWallet(walletName);
multisigWallets.remove(tradeId); multisigWallets.remove(tradeId);
return true; return true;
} }
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
try {
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig().setAccountIndex(0).setDestinations(destinations).setRelay(false).setCanSplit(false));
printTxs("XmrWalletService.createTx", tx);
return tx;
} catch (Exception e) {
throw e;
}
}
public void shutDown() {
closeAllWallets();
}
// ------------------------------ PRIVATE HELPERS -------------------------
private void initialize() {
// backup wallet files
backupWallets();
// initialize main wallet
MoneroWalletConfig walletConfig = new MoneroWalletConfig().setPath(MONERO_WALLET_NAME).setPassword(getWalletPassword());
wallet = MoneroUtils.walletExists(xmrWalletFile.getPath()) ? openWallet(walletConfig, rpcBindPort) : createWallet(walletConfig, rpcBindPort);
System.out.println("Monero wallet path: " + wallet.getPath());
System.out.println("Monero wallet address: " + wallet.getPrimaryAddress());
System.out.println("Monero wallet uri: " + ((MoneroWalletRpc) wallet).getRpcConnection().getUri());
wallet.sync(); // blocking
connectionsService.doneDownload(); // TODO: using this to signify both daemon and wallet synced, refactor sync handling of both
wallet.save();
System.out.println("Loaded wallet balance: " + wallet.getBalance(0));
System.out.println("Loaded wallet unlocked balance: " + wallet.getUnlockedBalance(0));
// update wallet connections on change
connectionsService.addListener(newConnection -> {
setWalletDaemonConnections(newConnection);
});
// notify on balance changes
wallet.addListener(new MoneroWalletListener() {
@Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
notifyBalanceListeners();
}
});
}
private void backupWallets() {
FileUtil.rollingBackup(walletDir, xmrWalletFile.getName(), 20);
FileUtil.rollingBackup(walletDir, xmrWalletFile.getName() + ".keys", 20);
FileUtil.rollingBackup(walletDir, xmrWalletFile.getName() + ".address.txt", 20);
}
private void setWalletDaemonConnections(MoneroRpcConnection connection) {
log.info("Setting wallet daemon connections: " + (connection == null ? null : connection.getUri()));
if (wallet != null) wallet.setDaemonConnection(connection);
for (MoneroWallet multisigWallet : multisigWallets.values()) multisigWallet.setDaemonConnection(connection);
}
private void notifyBalanceListeners() {
for (XmrBalanceListener balanceListener : balanceListeners) {
Coin balance;
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
else balance = getAvailableConfirmedBalance();
UserThread.execute(new Runnable() {
@Override
public void run() {
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
}
});
}
}
private void changeWalletPasswords(String oldPassword, String newPassword) {
List<String> tradeIds = tradeManager.getTrades().stream().map(Trade::getId).collect(Collectors.toList());
ExecutorService pool = Executors.newFixedThreadPool(Math.min(10, 1 + tradeIds.size()));
pool.submit(new Runnable() {
@Override
public void run() {
try {
wallet.changePassword(oldPassword, newPassword);
wallet.save();
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
});
for (String tradeId : tradeIds) {
pool.submit(new Runnable() {
@Override
public void run() {
MoneroWallet multisigWallet = getMultisigWallet(tradeId); // TODO (woodser): this unnecessarily connects and syncs unopen wallets and leaves open
if (multisigWallet == null) return;
multisigWallet.changePassword(oldPassword, newPassword);
multisigWallet.save();
}
});
}
pool.shutdown();
try {
if (!pool.awaitTermination(60000, TimeUnit.SECONDS)) pool.shutdownNow();
} catch (InterruptedException e) {
try { pool.shutdownNow(); }
catch (Exception e2) { }
throw new RuntimeException(e);
}
}
private void closeAllWallets() {
// collect wallets to shutdown
List<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
if (wallet != null) openWallets.add(wallet);
for (String multisigWalletKey : multisigWallets.keySet()) {
openWallets.add(multisigWallets.get(multisigWalletKey));
}
// done if no open wallets
if (openWallets.isEmpty()) return;
// close all wallets in parallel
ExecutorService pool = Executors.newFixedThreadPool(Math.min(10, openWallets.size()));
for (MoneroWallet openWallet : openWallets) {
pool.submit(new Runnable() {
@Override
public void run() {
try {
closeWallet(openWallet, true);
} catch (Exception e) {
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
}
}
});
}
pool.shutdown();
try {
if (!pool.awaitTermination(60000, TimeUnit.SECONDS)) pool.shutdownNow();
} catch (InterruptedException e) {
pool.shutdownNow();
throw new RuntimeException(e);
}
// clear wallets
wallet = null;
multisigWallets.clear();
}
// ----------------------------- LEGACY APP -------------------------------
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) { public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE); var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE);
if (!available.isPresent()) if (!available.isPresent()) return null;
return null; return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
} }
public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) { public XmrAddressEntry getNewAddressEntry(String offerId, XmrAddressEntry.Context context) {
MoneroSubaddress subaddress = wallet.createSubaddress(0); MoneroSubaddress subaddress = wallet.createSubaddress(0);
XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null); XmrAddressEntry entry = new XmrAddressEntry(subaddress.getIndex(), subaddress.getAddress(), context, offerId, null);
addressEntryList.addAddressEntry(entry); xmrAddressEntryList.addAddressEntry(entry);
return entry; return entry;
} }
public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) { public XmrAddressEntry getOrCreateAddressEntry(String offerId, XmrAddressEntry.Context context) {
Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream() Optional<XmrAddressEntry> addressEntry = getAddressEntryListAsImmutableList().stream().filter(e -> offerId.equals(e.getOfferId())).filter(e -> context == e.getContext()).findAny();
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
if (addressEntry.isPresent()) { if (addressEntry.isPresent()) {
return addressEntry.get(); return addressEntry.get();
} else { } else {
// We try to use available and not yet used entries // We try to use available and not yet used entries
Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream() Optional<XmrAddressEntry> emptyAvailableAddressEntry = getAddressEntryListAsImmutableList().stream().filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext())
.filter(e -> XmrAddressEntry.Context.AVAILABLE == e.getContext()) .filter(e -> isSubaddressUnused(e.getSubaddressIndex())).findAny();
.filter(e -> isSubaddressUnused(e.getSubaddressIndex()))
.findAny();
if (emptyAvailableAddressEntry.isPresent()) { if (emptyAvailableAddressEntry.isPresent()) {
return addressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId); return xmrAddressEntryList.swapAvailableToAddressEntryWithOfferId(emptyAvailableAddressEntry.get(), context, offerId);
} else { } else {
return getNewAddressEntry(offerId, context); return getNewAddressEntry(offerId, context);
} }
@ -168,24 +449,17 @@ public class XmrWalletService {
} }
public Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) { public Optional<XmrAddressEntry> getAddressEntry(String offerId, XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream() return getAddressEntryListAsImmutableList().stream().filter(e -> offerId.equals(e.getOfferId())).filter(e -> context == e.getContext()).findAny();
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
} }
public void swapTradeEntryToAvailableEntry(String offerId, XmrAddressEntry.Context context) { public void swapTradeEntryToAvailableEntry(String offerId, XmrAddressEntry.Context context) {
Optional<XmrAddressEntry> addressEntryOptional = getAddressEntryListAsImmutableList().stream() Optional<XmrAddressEntry> addressEntryOptional = getAddressEntryListAsImmutableList().stream().filter(e -> offerId.equals(e.getOfferId())).filter(e -> context == e.getContext()).findAny();
.filter(e -> offerId.equals(e.getOfferId()))
.filter(e -> context == e.getContext())
.findAny();
addressEntryOptional.ifPresent(e -> { addressEntryOptional.ifPresent(e -> {
log.info("swap addressEntry with address {} and offerId {} from context {} to available", log.info("swap addressEntry with address {} and offerId {} from context {} to available", e.getAddressString(), e.getOfferId(), context);
e.getAddressString(), e.getOfferId(), context); xmrAddressEntryList.swapToAvailable(e);
addressEntryList.swapToAvailable(e);
saveAddressEntryList(); saveAddressEntryList();
}); });
} }
public void resetAddressEntriesForOpenOffer(String offerId) { public void resetAddressEntriesForOpenOffer(String offerId) {
log.info("resetAddressEntriesForOpenOffer offerId={}", offerId); log.info("resetAddressEntriesForOpenOffer offerId={}", offerId);
@ -195,54 +469,50 @@ public class XmrWalletService {
public void resetAddressEntriesForPendingTrade(String offerId) { public void resetAddressEntriesForPendingTrade(String offerId) {
swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.MULTI_SIG); swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.MULTI_SIG);
// We swap also TRADE_PAYOUT to be sure all is cleaned up. There might be cases where a user cannot send the funds // We swap also TRADE_PAYOUT to be sure all is cleaned up. There might be cases
// to an external wallet directly in the last step of the trade, but the funds are in the Bisq wallet anyway and // where a user cannot send the funds
// the dealing with the external wallet is pure UI thing. The user can move the funds to the wallet and then // to an external wallet directly in the last step of the trade, but the funds
// send out the funds to the external wallet. As this cleanup is a rare situation and most users do not use // are in the Bisq wallet anyway and
// the feature to send out the funds we prefer that strategy (if we keep the address entry it might cause // the dealing with the external wallet is pure UI thing. The user can move the
// funds to the wallet and then
// send out the funds to the external wallet. As this cleanup is a rare
// situation and most users do not use
// the feature to send out the funds we prefer that strategy (if we keep the
// address entry it might cause
// complications in some edge cases after a SPV resync). // complications in some edge cases after a SPV resync).
swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT); swapTradeEntryToAvailableEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT);
} }
private Optional<XmrAddressEntry> findAddressEntry(String address, XmrAddressEntry.Context context) { private Optional<XmrAddressEntry> findAddressEntry(String address, XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream() return getAddressEntryListAsImmutableList().stream().filter(e -> address.equals(e.getAddressString())).filter(e -> context == e.getContext()).findAny();
.filter(e -> address.equals(e.getAddressString()))
.filter(e -> context == e.getContext())
.findAny();
} }
public List<XmrAddressEntry> getAvailableAddressEntries() { public List<XmrAddressEntry> getAvailableAddressEntries() {
return getAddressEntryListAsImmutableList().stream() return getAddressEntryListAsImmutableList().stream().filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext()).collect(Collectors.toList());
.filter(addressEntry -> XmrAddressEntry.Context.AVAILABLE == addressEntry.getContext()) }
.collect(Collectors.toList());
}
public List<XmrAddressEntry> getAddressEntriesForTrade() { public List<XmrAddressEntry> getAddressEntriesForTrade() {
return getAddressEntryListAsImmutableList().stream() return getAddressEntryListAsImmutableList().stream()
.filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() || .filter(addressEntry -> XmrAddressEntry.Context.MULTI_SIG == addressEntry.getContext() || XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext())
XmrAddressEntry.Context.TRADE_PAYOUT == addressEntry.getContext())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public List<XmrAddressEntry> getAddressEntries(XmrAddressEntry.Context context) { public List<XmrAddressEntry> getAddressEntries(XmrAddressEntry.Context context) {
return getAddressEntryListAsImmutableList().stream() return getAddressEntryListAsImmutableList().stream().filter(addressEntry -> context == addressEntry.getContext()).collect(Collectors.toList());
.filter(addressEntry -> context == addressEntry.getContext())
.collect(Collectors.toList());
} }
public List<XmrAddressEntry> getFundedAvailableAddressEntries() { public List<XmrAddressEntry> getFundedAvailableAddressEntries() {
return getAvailableAddressEntries().stream() return getAvailableAddressEntries().stream().filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive()).collect(Collectors.toList());
.filter(addressEntry -> getBalanceForSubaddress(addressEntry.getSubaddressIndex()).isPositive())
.collect(Collectors.toList());
} }
public List<XmrAddressEntry> getAddressEntryListAsImmutableList() { public List<XmrAddressEntry> getAddressEntryListAsImmutableList() {
return addressEntryList.getAddressEntriesAsListImmutable(); return xmrAddressEntryList.getAddressEntriesAsListImmutable();
} }
public boolean isSubaddressUnused(int subaddressIndex) { public boolean isSubaddressUnused(int subaddressIndex) {
return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0; return subaddressIndex != 0 && getBalanceForSubaddress(subaddressIndex).value == 0;
//return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed() does not include unconfirmed funds // return !wallet.getSubaddress(accountIndex, 0).isUsed(); // TODO: isUsed()
// does not include unconfirmed funds
} }
public Coin getBalanceForSubaddress(int subaddressIndex) { public Coin getBalanceForSubaddress(int subaddressIndex) {
@ -264,7 +534,6 @@ public class XmrWalletService {
return Coin.valueOf(balance.longValueExact()); return Coin.valueOf(balance.longValueExact());
} }
public Coin getAvailableConfirmedBalance() { public Coin getAvailableConfirmedBalance() {
return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO; return wallet != null ? Coin.valueOf(wallet.getUnlockedBalance(0).longValueExact()) : Coin.ZERO;
} }
@ -289,133 +558,24 @@ public class XmrWalletService {
} }
public void saveAddressEntryList() { public void saveAddressEntryList() {
addressEntryList.requestPersistence(); xmrAddressEntryList.requestPersistence();
} }
public List<MoneroTxWallet> getTransactions(boolean includeDead) { public List<MoneroTxWallet> getTransactions(boolean includeDead) {
return wallet.getTxs(new MoneroTxQuery().setIsFailed(includeDead ? null : false)); return wallet.getTxs(new MoneroTxQuery().setIsFailed(includeDead ? null : false));
} }
public void shutDown() {
// collect wallets to shutdown
List<MoneroWallet> openWallets = new ArrayList<MoneroWallet>();
if (wallet != null) openWallets.add(wallet);
for (String multisigWalletKey : multisigWallets.keySet()) {
openWallets.add(multisigWallets.get(multisigWalletKey));
}
// create shutdown threads
List<Thread> threads = new ArrayList<Thread>();
for (MoneroWallet openWallet : openWallets) {
threads.add(new Thread(new Runnable() {
@Override
public void run() {
try { walletsSetup.getWalletConfig().closeWallet(openWallet, true); }
catch (Exception e) {
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
}
}
}));
}
// run shutdown threads in parallel
for (Thread thread : threads) thread.start();
// wait for all threads
for (Thread thread : threads) {
try { thread.join(); }
catch (InterruptedException e) { e.printStackTrace(); }
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Withdrawal Send
///////////////////////////////////////////////////////////////////////////////////////////
public String sendFunds(int fromAccountIndex,
String toAddress,
Coin receiverAmount,
@SuppressWarnings("SameParameterValue") XmrAddressEntry.Context context,
FutureCallback<MoneroTxWallet> callback) throws AddressFormatException,
AddressEntryException, InsufficientMoneyException {
try {
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(fromAccountIndex)
.setAddress(toAddress)
.setAmount(ParsingUtils.coinToAtomicUnits(receiverAmount))
.setRelay(true));
callback.onSuccess(tx);
printTxs("sendFunds", tx);
return tx.getHash();
} catch (Exception e) {
callback.onFailure(e);
throw e;
}
}
// public String sendFunds(String fromAddress, String toAddress, Coin receiverAmount, Coin fee, @Nullable KeyParameter aesKey, @SuppressWarnings("SameParameterValue") AddressEntry.Context context,
// FutureCallback<Transaction> callback) throws AddressFormatException, AddressEntryException, InsufficientMoneyException {
// SendRequest sendRequest = getSendRequest(fromAddress, toAddress, receiverAmount, fee, aesKey, context);
// Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
// Futures.addCallback(sendResult.broadcastComplete, callback, MoreExecutors.directExecutor());
//
// printTx("sendFunds", sendResult.tx);
// return sendResult.tx.getTxId().toString();
// }
///////////////////////////////////////////////////////////////////////////////////////////
// Create Tx
///////////////////////////////////////////////////////////////////////////////////////////
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
try {
MoneroTxWallet tx = wallet.createTx(new MoneroTxConfig()
.setAccountIndex(0)
.setDestinations(destinations)
.setRelay(false)
.setCanSplit(false));
printTxs("XmrWalletService.createTx", tx);
return tx;
} catch (Exception e) {
throw e;
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Util // Util
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public static void printTxs(String tracePrefix, MoneroTxWallet... txs) { public static void printTxs(String tracePrefix, MoneroTxWallet... txs) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString()); for (MoneroTxWallet tx : txs) sb.append('\n' + tx.toString());
log.info("\n" + tracePrefix + ":" + sb.toString()); log.info("\n" + tracePrefix + ":" + sb.toString());
} }
private void notifyBalanceListeners() {
for (XmrBalanceListener balanceListener : balanceListeners) {
Coin balance;
if (balanceListener.getSubaddressIndex() != null && balanceListener.getSubaddressIndex() != 0) {
balance = getBalanceForSubaddress(balanceListener.getSubaddressIndex());
} else {
balance = getAvailableConfirmedBalance();
}
UserThread.execute(new Runnable() {
@Override public void run() {
balanceListener.onBalanceChanged(BigInteger.valueOf(balance.value));
}
});
}
}
private void updateDaemonConnections(MoneroRpcConnection connection) {
log.info("Setting wallet daemon connections: " + (connection == null ? null : connection.getUri()));
walletsSetup.getXmrWallet().setDaemonConnection(connection);
for (MoneroWallet multisigWallet : multisigWallets.values()) multisigWallet.setDaemonConnection(connection);
}
/** /**
* Wraps a MoneroWalletListener to notify the Haveno application. * Wraps a MoneroWalletListener to notify the Haveno application.
* *
@ -432,7 +592,8 @@ public class XmrWalletService {
@Override @Override
public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) { public void onSyncProgress(long height, long startHeight, long endHeight, double percentDone, String message) {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override public void run() { @Override
public void run() {
listener.onSyncProgress(height, startHeight, endHeight, percentDone, message); listener.onSyncProgress(height, startHeight, endHeight, percentDone, message);
} }
}); });
@ -441,7 +602,8 @@ public class XmrWalletService {
@Override @Override
public void onNewBlock(long height) { public void onNewBlock(long height) {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override public void run() { @Override
public void run() {
listener.onNewBlock(height); listener.onNewBlock(height);
} }
}); });
@ -450,7 +612,8 @@ public class XmrWalletService {
@Override @Override
public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) { public void onBalancesChanged(BigInteger newBalance, BigInteger newUnlockedBalance) {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override public void run() { @Override
public void run() {
listener.onBalancesChanged(newBalance, newUnlockedBalance); listener.onBalancesChanged(newBalance, newUnlockedBalance);
} }
}); });
@ -459,7 +622,8 @@ public class XmrWalletService {
@Override @Override
public void onOutputReceived(MoneroOutputWallet output) { public void onOutputReceived(MoneroOutputWallet output) {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override public void run() { @Override
public void run() {
listener.onOutputReceived(output); listener.onOutputReceived(output);
} }
}); });
@ -468,7 +632,8 @@ public class XmrWalletService {
@Override @Override
public void onOutputSpent(MoneroOutputWallet output) { public void onOutputSpent(MoneroOutputWallet output) {
UserThread.execute(new Runnable() { UserThread.execute(new Runnable() {
@Override public void run() { @Override
public void run() {
listener.onOutputSpent(output); listener.onOutputSpent(output);
} }
}); });

View File

@ -24,8 +24,7 @@ import bisq.core.notifications.MobileNotificationService;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.crypto.PubKeyRing;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -41,15 +40,15 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@Singleton @Singleton
public class TradeEvents { public class TradeEvents {
private final PubKeyRing pubKeyRing; private final PubKeyRingProvider pubKeyRingProvider;
private final TradeManager tradeManager; private final TradeManager tradeManager;
private final MobileNotificationService mobileNotificationService; private final MobileNotificationService mobileNotificationService;
@Inject @Inject
public TradeEvents(TradeManager tradeManager, KeyRing keyRing, MobileNotificationService mobileNotificationService) { public TradeEvents(TradeManager tradeManager, PubKeyRingProvider pubKeyRingProvider, MobileNotificationService mobileNotificationService) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.mobileNotificationService = mobileNotificationService; this.mobileNotificationService = mobileNotificationService;
this.pubKeyRing = keyRing.getPubKeyRing(); this.pubKeyRingProvider = pubKeyRingProvider;
} }
public void onAllServicesInitialized() { public void onAllServicesInitialized() {
@ -74,19 +73,19 @@ public class TradeEvents {
case DEPOSIT_PUBLISHED: case DEPOSIT_PUBLISHED:
break; break;
case DEPOSIT_CONFIRMED: case DEPOSIT_CONFIRMED:
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getBuyerPubKeyRing())) if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
msg = Res.get("account.notifications.trade.message.msg.conf", shortId); msg = Res.get("account.notifications.trade.message.msg.conf", shortId);
break; break;
case FIAT_SENT: case FIAT_SENT:
// We only notify the seller // We only notify the seller
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getSellerPubKeyRing())) if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getSellerPubKeyRing()))
msg = Res.get("account.notifications.trade.message.msg.started", shortId); msg = Res.get("account.notifications.trade.message.msg.started", shortId);
break; break;
case FIAT_RECEIVED: case FIAT_RECEIVED:
break; break;
case PAYOUT_PUBLISHED: case PAYOUT_PUBLISHED:
// We only notify the buyer // We only notify the buyer
if (trade.getContract() != null && pubKeyRing.equals(trade.getContract().getBuyerPubKeyRing())) if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
msg = Res.get("account.notifications.trade.message.msg.completed", shortId); msg = Res.get("account.notifications.trade.message.msg.completed", shortId);
break; break;
case WITHDRAWN: case WITHDRAWN:

View File

@ -39,7 +39,7 @@ import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
@ -65,7 +65,7 @@ public class CreateOfferService {
private final TxFeeEstimationService txFeeEstimationService; private final TxFeeEstimationService txFeeEstimationService;
private final PriceFeedService priceFeedService; private final PriceFeedService priceFeedService;
private final P2PService p2PService; private final P2PService p2PService;
private final PubKeyRing pubKeyRing; private final PubKeyRingProvider pubKeyRingProvider;
private final User user; private final User user;
private final BtcWalletService btcWalletService; private final BtcWalletService btcWalletService;
private final TradeStatisticsManager tradeStatisticsManager; private final TradeStatisticsManager tradeStatisticsManager;
@ -81,7 +81,7 @@ public class CreateOfferService {
TxFeeEstimationService txFeeEstimationService, TxFeeEstimationService txFeeEstimationService,
PriceFeedService priceFeedService, PriceFeedService priceFeedService,
P2PService p2PService, P2PService p2PService,
PubKeyRing pubKeyRing, PubKeyRingProvider pubKeyRingProvider,
User user, User user,
BtcWalletService btcWalletService, BtcWalletService btcWalletService,
TradeStatisticsManager tradeStatisticsManager, TradeStatisticsManager tradeStatisticsManager,
@ -90,7 +90,7 @@ public class CreateOfferService {
this.txFeeEstimationService = txFeeEstimationService; this.txFeeEstimationService = txFeeEstimationService;
this.priceFeedService = priceFeedService; this.priceFeedService = priceFeedService;
this.p2PService = p2PService; this.p2PService = p2PService;
this.pubKeyRing = pubKeyRing; this.pubKeyRingProvider = pubKeyRingProvider;
this.user = user; this.user = user;
this.btcWalletService = btcWalletService; this.btcWalletService = btcWalletService;
this.tradeStatisticsManager = tradeStatisticsManager; this.tradeStatisticsManager = tradeStatisticsManager;
@ -197,7 +197,7 @@ public class CreateOfferService {
OfferPayload offerPayload = new OfferPayload(offerId, OfferPayload offerPayload = new OfferPayload(offerId,
creationTime, creationTime,
makerAddress, makerAddress,
pubKeyRing, pubKeyRingProvider.get(),
OfferPayload.Direction.valueOf(direction.name()), OfferPayload.Direction.valueOf(direction.name()),
priceAsLong, priceAsLong,
marketPriceMarginParam, marketPriceMarginParam,

View File

@ -63,7 +63,7 @@ public class OfferFilter {
this.filterManager = filterManager; this.filterManager = filterManager;
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
if (user != null) { if (user != null && user.getPaymentAccountsAsObservable() != null) {
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data // If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c -> user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
myInsufficientTradeLimitCache.clear()); myInsufficientTradeLimitCache.clear());

View File

@ -18,6 +18,7 @@
package bisq.core.offer; package bisq.core.offer;
import bisq.core.api.CoreContext; import bisq.core.api.CoreContext;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
@ -110,6 +111,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final KeyRing keyRing; private final KeyRing keyRing;
private final User user; private final User user;
private final P2PService p2PService; private final P2PService p2PService;
private final CoreMoneroConnectionsService connectionService;
private final BtcWalletService btcWalletService; private final BtcWalletService btcWalletService;
private final XmrWalletService xmrWalletService; private final XmrWalletService xmrWalletService;
private final TradeWalletService tradeWalletService; private final TradeWalletService tradeWalletService;
@ -144,6 +146,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
KeyRing keyRing, KeyRing keyRing,
User user, User user,
P2PService p2PService, P2PService p2PService,
CoreMoneroConnectionsService connectionService,
BtcWalletService btcWalletService, BtcWalletService btcWalletService,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
@ -163,6 +166,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
this.keyRing = keyRing; this.keyRing = keyRing;
this.user = user; this.user = user;
this.p2PService = p2PService; this.p2PService = p2PService;
this.connectionService = connectionService;
this.btcWalletService = btcWalletService; this.btcWalletService = btcWalletService;
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
@ -751,8 +755,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
return; return;
} }
// Don't allow trade start if BitcoinJ is not fully synced (bisq issue #4764) // Don't allow trade start if Monero node is not fully synced
if (!btcWalletService.isChainHeightSyncedWithinTolerance()) { if (!connectionService.isChainHeightSyncedWithinTolerance()) {
errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced."; errorMessage = "We got a handleOfferAvailabilityRequest but our chain is not synced.";
log.info(errorMessage); log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);

View File

@ -17,7 +17,7 @@
package bisq.core.support; package bisq.core.support;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.support.messages.ChatMessage; import bisq.core.support.messages.ChatMessage;
import bisq.core.support.messages.SupportMessage; import bisq.core.support.messages.SupportMessage;
@ -47,7 +47,7 @@ import javax.annotation.Nullable;
@Slf4j @Slf4j
public abstract class SupportManager { public abstract class SupportManager {
protected final P2PService p2PService; protected final P2PService p2PService;
protected final WalletsSetup walletsSetup; protected final CoreMoneroConnectionsService connectionService;
protected final Map<String, Timer> delayMsgMap = new HashMap<>(); protected final Map<String, Timer> delayMsgMap = new HashMap<>();
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedMailboxMessageWithPubKeys = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedMailboxMessageWithPubKeys = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>();
@ -59,12 +59,11 @@ public abstract class SupportManager {
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public SupportManager(P2PService p2PService, WalletsSetup walletsSetup) { public SupportManager(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
this.p2PService = p2PService; this.p2PService = p2PService;
this.connectionService = connectionService;
mailboxMessageService = p2PService.getMailboxMessageService(); mailboxMessageService = p2PService.getMailboxMessageService();
this.walletsSetup = walletsSetup;
// We get first the message handler called then the onBootstrapped // We get first the message handler called then the onBootstrapped
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> { p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
// As decryptedDirectMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was // As decryptedDirectMessageWithPubKeys is a CopyOnWriteArraySet we do not need to check if it was
@ -293,8 +292,8 @@ public abstract class SupportManager {
private boolean isReady() { private boolean isReady() {
return allServicesInitialized && return allServicesInitialized &&
p2PService.isBootstrapped() && p2PService.isBootstrapped() &&
walletsSetup.isDownloadComplete() && connectionService.isDownloadComplete() &&
walletsSetup.hasSufficientPeersForBroadcast(); connectionService.hasSufficientPeersForBroadcast();
} }

View File

@ -17,7 +17,7 @@
package bisq.core.support.dispute; package bisq.core.support.dispute;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.Restrictions; import bisq.core.btc.wallet.Restrictions;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
@ -39,7 +39,6 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeDataValidation; import bisq.core.trade.TradeDataValidation;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.closed.ClosedTradableManager;
import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
@ -111,7 +110,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
public DisputeManager(P2PService p2PService, public DisputeManager(P2PService p2PService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
TradeManager tradeManager, TradeManager tradeManager,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
@ -119,7 +118,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
DisputeListService<T> disputeListService, DisputeListService<T> disputeListService,
Config config, Config config,
PriceFeedService priceFeedService) { PriceFeedService priceFeedService) {
super(p2PService, walletsSetup); super(p2PService, connectionService);
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
@ -252,13 +251,13 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
} }
}); });
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> { connectionService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
if (walletsSetup.isDownloadComplete()) if (connectionService.isDownloadComplete())
tryApplyMessages(); tryApplyMessages();
}); });
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> { connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
if (walletsSetup.hasSufficientPeersForBroadcast()) if (connectionService.hasSufficientPeersForBroadcast())
tryApplyMessages(); tryApplyMessages();
}); });

View File

@ -17,7 +17,7 @@
package bisq.core.support.dispute.arbitration; package bisq.core.support.dispute.arbitration;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
@ -95,7 +95,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
public ArbitrationManager(P2PService p2PService, public ArbitrationManager(P2PService p2PService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
XmrWalletService walletService, XmrWalletService walletService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
TradeManager tradeManager, TradeManager tradeManager,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
@ -103,7 +103,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
ArbitrationDisputeListService arbitrationDisputeListService, ArbitrationDisputeListService arbitrationDisputeListService,
Config config, Config config,
PriceFeedService priceFeedService) { PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
openOfferManager, keyRing, arbitrationDisputeListService, config, priceFeedService); openOfferManager, keyRing, arbitrationDisputeListService, config, priceFeedService);
} }
@ -365,19 +365,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
cleanupRetryMap(uid); cleanupRetryMap(uid);
// update multisig wallet // update multisig wallet
// TODO: multisig wallet may already be deleted if peer completed trade with arbitrator. refactor trade completion?
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId()); MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
if (multisigWallet != null) {
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex())); multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
// parse payout tx
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0); MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
}
// System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs()); // System.out.println("LOSER'S VIEW OF MULTISIG WALLET (SHOULD INCLUDE PAYOUT TX):\n" + multisigWallet.getTxs());
// if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx"); // if (multisigWallet.getTxs().size() != 3) throw new RuntimeException("Loser's multisig wallet does not include record of payout tx");
// Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet()); // Transaction committedDisputePayoutTx = WalletService.maybeAddNetworkTxToWallet(peerPublishedDisputePayoutTxMessage.getTransaction(), btcWalletService.getWallet());
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
// We can only send the ack msg if we have the peersPubKeyRing which requires the dispute // We can only send the ack msg if we have the peersPubKeyRing which requires the dispute
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null); sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
requestPersistence(); requestPersistence();
@ -436,7 +436,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
SupportType.ARBITRATION, SupportType.ARBITRATION,
payoutTx.getTxSet().getMultisigTxHex()); payoutTx.getTxSet().getMultisigTxHex());
log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), contract.getArbitratorNodeAddress(), dispute.getTradeId(), response.getUid()); log.info("Send {} to peer {}. tradeId={}, uid={}", response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(), p2PService.sendEncryptedDirectMessage(request.getSenderNodeAddress(),
senderPubKeyRing, senderPubKeyRing,
response, response,
@ -644,8 +644,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
numAttempts++; numAttempts++;
payoutTx = multisigWallet.createTx(txConfig); payoutTx = multisigWallet.createTx(txConfig);
} catch (MoneroError e) { } catch (MoneroError e) {
System.out.println(e.toString());
System.out.println(e.getStackTrace());
// exception expected // TODO: better way of estimating fee? // exception expected // TODO: better way of estimating fee?
} }
} }
@ -716,7 +714,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
// TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE // TODO (woodser): VERIFY PAYOUT TX AMOUNTS WHICH CONSIDERS FEE IF LONG TRADE, EXACT AMOUNT IF SHORT TRADE
// if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout); // if (!buyerPayoutDestination.getAmount().equals(expectedBuyerPayout)) throw new RuntimeException("Buyer destination amount is not payout amount - 1/2 tx costs, " + buyerPayoutDestination.getAmount() + " vs " + expectedBuyerPayout);
// verify seller destination amount is payout amount - 1/2 tx costs // verify seller destination amount is payout amount - 1/2 tx costs

View File

@ -17,7 +17,7 @@
package bisq.core.support.dispute.mediation; package bisq.core.support.dispute.mediation;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
@ -77,7 +77,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
public MediationManager(P2PService p2PService, public MediationManager(P2PService p2PService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
XmrWalletService walletService, XmrWalletService walletService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
TradeManager tradeManager, TradeManager tradeManager,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
@ -85,7 +85,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
MediationDisputeListService mediationDisputeListService, MediationDisputeListService mediationDisputeListService,
Config config, Config config,
PriceFeedService priceFeedService) { PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
openOfferManager, keyRing, mediationDisputeListService, config, priceFeedService); openOfferManager, keyRing, mediationDisputeListService, config, priceFeedService);
} }

View File

@ -17,7 +17,7 @@
package bisq.core.support.dispute.refund; package bisq.core.support.dispute.refund;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
@ -71,7 +71,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
public RefundManager(P2PService p2PService, public RefundManager(P2PService p2PService,
TradeWalletService tradeWalletService, TradeWalletService tradeWalletService,
XmrWalletService walletService, XmrWalletService walletService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
TradeManager tradeManager, TradeManager tradeManager,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
@ -80,7 +80,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
RefundDisputeListService refundDisputeListService, RefundDisputeListService refundDisputeListService,
Config config, Config config,
PriceFeedService priceFeedService) { PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager, super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
openOfferManager, keyRing, refundDisputeListService, config, priceFeedService); openOfferManager, keyRing, refundDisputeListService, config, priceFeedService);
} }

View File

@ -17,7 +17,7 @@
package bisq.core.support.traderchat; package bisq.core.support.traderchat;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.support.SupportManager; import bisq.core.support.SupportManager;
import bisq.core.support.SupportType; import bisq.core.support.SupportType;
@ -31,6 +31,7 @@ import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -46,7 +47,7 @@ import lombok.extern.slf4j.Slf4j;
@Singleton @Singleton
public class TraderChatManager extends SupportManager { public class TraderChatManager extends SupportManager {
private final TradeManager tradeManager; private final TradeManager tradeManager;
private final PubKeyRing pubKeyRing; private final PubKeyRingProvider pubKeyRingProvider;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -55,12 +56,12 @@ public class TraderChatManager extends SupportManager {
@Inject @Inject
public TraderChatManager(P2PService p2PService, public TraderChatManager(P2PService p2PService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
TradeManager tradeManager, TradeManager tradeManager,
PubKeyRing pubKeyRing) { PubKeyRingProvider pubKeyRingProvider) {
super(p2PService, walletsSetup); super(p2PService, connectionService);
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.pubKeyRing = pubKeyRing; this.pubKeyRingProvider = pubKeyRingProvider;
} }
@ -82,7 +83,7 @@ public class TraderChatManager extends SupportManager {
public NodeAddress getPeerNodeAddress(ChatMessage message) { public NodeAddress getPeerNodeAddress(ChatMessage message) {
return tradeManager.getTradeById(message.getTradeId()).map(trade -> { return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
if (trade.getContract() != null) { if (trade.getContract() != null) {
return trade.getContract().getPeersNodeAddress(pubKeyRing); return trade.getContract().getPeersNodeAddress(pubKeyRingProvider.get());
} else { } else {
return null; return null;
} }
@ -93,7 +94,7 @@ public class TraderChatManager extends SupportManager {
public PubKeyRing getPeerPubKeyRing(ChatMessage message) { public PubKeyRing getPeerPubKeyRing(ChatMessage message) {
return tradeManager.getTradeById(message.getTradeId()).map(trade -> { return tradeManager.getTradeById(message.getTradeId()).map(trade -> {
if (trade.getContract() != null) { if (trade.getContract() != null) {
return trade.getContract().getPeersPubKeyRing(pubKeyRing); return trade.getContract().getPeersPubKeyRing(pubKeyRingProvider.get());
} else { } else {
return null; return null;
} }
@ -139,11 +140,13 @@ public class TraderChatManager extends SupportManager {
// API // API
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void onAllServicesInitialized() { public void onAllServicesInitialized() {
super.onAllServicesInitialized(); super.onAllServicesInitialized();
tryApplyMessages(); tryApplyMessages();
} }
@Override
public void onSupportMessage(SupportMessage message) { public void onSupportMessage(SupportMessage message) {
if (canProcessMessage(message)) { if (canProcessMessage(message)) {
log.info("Received {} with tradeId {} and uid {}", log.info("Received {} with tradeId {} and uid {}",

View File

@ -1108,7 +1108,8 @@ public abstract class Trade implements Tradable, Model {
return false; return false;
} }
// Legacy arbitration is not handled anymore as not used anymore. // check for closed disputed case
if (disputeState == DisputeState.DISPUTE_CLOSED) return false;
// In mediation case we check for the mediationResultState. As there are multiple sub-states we use ordinal. // In mediation case we check for the mediationResultState. As there are multiple sub-states we use ordinal.
if (disputeState == DisputeState.MEDIATION_CLOSED) { if (disputeState == DisputeState.MEDIATION_CLOSED) {

View File

@ -118,10 +118,9 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import monero.wallet.model.MoneroTxWallet; import monero.wallet.model.MoneroTxWallet;
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener { public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
private static final Logger log = LoggerFactory.getLogger(TradeManager.class); private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
@ -283,6 +282,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged()); getObservableList().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
onTradesChanged(); onTradesChanged();
xmrWalletService.setTradeManager(this);
xmrWalletService.getAddressEntriesForAvailableBalanceStream() xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null) .filter(addressEntry -> addressEntry.getOfferId() != null)
.forEach(addressEntry -> { .forEach(addressEntry -> {
@ -1014,7 +1014,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade)); p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
xmrWalletService.deleteMultisigWallet(trade.getId()); xmrWalletService.deleteMultisigWallet(trade.getId()); // TODO (woodser): don't delete multisig wallet until payout tx unlocked?
requestPersistence(); requestPersistence();
} }
} }

View File

@ -17,6 +17,7 @@
package bisq.core.trade.protocol.tasks; package bisq.core.trade.protocol.tasks;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade; import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade; import bisq.core.trade.TakerTrade;
@ -68,6 +69,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage(); InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
checkNotNull(request); checkNotNull(request);
checkTradeId(processModel.getOfferId(), request); checkTradeId(processModel.getOfferId(), request);
XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService();
System.out.println("PROCESS MULTISIG MESSAGE"); System.out.println("PROCESS MULTISIG MESSAGE");
System.out.println(request); System.out.println(request);
@ -98,18 +100,18 @@ public class ProcessInitMultisigRequest extends TradeTask {
boolean updateParticipants = false; boolean updateParticipants = false;
if (processModel.getPreparedMultisigHex() == null) { if (processModel.getPreparedMultisigHex() == null) {
System.out.println("Preparing multisig wallet!"); System.out.println("Preparing multisig wallet!");
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId()); multisigWallet = xmrWalletService.createMultisigWallet(trade.getId());
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig()); processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
updateParticipants = true; updateParticipants = true;
} else { } else {
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId()); multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
} }
// make multisig if applicable // make multisig if applicable
TradingPeer[] peers = getMultisigPeers(); TradingPeer[] peers = getMultisigPeers();
if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) { if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) {
System.out.println("Making multisig wallet!"); System.out.println("Making multisig wallet!");
MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, "abctesting123"); // TODO (woodser): move this to config MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, xmrWalletService.getWalletPassword()); // TODO (woodser): xmrWalletService.makeMultisig(tradeId, multisigHexes, threshold)?
processModel.setMadeMultisigHex(result.getMultisigHex()); processModel.setMadeMultisigHex(result.getMultisigHex());
updateParticipants = true; updateParticipants = true;
} }
@ -117,7 +119,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
// exchange multisig keys if applicable // exchange multisig keys if applicable
if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) { if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
System.out.println("Exchanging multisig wallet!"); System.out.println("Exchanging multisig wallet!");
multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), "abctesting123"); // TODO (woodser): move this to config multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), xmrWalletService.getWalletPassword());
processModel.setMultisigSetupComplete(true); processModel.setMultisigSetupComplete(true);
} }

View File

@ -1,71 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks.taker;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.common.taskrunner.TaskRunner;
import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import monero.wallet.model.MoneroTxWallet;
// TODO (woodser): rename this to TakerCreateFeeTx or rename TakerPublishFeeTx to TakerPublishReserveTradeTx for consistency
@Slf4j
public class TakerCreateFeeTx extends TradeTask {
@SuppressWarnings({ "unused" })
public TakerCreateFeeTx(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
String id = processModel.getOffer().getId();
XmrAddressEntry reservedForTradeAddressEntry = walletService.getOrCreateAddressEntry(id, XmrAddressEntry.Context.RESERVED_FOR_TRADE);
TradeWalletService tradeWalletService = processModel.getTradeWalletService();
String feeReceiver = "52FnB7ABUrKJzVQRpbMNrqDFWbcKLjFUq8Rgek7jZEuB6WE2ZggXaTf4FK6H8gQymvSrruHHrEuKhMN3qTMiBYzREKsmRKM"; // TODO (woodser): don't hardcode
// pay trade fee to reserve trade
MoneroTxWallet tx = tradeWalletService.createXmrTradingFeeTx(
reservedForTradeAddressEntry.getAddressString(),
Coin.valueOf(processModel.getFundsNeededForTradeAsLong()),
trade.getTakerFee(),
trade.getTxFee(),
feeReceiver,
false);
trade.setTakerFeeTxId(tx.getHash());
processModel.setTakeOfferFeeTx(tx);
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View File

@ -17,7 +17,7 @@
package bisq.core.trade.txproof.xmr; package bisq.core.trade.txproof.xmr;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.support.dispute.mediation.MediationManager; import bisq.core.support.dispute.mediation.MediationManager;
@ -76,7 +76,7 @@ public class XmrTxProofService implements AssetTxProofService {
private final MediationManager mediationManager; private final MediationManager mediationManager;
private final RefundManager refundManager; private final RefundManager refundManager;
private final P2PService p2PService; private final P2PService p2PService;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
private final Socks5ProxyProvider socks5ProxyProvider; private final Socks5ProxyProvider socks5ProxyProvider;
private final Map<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>(); private final Map<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>();
private AutoConfirmSettings autoConfirmSettings; private AutoConfirmSettings autoConfirmSettings;
@ -101,7 +101,7 @@ public class XmrTxProofService implements AssetTxProofService {
MediationManager mediationManager, MediationManager mediationManager,
RefundManager refundManager, RefundManager refundManager,
P2PService p2PService, P2PService p2PService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
Socks5ProxyProvider socks5ProxyProvider) { Socks5ProxyProvider socks5ProxyProvider) {
this.filterManager = filterManager; this.filterManager = filterManager;
this.preferences = preferences; this.preferences = preferences;
@ -111,7 +111,7 @@ public class XmrTxProofService implements AssetTxProofService {
this.mediationManager = mediationManager; this.mediationManager = mediationManager;
this.refundManager = refundManager; this.refundManager = refundManager;
this.p2PService = p2PService; this.p2PService = p2PService;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.socks5ProxyProvider = socks5ProxyProvider; this.socks5ProxyProvider = socks5ProxyProvider;
} }
@ -289,32 +289,32 @@ public class XmrTxProofService implements AssetTxProofService {
private BooleanProperty isXmrBlockDownloadComplete() { private BooleanProperty isXmrBlockDownloadComplete() {
BooleanProperty result = new SimpleBooleanProperty(); BooleanProperty result = new SimpleBooleanProperty();
if (walletsSetup.isDownloadComplete()) { if (connectionService.isDownloadComplete()) {
result.set(true); result.set(true);
} else { } else {
xmrBlockListener = (observable, oldValue, newValue) -> { xmrBlockListener = (observable, oldValue, newValue) -> {
if (walletsSetup.isDownloadComplete()) { if (connectionService.isDownloadComplete()) {
walletsSetup.downloadPercentageProperty().removeListener(xmrBlockListener); connectionService.downloadPercentageProperty().removeListener(xmrBlockListener);
result.set(true); result.set(true);
} }
}; };
walletsSetup.downloadPercentageProperty().addListener(xmrBlockListener); connectionService.downloadPercentageProperty().addListener(xmrBlockListener);
} }
return result; return result;
} }
private BooleanProperty hasSufficientXmrPeers() { private BooleanProperty hasSufficientXmrPeers() {
BooleanProperty result = new SimpleBooleanProperty(); BooleanProperty result = new SimpleBooleanProperty();
if (walletsSetup.hasSufficientPeersForBroadcast()) { if (connectionService.hasSufficientPeersForBroadcast()) {
result.set(true); result.set(true);
} else { } else {
xmrPeersListener = (observable, oldValue, newValue) -> { xmrPeersListener = (observable, oldValue, newValue) -> {
if (walletsSetup.hasSufficientPeersForBroadcast()) { if (connectionService.hasSufficientPeersForBroadcast()) {
walletsSetup.numPeersProperty().removeListener(xmrPeersListener); connectionService.numPeersProperty().removeListener(xmrPeersListener);
result.set(true); result.set(true);
} }
}; };
walletsSetup.numPeersProperty().addListener(xmrPeersListener); connectionService.numPeersProperty().addListener(xmrPeersListener);
} }
return result; return result;
} }

View File

@ -399,6 +399,7 @@ public class User implements PersistedDataHost {
return userPayload.getPaymentAccounts(); return userPayload.getPaymentAccounts();
} }
@Nullable
public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() { public ObservableSet<PaymentAccount> getPaymentAccountsAsObservable() {
return paymentAccountsAsObservable; return paymentAccountsAsObservable;
} }

View File

@ -162,8 +162,8 @@ public class AccountAgeWitnessServiceTest {
@Test @Test
public void testArbitratorSignWitness() { public void testArbitratorSignWitness() {
KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1)); KeyRing buyerKeyRing = new KeyRing(new KeyStorage(dir1), null, true);
KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2)); KeyRing sellerKeyRing = new KeyRing(new KeyStorage(dir2), null, true);
// Setup dispute for arbitrator to sign both sides // Setup dispute for arbitrator to sign both sides
List<Dispute> disputes = new ArrayList<>(); List<Dispute> disputes = new ArrayList<>();
@ -278,9 +278,9 @@ public class AccountAgeWitnessServiceTest {
public void testArbitratorSignDummyWitness() throws CryptoException { public void testArbitratorSignDummyWitness() throws CryptoException {
ECKey arbitratorKey = new ECKey(); ECKey arbitratorKey = new ECKey();
// Init 2 user accounts // Init 2 user accounts
var user1KeyRing = new KeyRing(new KeyStorage(dir1)); var user1KeyRing = new KeyRing(new KeyStorage(dir1), null, true);
var user2KeyRing = new KeyRing(new KeyStorage(dir2)); var user2KeyRing = new KeyRing(new KeyStorage(dir2), null, true);
var user3KeyRing = new KeyRing(new KeyStorage(dir3)); var user3KeyRing = new KeyRing(new KeyStorage(dir3), null, true);
var pubKeyRing1 = user1KeyRing.getPubKeyRing(); var pubKeyRing1 = user1KeyRing.getPubKeyRing();
var pubKeyRing2 = user2KeyRing.getPubKeyRing(); var pubKeyRing2 = user2KeyRing.getPubKeyRing();
var pubKeyRing3 = user3KeyRing.getPubKeyRing(); var pubKeyRing3 = user3KeyRing.getPubKeyRing();

View File

@ -49,7 +49,7 @@ public class EncryptionTest {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
dir.mkdir(); dir.mkdir();
KeyStorage keyStorage = new KeyStorage(dir); KeyStorage keyStorage = new KeyStorage(dir);
keyRing = new KeyRing(keyStorage); keyRing = new KeyRing(keyStorage, null, true);
} }
@After @After

View File

@ -51,7 +51,7 @@ public class SigTest {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
dir.mkdir(); dir.mkdir();
KeyStorage keyStorage = new KeyStorage(dir); KeyStorage keyStorage = new KeyStorage(dir);
keyRing = new KeyRing(keyStorage); keyRing = new KeyRing(keyStorage, null, true);
} }
@After @After

View File

@ -60,6 +60,7 @@ public class OpenOfferManagerTest {
null, null,
null, null,
null, null,
null,
offerBookService, offerBookService,
null, null,
null, null,
@ -106,6 +107,7 @@ public class OpenOfferManagerTest {
null, null,
null, null,
null, null,
null,
offerBookService, offerBookService,
null, null,
null, null,
@ -146,6 +148,7 @@ public class OpenOfferManagerTest {
null, null,
null, null,
null, null,
null,
offerBookService, offerBookService,
null, null,
null, null,

View File

@ -0,0 +1,64 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.daemon.app;
import java.util.concurrent.*;
/**
* A cancellable console input reader.
* Derived from https://www.javaspecialists.eu/archive/Issue153-Timeout-on-Console-Input.html
*/
public class ConsoleInput {
private final int tries;
private final int timeout;
private final TimeUnit unit;
private Future<String> future;
public ConsoleInput(int tries, int timeout, TimeUnit unit) {
this.tries = tries;
this.timeout = timeout;
this.unit = unit;
}
public void cancel() {
if (future != null)
future.cancel(true);
}
public String readLine() throws InterruptedException {
ExecutorService ex = Executors.newSingleThreadExecutor();
String input = null;
try {
for (int i = 0; i < tries; i++) {
future = ex.submit(new ConsoleInputReadTask());
try {
input = future.get(timeout, unit);
break;
} catch (ExecutionException e) {
e.getCause().printStackTrace();
} catch (TimeoutException e) {
future.cancel(true);
} finally {
future = null;
}
}
} finally {
ex.shutdownNow();
}
return input;
}
}

View File

@ -0,0 +1,45 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.daemon.app;
import java.io.*;
import java.util.concurrent.Callable;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConsoleInputReadTask implements Callable<String> {
public String call() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
log.debug("ConsoleInputReadTask run() called.");
String input;
do {
try {
// wait until we have data to complete a readLine()
while (!br.ready()) {
Thread.sleep(100);
}
// readline will always block until an input exists.
input = br.readLine();
} catch (InterruptedException e) {
log.debug("ConsoleInputReadTask() cancelled");
return null;
}
} while ("".equals(input));
return input;
}
}

View File

@ -19,21 +19,24 @@ package bisq.daemon.app;
import bisq.core.app.HavenoHeadlessAppMain; import bisq.core.app.HavenoHeadlessAppMain;
import bisq.core.app.HavenoSetup; import bisq.core.app.HavenoSetup;
import bisq.core.api.AccountServiceListener;
import bisq.core.app.CoreModule; import bisq.core.app.CoreModule;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.Console;
import java.util.concurrent.CancellationException;
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 lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import bisq.daemon.grpc.GrpcServer; import bisq.daemon.grpc.GrpcServer;
@Slf4j @Slf4j
@ -61,7 +64,6 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
@Override @Override
protected void launchApplication() { protected void launchApplication() {
headlessApp = new HavenoDaemon(); headlessApp = new HavenoDaemon();
UserThread.execute(this::onApplicationLaunched); UserThread.execute(this::onApplicationLaunched);
} }
@ -101,15 +103,116 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
@Override @Override
protected void onApplicationStarted() { protected void onApplicationStarted() {
super.onApplicationStarted(); super.onApplicationStarted();
grpcServer = injector.getInstance(GrpcServer.class);
grpcServer.start();
} }
@Override @Override
public void gracefulShutDown(ResultHandler resultHandler) { public void gracefulShutDown(ResultHandler resultHandler) {
super.gracefulShutDown(resultHandler); super.gracefulShutDown(resultHandler);
if (grpcServer != null) grpcServer.shutdown(); // could be null if application attempted to shutdown early
}
grpcServer.shutdown(); /**
* Start the grpcServer to allow logging in remotely.
*/
@Override
protected boolean loginAccount() {
boolean opened = super.loginAccount();
// Start rpc server in case login is coming in from rpc
grpcServer = injector.getInstance(GrpcServer.class);
grpcServer.start();
if (!opened) {
// Nonblocking, we need to stop if the login occurred through rpc.
// TODO: add a mode to mask password
ConsoleInput reader = new ConsoleInput(Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
Thread t = new Thread(() -> {
interactiveLogin(reader);
});
t.start();
// 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);
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.join();
}
} catch (InterruptedException e) {
// expected
}
accountService.removeListener(accountListener);
opened = accountService.isAccountOpen();
}
return opened;
}
/**
* Asks user for login. TODO: Implement in the desktop app.
* @return True if user logged in interactively.
*/
protected boolean interactiveLogin(ConsoleInput reader) {
Console console = System.console();
if (console == null) {
// The ConsoleInput class reads from system.in, can wait for input without a console.
log.info("No console available, account must be opened through rpc");
try {
// If user logs in through rpc, the reader will be interrupted through the event.
reader.readLine();
} catch (InterruptedException | CancellationException ex) {
log.info("Reader interrupted, continuing startup");
}
return false;
}
String openedOrCreated = "Account unlocked\n";
boolean accountExists = accountService.accountExists();
while (!accountService.isAccountOpen()) {
try {
if (accountExists) {
try {
// readPassword will not return until the user inputs something
// which is not suitable if we are waiting for rpc call which
// could login the account. Must be able to interrupt the read.
//new String(console.readPassword("Password:"));
System.out.printf("Password:\n");
String password = reader.readLine();
accountService.openAccount(password);
} catch (IncorrectPasswordException ipe) {
System.out.printf("Incorrect password\n");
}
} else {
System.out.printf("Creating a new account\n");
System.out.printf("Password:\n");
String password = reader.readLine();
System.out.printf("Confirm:\n");
String passwordConfirm = reader.readLine();
if (password.equals(passwordConfirm)) {
accountService.createAccount(password);
openedOrCreated = "Account created\n";
} else {
System.out.printf("Passwords did not match\n");
}
}
} catch (Exception ex) {
log.debug(ex.getMessage());
return false;
}
}
System.out.printf(openedOrCreated);
return true;
} }
} }

View File

@ -0,0 +1,286 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.daemon.grpc;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static bisq.proto.grpc.AccountGrpc.getAccountExistsMethod;
import static bisq.proto.grpc.AccountGrpc.getBackupAccountMethod;
import static bisq.proto.grpc.AccountGrpc.getChangePasswordMethod;
import static bisq.proto.grpc.AccountGrpc.getCloseAccountMethod;
import static bisq.proto.grpc.AccountGrpc.getCreateAccountMethod;
import static bisq.proto.grpc.AccountGrpc.getDeleteAccountMethod;
import static bisq.proto.grpc.AccountGrpc.getIsAccountOpenMethod;
import static bisq.proto.grpc.AccountGrpc.getOpenAccountMethod;
import static bisq.proto.grpc.AccountGrpc.getRestoreAccountMethod;
import static java.util.concurrent.TimeUnit.SECONDS;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.core.api.CoreApi;
import bisq.daemon.grpc.interceptor.CallRateMeteringInterceptor;
import bisq.daemon.grpc.interceptor.GrpcCallRateMeter;
import bisq.proto.grpc.AccountExistsReply;
import bisq.proto.grpc.AccountExistsRequest;
import bisq.proto.grpc.AccountGrpc.AccountImplBase;
import bisq.proto.grpc.BackupAccountReply;
import bisq.proto.grpc.BackupAccountRequest;
import bisq.proto.grpc.ChangePasswordReply;
import bisq.proto.grpc.ChangePasswordRequest;
import bisq.proto.grpc.CloseAccountReply;
import bisq.proto.grpc.CloseAccountRequest;
import bisq.proto.grpc.CreateAccountReply;
import bisq.proto.grpc.CreateAccountRequest;
import bisq.proto.grpc.DeleteAccountReply;
import bisq.proto.grpc.DeleteAccountRequest;
import bisq.proto.grpc.IsAccountOpenReply;
import bisq.proto.grpc.IsAccountOpenRequest;
import bisq.proto.grpc.IsAppInitializedReply;
import bisq.proto.grpc.IsAppInitializedRequest;
import bisq.proto.grpc.OpenAccountReply;
import bisq.proto.grpc.OpenAccountRequest;
import bisq.proto.grpc.RestoreAccountReply;
import bisq.proto.grpc.RestoreAccountRequest;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.ByteString;
import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.Optional;
import javax.inject.Inject;
import lombok.extern.slf4j.Slf4j;
@VisibleForTesting
@Slf4j
public class GrpcAccountService extends AccountImplBase {
private final CoreApi coreApi;
private final GrpcExceptionHandler exceptionHandler;
private ByteArrayOutputStream restoreStream; // in memory stream for restoring account
@Inject
public GrpcAccountService(CoreApi coreApi, GrpcExceptionHandler exceptionHandler) {
this.coreApi = coreApi;
this.exceptionHandler = exceptionHandler;
}
@Override
public void accountExists(AccountExistsRequest req, StreamObserver<AccountExistsReply> responseObserver) {
try {
var reply = AccountExistsReply.newBuilder()
.setAccountExists(coreApi.accountExists())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void isAccountOpen(IsAccountOpenRequest req, StreamObserver<IsAccountOpenReply> responseObserver) {
try {
var reply = IsAccountOpenReply.newBuilder()
.setIsAccountOpen(coreApi.isAccountOpen())
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void createAccount(CreateAccountRequest req, StreamObserver<CreateAccountReply> responseObserver) {
try {
coreApi.createAccount(req.getPassword());
var reply = CreateAccountReply.newBuilder()
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void openAccount(OpenAccountRequest req, StreamObserver<OpenAccountReply> responseObserver) {
try {
coreApi.openAccount(req.getPassword());
var reply = OpenAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
if (cause instanceof IncorrectPasswordException) cause = new IllegalStateException(cause);
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void isAppInitialized(IsAppInitializedRequest req, StreamObserver<IsAppInitializedReply> responseObserver) {
try {
var reply = IsAppInitializedReply.newBuilder().setIsAppInitialized(coreApi.isAppInitialized()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void changePassword(ChangePasswordRequest req, StreamObserver<ChangePasswordReply> responseObserver) {
try {
coreApi.changePassword(req.getPassword());
var reply = ChangePasswordReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void closeAccount(CloseAccountRequest req, StreamObserver<CloseAccountReply> responseObserver) {
try {
coreApi.closeAccount();
var reply = CloseAccountReply.newBuilder()
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void deleteAccount(DeleteAccountRequest req, StreamObserver<DeleteAccountReply> responseObserver) {
try {
coreApi.deleteAccount(() -> {
var reply = DeleteAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted(); // reply after shutdown
});
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void backupAccount(BackupAccountRequest req, StreamObserver<BackupAccountReply> responseObserver) {
// Send in large chunks to reduce unnecessary overhead. Typical backup will not be more than a few MB.
// From current testing it appears that client gRPC-web is slow in processing the bytes on download.
try {
int bufferSize = 1024 * 1024 * 8;
coreApi.backupAccount(bufferSize, (stream) -> {
try {
log.info("Sending bytes in chunks of: " + bufferSize);
byte[] buffer = new byte[bufferSize];
int length;
int total = 0;
while ((length = stream.read(buffer, 0, bufferSize)) != -1) {
total += length;
var reply = BackupAccountReply.newBuilder()
.setZipBytes(ByteString.copyFrom(buffer, 0, length))
.build();
responseObserver.onNext(reply);
}
log.info("Completed backup account total sent: " + total);
stream.close();
responseObserver.onCompleted();
} catch (Exception ex) {
exceptionHandler.handleException(log, ex, responseObserver);
}
}, (ex) -> exceptionHandler.handleException(log, ex, responseObserver));
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void restoreAccount(RestoreAccountRequest req, StreamObserver<RestoreAccountReply> responseObserver) {
try {
// Fail fast since uploading and processing bytes takes resources.
if (coreApi.accountExists()) throw new IllegalStateException("Cannot restore account if there is an existing account");
// If the entire zip is in memory, no need to write to disk.
// Restore the account directly from the zip stream.
if (!req.getHasMore() && req.getOffset() == 0) {
var inputStream = req.getZipBytes().newInput();
coreApi.restoreAccount(inputStream, 1024 * 64, () -> {
var reply = RestoreAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted(); // reply after shutdown
});
} else {
if (req.getOffset() == 0) {
log.info("RestoreAccount starting new chunked zip");
restoreStream = new ByteArrayOutputStream((int) req.getTotalLength());
}
if (restoreStream.size() != req.getOffset()) {
log.warn("Stream offset doesn't match current position");
IllegalStateException cause = new IllegalStateException("Stream offset doesn't match current position");
exceptionHandler.handleException(log, cause, responseObserver);
} else {
log.info("RestoreAccount writing chunk size " + req.getZipBytes().size());
req.getZipBytes().writeTo(restoreStream);
}
if (!req.getHasMore()) {
var inputStream = new ByteArrayInputStream(restoreStream.toByteArray());
restoreStream.close();
restoreStream = null;
coreApi.restoreAccount(inputStream, 1024 * 64, () -> {
var reply = RestoreAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted(); // reply after shutdown
});
} else {
var reply = RestoreAccountReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
final ServerInterceptor[] interceptors() {
Optional<ServerInterceptor> rateMeteringInterceptor = rateMeteringInterceptor();
return rateMeteringInterceptor.map(serverInterceptor ->
new ServerInterceptor[]{serverInterceptor}).orElseGet(() -> new ServerInterceptor[0]);
}
final Optional<ServerInterceptor> rateMeteringInterceptor() {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{
put(getAccountExistsMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getBackupAccountMethod().getFullMethodName(), new GrpcCallRateMeter(5, SECONDS));
put(getChangePasswordMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getCloseAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getCreateAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getDeleteAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getIsAccountOpenMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getOpenAccountMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getRestoreAccountMethod().getFullMethodName(), new GrpcCallRateMeter(5, SECONDS));
}}
)));
}
}

View File

@ -42,9 +42,9 @@ import bisq.proto.grpc.StartCheckingConnectionsReply;
import bisq.proto.grpc.StartCheckingConnectionsRequest; import bisq.proto.grpc.StartCheckingConnectionsRequest;
import bisq.proto.grpc.StopCheckingConnectionsReply; import bisq.proto.grpc.StopCheckingConnectionsReply;
import bisq.proto.grpc.StopCheckingConnectionsRequest; import bisq.proto.grpc.StopCheckingConnectionsRequest;
import bisq.proto.grpc.UriConnection; import bisq.proto.grpc.UrlConnection;
import java.net.URI; import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -84,7 +84,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
public void removeConnection(RemoveConnectionRequest request, public void removeConnection(RemoveConnectionRequest request,
StreamObserver<RemoveConnectionReply> responseObserver) { StreamObserver<RemoveConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
coreApi.removeMoneroConnection(validateUri(request.getUri())); coreApi.removeMoneroConnection(validateUri(request.getUrl()));
return RemoveConnectionReply.newBuilder().build(); return RemoveConnectionReply.newBuilder().build();
}); });
} }
@ -93,7 +93,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
public void getConnection(GetConnectionRequest request, public void getConnection(GetConnectionRequest request,
StreamObserver<GetConnectionReply> responseObserver) { StreamObserver<GetConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
UriConnection replyConnection = toUriConnection(coreApi.getMoneroConnection()); UrlConnection replyConnection = toUrlConnection(coreApi.getMoneroConnection());
GetConnectionReply.Builder builder = GetConnectionReply.newBuilder(); GetConnectionReply.Builder builder = GetConnectionReply.newBuilder();
if (replyConnection != null) { if (replyConnection != null) {
builder.setConnection(replyConnection); builder.setConnection(replyConnection);
@ -107,8 +107,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<GetConnectionsReply> responseObserver) { StreamObserver<GetConnectionsReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
List<MoneroRpcConnection> connections = coreApi.getMoneroConnections(); List<MoneroRpcConnection> connections = coreApi.getMoneroConnections();
List<UriConnection> replyConnections = connections.stream() List<UrlConnection> replyConnections = connections.stream()
.map(GrpcMoneroConnectionsService::toUriConnection).collect(Collectors.toList()); .map(GrpcMoneroConnectionsService::toUrlConnection).collect(Collectors.toList());
return GetConnectionsReply.newBuilder().addAllConnections(replyConnections).build(); return GetConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
}); });
} }
@ -117,8 +117,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
public void setConnection(SetConnectionRequest request, public void setConnection(SetConnectionRequest request,
StreamObserver<SetConnectionReply> responseObserver) { StreamObserver<SetConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
if (request.getUri() != null && !request.getUri().isEmpty()) if (request.getUrl() != null && !request.getUrl().isEmpty())
coreApi.setMoneroConnection(validateUri(request.getUri())); coreApi.setMoneroConnection(validateUri(request.getUrl()));
else if (request.hasConnection()) else if (request.hasConnection())
coreApi.setMoneroConnection(toMoneroRpcConnection(request.getConnection())); coreApi.setMoneroConnection(toMoneroRpcConnection(request.getConnection()));
else coreApi.setMoneroConnection((MoneroRpcConnection) null); // disconnect from client else coreApi.setMoneroConnection((MoneroRpcConnection) null); // disconnect from client
@ -131,7 +131,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<CheckConnectionReply> responseObserver) { StreamObserver<CheckConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
MoneroRpcConnection connection = coreApi.checkMoneroConnection(); MoneroRpcConnection connection = coreApi.checkMoneroConnection();
UriConnection replyConnection = toUriConnection(connection); UrlConnection replyConnection = toUrlConnection(connection);
CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder(); CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder();
if (replyConnection != null) { if (replyConnection != null) {
builder.setConnection(replyConnection); builder.setConnection(replyConnection);
@ -145,8 +145,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<CheckConnectionsReply> responseObserver) { StreamObserver<CheckConnectionsReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
List<MoneroRpcConnection> connections = coreApi.checkMoneroConnections(); List<MoneroRpcConnection> connections = coreApi.checkMoneroConnections();
List<UriConnection> replyConnections = connections.stream() List<UrlConnection> replyConnections = connections.stream()
.map(GrpcMoneroConnectionsService::toUriConnection).collect(Collectors.toList()); .map(GrpcMoneroConnectionsService::toUrlConnection).collect(Collectors.toList());
return CheckConnectionsReply.newBuilder().addAllConnections(replyConnections).build(); return CheckConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
}); });
} }
@ -176,7 +176,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<GetBestAvailableConnectionReply> responseObserver) { StreamObserver<GetBestAvailableConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> { handleRequest(responseObserver, () -> {
MoneroRpcConnection connection = coreApi.getBestAvailableMoneroConnection(); MoneroRpcConnection connection = coreApi.getBestAvailableMoneroConnection();
UriConnection replyConnection = toUriConnection(connection); UrlConnection replyConnection = toUrlConnection(connection);
GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder(); GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder();
if (replyConnection != null) { if (replyConnection != null) {
builder.setConnection(replyConnection); builder.setConnection(replyConnection);
@ -211,43 +211,40 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
} }
private static UriConnection toUriConnection(MoneroRpcConnection rpcConnection) { private static UrlConnection toUrlConnection(MoneroRpcConnection rpcConnection) {
if (rpcConnection == null) return null; if (rpcConnection == null) return null;
return UriConnection.newBuilder() return UrlConnection.newBuilder()
.setUri(rpcConnection.getUri()) .setUrl(rpcConnection.getUri())
.setPriority(rpcConnection.getPriority()) .setPriority(rpcConnection.getPriority())
.setOnlineStatus(toOnlineStatus(rpcConnection.isOnline())) .setOnlineStatus(toOnlineStatus(rpcConnection.isOnline()))
.setAuthenticationStatus(toAuthenticationStatus(rpcConnection.isAuthenticated())) .setAuthenticationStatus(toAuthenticationStatus(rpcConnection.isAuthenticated()))
.build(); .build();
} }
private static UriConnection.AuthenticationStatus toAuthenticationStatus(Boolean authenticated) { private static UrlConnection.AuthenticationStatus toAuthenticationStatus(Boolean authenticated) {
if (authenticated == null) return UriConnection.AuthenticationStatus.NO_AUTHENTICATION; if (authenticated == null) return UrlConnection.AuthenticationStatus.NO_AUTHENTICATION;
else if (authenticated) return UriConnection.AuthenticationStatus.AUTHENTICATED; else if (authenticated) return UrlConnection.AuthenticationStatus.AUTHENTICATED;
else return UriConnection.AuthenticationStatus.NOT_AUTHENTICATED; else return UrlConnection.AuthenticationStatus.NOT_AUTHENTICATED;
} }
private static UriConnection.OnlineStatus toOnlineStatus(Boolean online) { private static UrlConnection.OnlineStatus toOnlineStatus(Boolean online) {
if (online == null) return UriConnection.OnlineStatus.UNKNOWN; if (online == null) return UrlConnection.OnlineStatus.UNKNOWN;
else if (online) return UriConnection.OnlineStatus.ONLINE; else if (online) return UrlConnection.OnlineStatus.ONLINE;
else return UriConnection.OnlineStatus.OFFLINE; else return UrlConnection.OnlineStatus.OFFLINE;
} }
private static MoneroRpcConnection toMoneroRpcConnection(UriConnection uriConnection) throws URISyntaxException { private static MoneroRpcConnection toMoneroRpcConnection(UrlConnection uriConnection) throws MalformedURLException {
if (uriConnection == null) return null; if (uriConnection == null) return null;
return new MoneroRpcConnection( return new MoneroRpcConnection(
validateUri(uriConnection.getUri()), validateUri(uriConnection.getUrl()),
nullIfEmpty(uriConnection.getUsername()), nullIfEmpty(uriConnection.getUsername()),
nullIfEmpty(uriConnection.getPassword())) nullIfEmpty(uriConnection.getPassword()))
.setPriority(uriConnection.getPriority()); .setPriority(uriConnection.getPriority());
} }
private static String validateUri(String uri) throws URISyntaxException { private static String validateUri(String url) throws MalformedURLException {
if (uri.isEmpty()) { if (url.isEmpty()) throw new IllegalArgumentException("URL is required");
throw new IllegalArgumentException("URI is required"); return new URL(url).toString(); // validate and return
}
// Create new URI for validation, internally String is used again
return new URI(uri).toString();
} }
private static String nullIfEmpty(String value) { private static String nullIfEmpty(String value) {

View File

@ -49,6 +49,7 @@ public class GrpcServer {
public GrpcServer(CoreContext coreContext, public GrpcServer(CoreContext coreContext,
Config config, Config config,
PasswordAuthInterceptor passwordAuthInterceptor, PasswordAuthInterceptor passwordAuthInterceptor,
GrpcAccountService accountService,
GrpcDisputeAgentsService disputeAgentsService, GrpcDisputeAgentsService disputeAgentsService,
GrpcHelpService helpService, GrpcHelpService helpService,
GrpcOffersService offersService, GrpcOffersService offersService,
@ -63,6 +64,7 @@ public class GrpcServer {
GrpcMoneroConnectionsService moneroConnectionsService) { GrpcMoneroConnectionsService moneroConnectionsService) {
this.server = ServerBuilder.forPort(config.apiPort) this.server = ServerBuilder.forPort(config.apiPort)
.executor(UserThread.getExecutor()) .executor(UserThread.getExecutor())
.addService(interceptForward(accountService, accountService.interceptors()))
.addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors())) .addService(interceptForward(disputeAgentsService, disputeAgentsService.interceptors()))
.addService(interceptForward(helpService, helpService.interceptors())) .addService(interceptForward(helpService, helpService.interceptors()))
.addService(interceptForward(offersService, offersService.interceptors())) .addService(interceptForward(offersService, offersService.interceptors()))

View File

@ -48,9 +48,14 @@ class GrpcShutdownService extends ShutdownServerGrpc.ShutdownServerImplBase {
StreamObserver<StopReply> responseObserver) { StreamObserver<StopReply> responseObserver) {
try { try {
log.info("Shutdown request received."); log.info("Shutdown request received.");
HavenoHeadlessApp.setOnGracefulShutDownHandler(new Runnable() {
@Override
public void run() {
var reply = StopReply.newBuilder().build(); var reply = StopReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
}
});
UserThread.runAfter(HavenoHeadlessApp.getShutDownHandler(), 500, MILLISECONDS); UserThread.runAfter(HavenoHeadlessApp.getShutDownHandler(), 500, MILLISECONDS);
} catch (Throwable cause) { } catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver); exceptionHandler.handleException(log, cause, responseObserver);

View File

@ -40,9 +40,9 @@ import bisq.desktop.util.GUIUtil;
import bisq.core.account.sign.SignedWitnessService; import bisq.core.account.sign.SignedWitnessService;
import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.alert.PrivateNotificationManager; import bisq.core.alert.PrivateNotificationManager;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup; import bisq.core.app.HavenoSetup;
import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.CryptoCurrency; import bisq.core.locale.CryptoCurrency;
import bisq.core.locale.CurrencyUtil; import bisq.core.locale.CurrencyUtil;
@ -53,7 +53,6 @@ import bisq.core.payment.AliPayAccount;
import bisq.core.payment.AmazonGiftCardAccount; import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.CryptoCurrencyAccount; import bisq.core.payment.CryptoCurrencyAccount;
import bisq.core.payment.RevolutAccount; import bisq.core.payment.RevolutAccount;
import bisq.core.payment.payload.AssetsAccountPayload;
import bisq.core.presentation.BalancePresentation; import bisq.core.presentation.BalancePresentation;
import bisq.core.presentation.SupportTicketsPresentation; import bisq.core.presentation.SupportTicketsPresentation;
import bisq.core.presentation.TradePresentation; import bisq.core.presentation.TradePresentation;
@ -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 bisqSetup;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
private final User user; private final User user;
private final BalancePresentation balancePresentation; private final BalancePresentation balancePresentation;
private final TradePresentation tradePresentation; private final TradePresentation tradePresentation;
@ -140,7 +139,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
private final DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1); private final DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1);
private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty(); private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty();
private final StringProperty footerVersionInfo = new SimpleStringProperty(); private final StringProperty footerVersionInfo = new SimpleStringProperty();
private Timer checkNumberOfBtcPeersTimer; private Timer checkNumberOfXmrPeersTimer;
private Timer checkNumberOfP2pNetworkPeersTimer; private Timer checkNumberOfP2pNetworkPeersTimer;
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> tradesAndUIReady; private MonadicBinding<Boolean> tradesAndUIReady;
@ -153,7 +152,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
@Inject @Inject
public MainViewModel(HavenoSetup bisqSetup, public MainViewModel(HavenoSetup bisqSetup,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
BtcWalletService btcWalletService, BtcWalletService btcWalletService,
User user, User user,
BalancePresentation balancePresentation, BalancePresentation balancePresentation,
@ -178,7 +177,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
TorNetworkSettingsWindow torNetworkSettingsWindow, TorNetworkSettingsWindow torNetworkSettingsWindow,
CorruptedStorageFileHandler corruptedStorageFileHandler) { CorruptedStorageFileHandler corruptedStorageFileHandler) {
this.bisqSetup = bisqSetup; this.bisqSetup = bisqSetup;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.user = user; this.user = user;
this.balancePresentation = balancePresentation; this.balancePresentation = balancePresentation;
this.tradePresentation = tradePresentation; this.tradePresentation = tradePresentation;
@ -258,7 +257,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
}); });
setupP2PNumPeersWatcher(); setupP2PNumPeersWatcher();
setupBtcNumPeersWatcher(); setupXmrNumPeersWatcher();
marketPricePresentation.setup(); marketPricePresentation.setup();
accountPresentation.setup(); accountPresentation.setup();
@ -509,19 +508,19 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
}); });
} }
private void setupBtcNumPeersWatcher() { private void setupXmrNumPeersWatcher() {
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> { connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
int numPeers = (int) newValue; int numPeers = (int) newValue;
if ((int) oldValue > 0 && numPeers == 0) { if ((int) oldValue > 0 && numPeers == 0) {
if (checkNumberOfBtcPeersTimer != null) if (checkNumberOfXmrPeersTimer != null)
checkNumberOfBtcPeersTimer.stop(); checkNumberOfXmrPeersTimer.stop();
checkNumberOfBtcPeersTimer = UserThread.runAfter(() -> { checkNumberOfXmrPeersTimer = UserThread.runAfter(() -> {
// check again numPeers // check again numPeers
if (walletsSetup.numPeersProperty().get() == 0) { if (connectionService.numPeersProperty().get() == 0) {
if (localBitcoinNode.shouldBeUsed()) if (localBitcoinNode.shouldBeUsed())
getWalletServiceErrorMsg().set( getWalletServiceErrorMsg().set(
Res.get("mainView.networkWarning.localhostBitcoinLost", Res.get("mainView.networkWarning.localhostBitcoinLost", // TODO: update error message for XMR
Res.getBaseCurrencyName().toLowerCase())); Res.getBaseCurrencyName().toLowerCase()));
else else
getWalletServiceErrorMsg().set( getWalletServiceErrorMsg().set(
@ -532,8 +531,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
} }
}, 5); }, 5);
} else if ((int) oldValue == 0 && numPeers > 0) { } else if ((int) oldValue == 0 && numPeers > 0) {
if (checkNumberOfBtcPeersTimer != null) if (checkNumberOfXmrPeersTimer != null)
checkNumberOfBtcPeersTimer.stop(); checkNumberOfXmrPeersTimer.stop();
getWalletServiceErrorMsg().set(null); getWalletServiceErrorMsg().set(null);
} }
}); });

View File

@ -33,9 +33,8 @@ import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.PasswordValidator; import bisq.desktop.util.validation.PasswordValidator;
import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsManager;
import bisq.core.crypto.ScryptUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.common.crypto.ScryptUtil;
import bisq.common.util.Tuple4; import bisq.common.util.Tuple4;
import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.KeyCrypterScrypt;

View File

@ -61,7 +61,6 @@ import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepo
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse; import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerSendsInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs; import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx; import bisq.core.trade.protocol.tasks.seller_as_taker.SellerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.taker.TakerCreateFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse; import bisq.core.trade.protocol.tasks.taker.TakerProcessesInputsForDepositTxResponse;
import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx; import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment; import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
@ -119,7 +118,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList( FXCollections.observableArrayList(Arrays.asList(
ApplyFilter.class, ApplyFilter.class,
TakerVerifyMakerFeePayment.class, TakerVerifyMakerFeePayment.class,
TakerCreateFeeTx.class, // TODO (woodser): rename to TakerCreateFeeTx
SellerAsTakerCreatesDepositTxInputs.class, SellerAsTakerCreatesDepositTxInputs.class,
TakerProcessesInputsForDepositTxResponse.class, TakerProcessesInputsForDepositTxResponse.class,
@ -182,7 +180,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList( FXCollections.observableArrayList(Arrays.asList(
ApplyFilter.class, ApplyFilter.class,
TakerVerifyMakerFeePayment.class, TakerVerifyMakerFeePayment.class,
TakerCreateFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.class, BuyerAsTakerCreatesDepositTxInputs.class,
TakerProcessesInputsForDepositTxResponse.class, TakerProcessesInputsForDepositTxResponse.class,

View File

@ -24,7 +24,7 @@ import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Tradable; import bisq.core.trade.Tradable;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRingProvider;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -35,17 +35,17 @@ public class TransactionAwareTradableFactory {
private final ArbitrationManager arbitrationManager; private final ArbitrationManager arbitrationManager;
private final RefundManager refundManager; private final RefundManager refundManager;
private final XmrWalletService xmrWalletService; private final XmrWalletService xmrWalletService;
private final PubKeyRing pubKeyRing; private final PubKeyRingProvider pubKeyRingProvider;
@Inject @Inject
TransactionAwareTradableFactory(ArbitrationManager arbitrationManager, TransactionAwareTradableFactory(ArbitrationManager arbitrationManager,
RefundManager refundManager, RefundManager refundManager,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
PubKeyRing pubKeyRing) { PubKeyRingProvider pubKeyRingProvider) {
this.arbitrationManager = arbitrationManager; this.arbitrationManager = arbitrationManager;
this.refundManager = refundManager; this.refundManager = refundManager;
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
this.pubKeyRing = pubKeyRing; this.pubKeyRingProvider = pubKeyRingProvider;
} }
TransactionAwareTradable create(Tradable delegate) { TransactionAwareTradable create(Tradable delegate) {
@ -56,7 +56,7 @@ public class TransactionAwareTradableFactory {
arbitrationManager, arbitrationManager,
refundManager, refundManager,
xmrWalletService, xmrWalletService,
pubKeyRing); pubKeyRingProvider.get());
} else { } else {
return new DummyTransactionAwareTradable(delegate); return new DummyTransactionAwareTradable(delegate);
} }

View File

@ -28,8 +28,7 @@ import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.OfferDetailsWindow; import bisq.desktop.main.overlays.windows.OfferDetailsWindow;
import bisq.desktop.main.overlays.windows.TradeDetailsWindow; import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOffer;
@ -103,7 +102,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final BtcWalletService btcWalletService; private final BtcWalletService btcWalletService;
private final P2PService p2PService; private final P2PService p2PService;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
private final Preferences preferences; private final Preferences preferences;
private final TradeDetailsWindow tradeDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow;
private final OfferDetailsWindow offerDetailsWindow; private final OfferDetailsWindow offerDetailsWindow;
@ -120,14 +119,14 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Inject @Inject
private TransactionsView(BtcWalletService btcWalletService, private TransactionsView(BtcWalletService btcWalletService,
P2PService p2PService, P2PService p2PService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
Preferences preferences, Preferences preferences,
TradeDetailsWindow tradeDetailsWindow, TradeDetailsWindow tradeDetailsWindow,
OfferDetailsWindow offerDetailsWindow, OfferDetailsWindow offerDetailsWindow,
DisplayedTransactionsFactory displayedTransactionsFactory) { DisplayedTransactionsFactory displayedTransactionsFactory) {
this.btcWalletService = btcWalletService; this.btcWalletService = btcWalletService;
this.p2PService = p2PService; this.p2PService = p2PService;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.preferences = preferences; this.preferences = preferences;
this.tradeDetailsWindow = tradeDetailsWindow; this.tradeDetailsWindow = tradeDetailsWindow;
this.offerDetailsWindow = offerDetailsWindow; this.offerDetailsWindow = offerDetailsWindow;
@ -537,7 +536,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
} }
private void revertTransaction(String txId, @Nullable Tradable tradable) { private void revertTransaction(String txId, @Nullable Tradable tradable) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
try { try {
btcWalletService.doubleSpendTransaction(txId, () -> { btcWalletService.doubleSpendTransaction(txId, () -> {
if (tradable != null) if (tradable != null)

View File

@ -27,7 +27,7 @@ import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.locale.BankUtil; import bisq.core.locale.BankUtil;
import bisq.core.locale.CountryUtil; import bisq.core.locale.CountryUtil;
import bisq.core.locale.CryptoCurrency; import bisq.core.locale.CryptoCurrency;
@ -97,8 +97,8 @@ class OfferBookViewModel extends ActivatableViewModel {
private final User user; private final User user;
private final OfferBook offerBook; private final OfferBook offerBook;
final Preferences preferences; final Preferences preferences;
private final WalletsSetup walletsSetup;
private final P2PService p2PService; private final P2PService p2PService;
private final CoreMoneroConnectionsService connectionService;
final PriceFeedService priceFeedService; final PriceFeedService priceFeedService;
private final ClosedTradableManager closedTradableManager; private final ClosedTradableManager closedTradableManager;
final AccountAgeWitnessService accountAgeWitnessService; final AccountAgeWitnessService accountAgeWitnessService;
@ -142,7 +142,7 @@ class OfferBookViewModel extends ActivatableViewModel {
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
OfferBook offerBook, OfferBook offerBook,
Preferences preferences, Preferences preferences,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
P2PService p2PService, P2PService p2PService,
PriceFeedService priceFeedService, PriceFeedService priceFeedService,
ClosedTradableManager closedTradableManager, ClosedTradableManager closedTradableManager,
@ -157,7 +157,7 @@ class OfferBookViewModel extends ActivatableViewModel {
this.user = user; this.user = user;
this.offerBook = offerBook; this.offerBook = offerBook;
this.preferences = preferences; this.preferences = preferences;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.p2PService = p2PService; this.p2PService = p2PService;
this.priceFeedService = priceFeedService; this.priceFeedService = priceFeedService;
this.closedTradableManager = closedTradableManager; this.closedTradableManager = closedTradableManager;
@ -561,7 +561,7 @@ class OfferBookViewModel extends ActivatableViewModel {
boolean canCreateOrTakeOffer() { boolean canCreateOrTakeOffer() {
return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) && return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) &&
GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(walletsSetup) && GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(connectionService) &&
GUIUtil.isBootstrappedOrShowPopup(p2PService); GUIUtil.isBootstrappedOrShowPopup(p2PService);
} }

View File

@ -6,8 +6,7 @@ import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.popups.Popup; import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.Transitions; import bisq.desktop.util.Transitions;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions; import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.Res; import bisq.core.locale.Res;
@ -53,7 +52,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
private final WalletPasswordWindow walletPasswordWindow; private final WalletPasswordWindow walletPasswordWindow;
private final OpenOfferManager openOfferManager; private final OpenOfferManager openOfferManager;
private final P2PService p2PService; private final P2PService p2PService;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
private final BtcWalletService btcWalletService; private final BtcWalletService btcWalletService;
private final CoinFormatter btcFormatter; private final CoinFormatter btcFormatter;
@ -65,7 +64,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
public BtcEmptyWalletWindow(WalletPasswordWindow walletPasswordWindow, public BtcEmptyWalletWindow(WalletPasswordWindow walletPasswordWindow,
OpenOfferManager openOfferManager, OpenOfferManager openOfferManager,
P2PService p2PService, P2PService p2PService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
BtcWalletService btcWalletService, BtcWalletService btcWalletService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) { @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
headLine(Res.get("emptyWalletWindow.headline", "BTC")); headLine(Res.get("emptyWalletWindow.headline", "BTC"));
@ -73,13 +72,14 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
type = Type.Instruction; type = Type.Instruction;
this.p2PService = p2PService; this.p2PService = p2PService;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.btcWalletService = btcWalletService; this.btcWalletService = btcWalletService;
this.btcFormatter = btcFormatter; this.btcFormatter = btcFormatter;
this.walletPasswordWindow = walletPasswordWindow; this.walletPasswordWindow = walletPasswordWindow;
this.openOfferManager = openOfferManager; this.openOfferManager = openOfferManager;
} }
@Override
public void show() { public void show() {
createGridPane(); createGridPane();
addHeadLine(); addHeadLine();
@ -143,7 +143,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
} }
private void doEmptyWallet(KeyParameter aesKey) { private void doEmptyWallet(KeyParameter aesKey) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
if (!openOfferManager.getObservableList().isEmpty()) { if (!openOfferManager.getObservableList().isEmpty()) {
UserThread.runAfter(() -> UserThread.runAfter(() ->
new Popup().warning(Res.get("emptyWalletWindow.openOffers.warn")) new Popup().warning(Res.get("emptyWalletWindow.openOffers.warn"))

View File

@ -25,11 +25,10 @@ import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.desktop.util.validation.LengthValidator; import bisq.desktop.util.validation.LengthValidator;
import bisq.desktop.util.validation.PercentageNumberValidator; import bisq.desktop.util.validation.PercentageNumberValidator;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.exceptions.TransactionVerificationException; import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.TxBroadcastException; import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.exceptions.WalletException; import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster; import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsManager;
@ -92,7 +91,6 @@ import java.time.Instant;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -111,7 +109,7 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
private final P2PService p2PService; private final P2PService p2PService;
private final MediationManager mediationManager; private final MediationManager mediationManager;
private final Preferences preferences; private final Preferences preferences;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
private final WalletsManager walletsManager; private final WalletsManager walletsManager;
GridPane inputsGridPane; GridPane inputsGridPane;
GridPane importTxGridPane; GridPane importTxGridPane;
@ -150,17 +148,18 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
P2PService p2PService, P2PService p2PService,
MediationManager mediationManager, MediationManager mediationManager,
Preferences preferences, Preferences preferences,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
WalletsManager walletsManager) { WalletsManager walletsManager) {
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
this.p2PService = p2PService; this.p2PService = p2PService;
this.mediationManager = mediationManager; this.mediationManager = mediationManager;
this.preferences = preferences; this.preferences = preferences;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.walletsManager = walletsManager; this.walletsManager = walletsManager;
type = Type.Attention; type = Type.Attention;
} }
@Override
public void show() { public void show() {
if (headLine == null) if (headLine == null)
headLine = "Emergency MultiSig payout tool"; // We dont translate here as it is for dev only purpose headLine = "Emergency MultiSig payout tool"; // We dont translate here as it is for dev only purpose
@ -810,7 +809,7 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
} }
}; };
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) { if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
try { try {
tradeWalletService.emergencyPublishPayoutTxFrom2of2MultiSig( tradeWalletService.emergencyPublishPayoutTxFrom2of2MultiSig(
txIdAndHex.second, txIdAndHex.second,

View File

@ -28,12 +28,12 @@ import bisq.desktop.util.Layout;
import bisq.desktop.util.Transitions; import bisq.desktop.util.Transitions;
import bisq.core.btc.wallet.WalletsManager; import bisq.core.btc.wallet.WalletsManager;
import bisq.core.crypto.ScryptUtil;
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.UserThread;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.ScryptUtil;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import org.bitcoinj.crypto.KeyCrypterScrypt; import org.bitcoinj.crypto.KeyCrypterScrypt;

View File

@ -29,7 +29,7 @@ import bisq.desktop.main.support.dispute.client.mediation.MediationClientView;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
@ -55,6 +55,7 @@ import bisq.core.user.Preferences;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.FaultHandler; import bisq.common.handlers.FaultHandler;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
@ -97,7 +98,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
public final ArbitrationManager arbitrationManager; public final ArbitrationManager arbitrationManager;
public final MediationManager mediationManager; public final MediationManager mediationManager;
private final P2PService p2PService; private final P2PService p2PService;
private final WalletsSetup walletsSetup; private final CoreMoneroConnectionsService connectionService;
@Getter @Getter
private final AccountAgeWitnessService accountAgeWitnessService; private final AccountAgeWitnessService accountAgeWitnessService;
public final Navigation navigation; public final Navigation navigation;
@ -120,7 +121,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
private ChangeListener<Trade.State> tradeStateChangeListener; private ChangeListener<Trade.State> tradeStateChangeListener;
private Trade selectedTrade; private Trade selectedTrade;
@Getter @Getter
private final PubKeyRing pubKeyRing; private final PubKeyRingProvider pubKeyRingProvider;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialization // Constructor, initialization
@ -129,13 +130,13 @@ public class PendingTradesDataModel extends ActivatableDataModel {
@Inject @Inject
public PendingTradesDataModel(TradeManager tradeManager, public PendingTradesDataModel(TradeManager tradeManager,
XmrWalletService xmrWalletService, XmrWalletService xmrWalletService,
PubKeyRing pubKeyRing, PubKeyRingProvider pubKeyRingProvider,
ArbitrationManager arbitrationManager, ArbitrationManager arbitrationManager,
MediationManager mediationManager, MediationManager mediationManager,
TraderChatManager traderChatManager, TraderChatManager traderChatManager,
Preferences preferences, Preferences preferences,
P2PService p2PService, P2PService p2PService,
WalletsSetup walletsSetup, CoreMoneroConnectionsService connectionService,
AccountAgeWitnessService accountAgeWitnessService, AccountAgeWitnessService accountAgeWitnessService,
Navigation navigation, Navigation navigation,
WalletPasswordWindow walletPasswordWindow, WalletPasswordWindow walletPasswordWindow,
@ -143,13 +144,13 @@ public class PendingTradesDataModel extends ActivatableDataModel {
OfferUtil offerUtil) { OfferUtil offerUtil) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.xmrWalletService = xmrWalletService; this.xmrWalletService = xmrWalletService;
this.pubKeyRing = pubKeyRing; this.pubKeyRingProvider = pubKeyRingProvider;
this.arbitrationManager = arbitrationManager; this.arbitrationManager = arbitrationManager;
this.mediationManager = mediationManager; this.mediationManager = mediationManager;
this.traderChatManager = traderChatManager; this.traderChatManager = traderChatManager;
this.preferences = preferences; this.preferences = preferences;
this.p2PService = p2PService; this.p2PService = p2PService;
this.walletsSetup = walletsSetup; this.connectionService = connectionService;
this.accountAgeWitnessService = accountAgeWitnessService; this.accountAgeWitnessService = accountAgeWitnessService;
this.navigation = navigation; this.navigation = navigation;
this.walletPasswordWindow = walletPasswordWindow; this.walletPasswordWindow = walletPasswordWindow;
@ -513,11 +514,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr byte[] depositTxSerialized = null; // depositTx.bitcoinSerialize(); // TODO (woodser): no serialized txs in xmr
Dispute dispute = new Dispute(new Date().getTime(), Dispute dispute = new Dispute(new Date().getTime(),
trade.getId(), trade.getId(),
pubKeyRing.hashCode(), // traderId pubKeyRingProvider.get().hashCode(), // trader id
true, true,
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
isMaker, isMaker,
pubKeyRing, pubKeyRingProvider.get(),
trade.getDate().getTime(), trade.getDate().getTime(),
trade.getMaxTradePeriodDate().getTime(), trade.getMaxTradePeriodDate().getTime(),
trade.getContract(), trade.getContract(),
@ -549,11 +550,11 @@ public class PendingTradesDataModel extends ActivatableDataModel {
String depositTxHashAsString = null; // depositTx.getHashAsString(); TODO (woodser) String depositTxHashAsString = null; // depositTx.getHashAsString(); TODO (woodser)
Dispute dispute = new Dispute(new Date().getTime(), Dispute dispute = new Dispute(new Date().getTime(),
trade.getId(), trade.getId(),
pubKeyRing.hashCode(), // traderId, pubKeyRingProvider.get().hashCode(), // trader id,
true, true,
(offer.getDirection() == OfferPayload.Direction.BUY) == isMaker, (offer.getDirection() == OfferPayload.Direction.BUY) == isMaker,
isMaker, isMaker,
pubKeyRing, pubKeyRingProvider.get(),
trade.getDate().getTime(), trade.getDate().getTime(),
trade.getMaxTradePeriodDate().getTime(), trade.getMaxTradePeriodDate().getTime(),
trade.getContract(), trade.getContract(),
@ -595,7 +596,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
} }
public boolean isReadyForTxBroadcast() { public boolean isReadyForTxBroadcast() {
return GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup); return GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService);
} }
public boolean isBootstrappedOrShowPopup() { public boolean isBootstrappedOrShowPopup() {

View File

@ -398,7 +398,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
return Res.get("portfolio.pending.failedTrade.missingContract"); return Res.get("portfolio.pending.failedTrade.missingContract");
} }
PubKeyRing myPubKeyRing = model.dataModel.getPubKeyRing(); PubKeyRing myPubKeyRing = model.dataModel.getPubKeyRingProvider().get();
boolean isMyRoleBuyer = contract.isMyRoleBuyer(myPubKeyRing); boolean isMyRoleBuyer = contract.isMyRoleBuyer(myPubKeyRing);
boolean isMyRoleMaker = contract.isMyRoleMaker(myPubKeyRing); boolean isMyRoleMaker = contract.isMyRoleMaker(myPubKeyRing);

View File

@ -675,7 +675,7 @@ public abstract class TradeStepView extends AnchorPane {
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get(); DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
Contract contract = checkNotNull(trade.getContract(), "contract must not be null"); Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
boolean isMyRoleBuyer = contract.isMyRoleBuyer(model.dataModel.getPubKeyRing()); boolean isMyRoleBuyer = contract.isMyRoleBuyer(model.dataModel.getPubKeyRingProvider().get());
String buyerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount()); String buyerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getBuyerPayoutAmount());
String sellerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount()); String sellerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount());
String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount; String myPayoutAmount = isMyRoleBuyer ? buyerPayoutAmount : sellerPayoutAmount;

View File

@ -28,6 +28,7 @@ import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow; import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
import bisq.desktop.util.GUIUtil; import bisq.desktop.util.GUIUtil;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.nodes.BtcNodes; import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.LocalBitcoinNode; import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.setup.WalletsSetup; import bisq.core.btc.setup.WalletsSetup;
@ -38,7 +39,6 @@ import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
import bisq.core.util.validation.RegexValidator; import bisq.core.util.validation.RegexValidator;
import bisq.core.util.validation.RegexValidatorFactory; import bisq.core.util.validation.RegexValidatorFactory;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.network.p2p.network.Statistic; import bisq.network.p2p.network.Statistic;
@ -119,6 +119,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
private final ClockWatcher clockWatcher; private final ClockWatcher clockWatcher;
private final WalletsSetup walletsSetup; private final WalletsSetup walletsSetup;
private final P2PService p2PService; private final P2PService p2PService;
private final CoreMoneroConnectionsService connectionManager;
private final ObservableList<P2pNetworkListItem> p2pNetworkListItems = FXCollections.observableArrayList(); private final ObservableList<P2pNetworkListItem> p2pNetworkListItems = FXCollections.observableArrayList();
private final SortedList<P2pNetworkListItem> p2pSortedList = new SortedList<>(p2pNetworkListItems); private final SortedList<P2pNetworkListItem> p2pSortedList = new SortedList<>(p2pNetworkListItems);
@ -139,6 +140,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@Inject @Inject
public NetworkSettingsView(WalletsSetup walletsSetup, public NetworkSettingsView(WalletsSetup walletsSetup,
P2PService p2PService, P2PService p2PService,
CoreMoneroConnectionsService connectionManager,
Preferences preferences, Preferences preferences,
BtcNodes btcNodes, BtcNodes btcNodes,
FilterManager filterManager, FilterManager filterManager,
@ -148,6 +150,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
super(); super();
this.walletsSetup = walletsSetup; this.walletsSetup = walletsSetup;
this.p2PService = p2PService; this.p2PService = p2PService;
this.connectionManager = connectionManager;
this.preferences = preferences; this.preferences = preferences;
this.btcNodes = btcNodes; this.btcNodes = btcNodes;
this.filterManager = filterManager; this.filterManager = filterManager;
@ -291,10 +294,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
reSyncSPVChainButton.setOnAction(event -> GUIUtil.reSyncSPVChain(preferences)); reSyncSPVChainButton.setOnAction(event -> GUIUtil.reSyncSPVChain(preferences));
moneroPeersSubscription = EasyBind.subscribe(walletsSetup.peerConnectionsProperty(), moneroPeersSubscription = EasyBind.subscribe(connectionManager.peerConnectionsProperty(),
this::updateMoneroPeersTable); this::updateMoneroPeersTable);
moneroBlockHeightSubscription = EasyBind.subscribe(walletsSetup.chainHeightProperty(), moneroBlockHeightSubscription = EasyBind.subscribe(connectionManager.chainHeightProperty(),
this::updateChainHeightTextField); this::updateChainHeightTextField);
nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(), nodeAddressSubscription = EasyBind.subscribe(p2PService.getNetworkNode().nodeAddressProperty(),

View File

@ -30,29 +30,24 @@ import bisq.desktop.main.overlays.popups.Popup;
import bisq.core.account.witness.AccountAgeWitness; import bisq.core.account.witness.AccountAgeWitness;
import bisq.core.account.witness.AccountAgeWitnessService; import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup; import bisq.core.app.HavenoSetup;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Country; import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil; import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil; import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency; import bisq.core.locale.TradeCurrency;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferRestrictions; import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountList; import bisq.core.payment.PaymentAccountList;
import bisq.core.payment.payload.PaymentMethod; import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.fee.FeeService; import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.MarketPrice;
import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.txproof.AssetTxProofResult; import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.user.DontShowAgainLookup; import bisq.core.user.DontShowAgainLookup;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
@ -63,7 +58,6 @@ import bisq.common.file.CorruptedStorageFileHandler;
import bisq.common.persistence.PersistenceManager; import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistableEnvelope; import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.proto.persistable.PersistenceProtoResolver; import bisq.common.proto.persistable.PersistenceProtoResolver;
import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3; import bisq.common.util.Tuple3;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
@ -72,7 +66,6 @@ import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.uri.BitcoinURI; import org.bitcoinj.uri.BitcoinURI;
import org.bitcoinj.utils.Fiat;
import com.googlecode.jcsv.CSVStrategy; import com.googlecode.jcsv.CSVStrategy;
import com.googlecode.jcsv.writer.CSVEntryConverter; import com.googlecode.jcsv.writer.CSVEntryConverter;
@ -757,17 +750,17 @@ public class GUIUtil {
return true; return true;
} }
public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, WalletsSetup walletsSetup) { public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) { if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
return false; return false;
} }
if (!walletsSetup.hasSufficientPeersForBroadcast()) { if (!connectionService.hasSufficientPeersForBroadcast()) {
new Popup().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", walletsSetup.getMinBroadcastConnections())).show(); new Popup().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", connectionService.getMinBroadcastConnections())).show();
return false; return false;
} }
if (!walletsSetup.isDownloadComplete()) { if (!connectionService.isDownloadComplete()) {
new Popup().information(Res.get("popup.warning.downloadNotComplete")).show(); new Popup().information(Res.get("popup.warning.downloadNotComplete")).show();
return false; return false;
} }
@ -775,8 +768,8 @@ public class GUIUtil {
return true; return true;
} }
public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(WalletsSetup walletsSetup) { public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(CoreMoneroConnectionsService connectionService) {
if (!walletsSetup.isChainHeightSyncedWithinTolerance()) { if (!connectionService.isChainHeightSyncedWithinTolerance()) {
new Popup().information(Res.get("popup.warning.chainNotSynced")).show(); new Popup().information(Res.get("popup.warning.chainNotSynced")).show();
return false; return false;
} }

View File

@ -107,7 +107,6 @@ public class GuiceSetupTest {
assertSingleton(TradeLimits.class); assertSingleton(TradeLimits.class);
assertSingleton(KeyStorage.class); assertSingleton(KeyStorage.class);
assertSingleton(KeyRing.class); assertSingleton(KeyRing.class);
assertSingleton(PubKeyRing.class);
assertSingleton(User.class); assertSingleton(User.class);
assertSingleton(ClockWatcher.class); assertSingleton(ClockWatcher.class);
assertSingleton(Preferences.class); assertSingleton(Preferences.class);

View File

@ -20,7 +20,6 @@ package bisq.network.crypto;
import bisq.common.crypto.CryptoException; import bisq.common.crypto.CryptoException;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage; import bisq.common.crypto.KeyStorage;
import bisq.common.crypto.PubKeyRing;
import bisq.common.file.FileUtil; import bisq.common.file.FileUtil;
import bisq.common.proto.network.NetworkEnvelope; import bisq.common.proto.network.NetworkEnvelope;
@ -45,7 +44,6 @@ public class EncryptionServiceTests {
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
private PubKeyRing pubKeyRing;
private KeyRing keyRing; private KeyRing keyRing;
private File dir; private File dir;
@ -58,8 +56,7 @@ public class EncryptionServiceTests {
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
dir.mkdir(); dir.mkdir();
KeyStorage keyStorage = new KeyStorage(dir); KeyStorage keyStorage = new KeyStorage(dir);
keyRing = new KeyRing(keyStorage); keyRing = new KeyRing(keyStorage, null, true);
pubKeyRing = keyRing.getPubKeyRing();
} }
@After @After

View File

@ -61,7 +61,7 @@ public class AddDataMessageTest {
dir1.delete(); dir1.delete();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
dir1.mkdir(); dir1.mkdir();
keyRing1 = new KeyRing(new KeyStorage(dir1)); keyRing1 = new KeyRing(new KeyStorage(dir1), null, true);
} }
@Test @Test

View File

@ -24,20 +24,134 @@ option java_package = "bisq.proto.grpc";
option java_multiple_files = true; option java_multiple_files = true;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// DisputeAgents // Help
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
service DisputeAgents { service Help {
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) { rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) {
} }
} }
message RegisterDisputeAgentRequest { message GetMethodHelpRequest {
string dispute_agent_type = 1; string method_name = 1;
string registration_key = 2;
} }
message RegisterDisputeAgentReply { message GetMethodHelpReply {
string method_help = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Version
///////////////////////////////////////////////////////////////////////////////////////////
service GetVersion {
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
}
}
message GetVersionRequest {
}
message GetVersionReply {
string version = 1;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Account
///////////////////////////////////////////////////////////////////////////////////////////
service Account {
rpc AccountExists (AccountExistsRequest) returns (AccountExistsReply) {
}
rpc IsAccountOpen (IsAccountOpenRequest) returns (IsAccountOpenReply) {
}
rpc CreateAccount (CreateAccountRequest) returns (CreateAccountReply) {
}
rpc OpenAccount (OpenAccountRequest) returns (OpenAccountReply) {
}
rpc IsAppInitialized (IsAppInitializedRequest) returns (IsAppInitializedReply) {
}
rpc ChangePassword (ChangePasswordRequest) returns (ChangePasswordReply) {
}
rpc CloseAccount (CloseAccountRequest) returns (CloseAccountReply) {
}
rpc DeleteAccount (DeleteAccountRequest) returns (DeleteAccountReply) {
}
rpc BackupAccount (BackupAccountRequest) returns (stream BackupAccountReply) {
}
rpc RestoreAccount (RestoreAccountRequest) returns (RestoreAccountReply) {
}
}
message AccountExistsRequest {
}
message AccountExistsReply {
bool account_exists = 1;
}
message IsAccountOpenRequest {
}
message IsAccountOpenReply {
bool is_account_open = 1;
}
message CreateAccountRequest {
string password = 1;
}
message CreateAccountReply {
}
message OpenAccountRequest {
string password = 1;
}
message OpenAccountReply {
}
message IsAppInitializedRequest {
}
message IsAppInitializedReply {
bool is_app_initialized = 1;
}
message ChangePasswordRequest {
string password = 1;
}
message ChangePasswordReply {
}
message CloseAccountRequest {
}
message CloseAccountReply {
}
message DeleteAccountRequest {
}
message DeleteAccountReply {
}
message BackupAccountRequest {
}
message BackupAccountReply {
bytes zip_bytes = 1;
}
message RestoreAccountRequest {
bytes zip_bytes = 1;
uint64 offset = 2;
uint64 total_length = 3;
bool has_more = 4;
}
message RestoreAccountReply {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -56,9 +170,10 @@ message RegisterNotificationListenerRequest {
message NotificationMessage { message NotificationMessage {
enum NotificationType { enum NotificationType {
TRADE_UPDATE = 0; APP_INITIALIZED = 0;
CHAT_MESSAGE = 1; KEEP_ALIVE = 1;
KEEP_ALIVE = 2; TRADE_UPDATE = 2;
CHAT_MESSAGE = 3;
} }
string id = 1; string id = 1;
@ -78,20 +193,134 @@ message SendNotificationReply {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Help // MoneroConnections
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
service Help { service MoneroConnections {
rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) { rpc AddConnection (AddConnectionRequest) returns (AddConnectionReply) {
}
rpc RemoveConnection(RemoveConnectionRequest) returns (RemoveConnectionReply) {
}
rpc GetConnection(GetConnectionRequest) returns (GetConnectionReply) {
}
rpc GetConnections(GetConnectionsRequest) returns (GetConnectionsReply) {
}
rpc SetConnection(SetConnectionRequest) returns (SetConnectionReply) {
}
rpc CheckConnection(CheckConnectionRequest) returns (CheckConnectionReply) {
}
rpc CheckConnections(CheckConnectionsRequest) returns (CheckConnectionsReply) {
}
rpc StartCheckingConnections(StartCheckingConnectionsRequest) returns (StartCheckingConnectionsReply) {
}
rpc StopCheckingConnections(StopCheckingConnectionsRequest) returns (StopCheckingConnectionsReply) {
}
rpc GetBestAvailableConnection(GetBestAvailableConnectionRequest) returns (GetBestAvailableConnectionReply) {
}
rpc SetAutoSwitch(SetAutoSwitchRequest) returns (SetAutoSwitchReply) {
} }
} }
message GetMethodHelpRequest { message UrlConnection {
string method_name = 1; enum OnlineStatus {
UNKNOWN = 0;
ONLINE = 1;
OFFLINE = 2;
}
enum AuthenticationStatus {
NO_AUTHENTICATION = 0;
AUTHENTICATED = 1;
NOT_AUTHENTICATED = 2;
}
string url = 1;
string username = 2; // request only
string password = 3; // request only
int32 priority = 4;
OnlineStatus online_status = 5; // reply only
AuthenticationStatus authentication_status = 6; // reply only
} }
message GetMethodHelpReply { message AddConnectionRequest {
string method_help = 1; UrlConnection connection = 1;
}
message AddConnectionReply {}
message RemoveConnectionRequest {
string url = 1;
}
message RemoveConnectionReply {}
message GetConnectionRequest {}
message GetConnectionReply {
UrlConnection connection = 1;
}
message GetConnectionsRequest {}
message GetConnectionsReply {
repeated UrlConnection connections = 1;
}
message SetConnectionRequest {
string url = 1;
UrlConnection connection = 2;
}
message SetConnectionReply {}
message CheckConnectionRequest {}
message CheckConnectionReply {
UrlConnection connection = 1;
}
message CheckConnectionsRequest {}
message CheckConnectionsReply {
repeated UrlConnection connections = 1;
}
message StartCheckingConnectionsRequest {
int32 refresh_period = 1; // milliseconds
}
message StartCheckingConnectionsReply {}
message StopCheckingConnectionsRequest {}
message StopCheckingConnectionsReply {}
message GetBestAvailableConnectionRequest {}
message GetBestAvailableConnectionReply {
UrlConnection connection = 1;
}
message SetAutoSwitchRequest {
bool auto_switch = 1;
}
message SetAutoSwitchReply {}
///////////////////////////////////////////////////////////////////////////////////////////
// DisputeAgents
///////////////////////////////////////////////////////////////////////////////////////////
service DisputeAgents {
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) {
}
}
message RegisterDisputeAgentRequest {
string dispute_agent_type = 1;
string registration_key = 2;
}
message RegisterDisputeAgentReply {
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -704,133 +933,3 @@ message AddressBalanceInfo {
int64 num_confirmations = 3; int64 num_confirmations = 3;
bool is_address_unused = 4; bool is_address_unused = 4;
} }
///////////////////////////////////////////////////////////////////////////////////////////
// MoneroConnections
///////////////////////////////////////////////////////////////////////////////////////////
service MoneroConnections {
rpc AddConnection (AddConnectionRequest) returns (AddConnectionReply) {
}
rpc RemoveConnection(RemoveConnectionRequest) returns (RemoveConnectionReply) {
}
rpc GetConnection(GetConnectionRequest) returns (GetConnectionReply) {
}
rpc GetConnections(GetConnectionsRequest) returns (GetConnectionsReply) {
}
rpc SetConnection(SetConnectionRequest) returns (SetConnectionReply) {
}
rpc CheckConnection(CheckConnectionRequest) returns (CheckConnectionReply) {
}
rpc CheckConnections(CheckConnectionsRequest) returns (CheckConnectionsReply) {
}
rpc StartCheckingConnections(StartCheckingConnectionsRequest) returns (StartCheckingConnectionsReply) {
}
rpc StopCheckingConnections(StopCheckingConnectionsRequest) returns (StopCheckingConnectionsReply) {
}
rpc GetBestAvailableConnection(GetBestAvailableConnectionRequest) returns (GetBestAvailableConnectionReply) {
}
rpc SetAutoSwitch(SetAutoSwitchRequest) returns (SetAutoSwitchReply) {
}
}
message UriConnection {
enum OnlineStatus {
UNKNOWN = 0;
ONLINE = 1;
OFFLINE = 2;
}
enum AuthenticationStatus {
NO_AUTHENTICATION = 0;
AUTHENTICATED = 1;
NOT_AUTHENTICATED = 2;
}
string uri = 1;
string username = 2; // request only
string password = 3; // request only
int32 priority = 4;
OnlineStatus online_status = 5; // reply only
AuthenticationStatus authentication_status = 6; // reply only
}
message AddConnectionRequest {
UriConnection connection = 1;
}
message AddConnectionReply {}
message RemoveConnectionRequest {
string uri = 1;
}
message RemoveConnectionReply {}
message GetConnectionRequest {}
message GetConnectionReply {
UriConnection connection = 1;
}
message GetConnectionsRequest {}
message GetConnectionsReply {
repeated UriConnection connections = 1;
}
message SetConnectionRequest {
string uri = 1;
UriConnection connection = 2;
}
message SetConnectionReply {}
message CheckConnectionRequest {}
message CheckConnectionReply {
UriConnection connection = 1;
}
message CheckConnectionsRequest {}
message CheckConnectionsReply {
repeated UriConnection connections = 1;
}
message StartCheckingConnectionsRequest {
int32 refresh_period = 1; // milliseconds
}
message StartCheckingConnectionsReply {}
message StopCheckingConnectionsRequest {}
message StopCheckingConnectionsReply {}
message GetBestAvailableConnectionRequest {}
message GetBestAvailableConnectionReply {
UriConnection connection = 1;
}
message SetAutoSwitchRequest {
bool auto_switch = 1;
}
message SetAutoSwitchReply {}
///////////////////////////////////////////////////////////////////////////////////////////
// Version
///////////////////////////////////////////////////////////////////////////////////////////
service GetVersion {
rpc GetVersion (GetVersionRequest) returns (GetVersionReply) {
}
}
message GetVersionRequest {
}
message GetVersionReply {
string version = 1;
}

View File

@ -1696,7 +1696,7 @@ message TradingPeer {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
message EncryptedConnection { message EncryptedConnection {
string uri = 1; string url = 1;
string username = 2; string username = 2;
bytes encrypted_password = 3; bytes encrypted_password = 3;
bytes encryption_salt = 4; bytes encryption_salt = 4;
@ -1706,7 +1706,7 @@ message EncryptedConnection {
message EncryptedConnectionList { message EncryptedConnectionList {
bytes salt = 1; bytes salt = 1;
repeated EncryptedConnection items = 2; repeated EncryptedConnection items = 2;
string current_connection_uri = 3; string current_connection_url = 3;
int64 refresh_period = 4; // negative: no automated refresh is activated, zero: automated refresh with default period, positive: automated refresh with configured period (value) int64 refresh_period = 4; // negative: no automated refresh is activated, zero: automated refresh with default period, positive: automated refresh with configured period (value)
bool auto_switch = 5; bool auto_switch = 5;
} }