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 \
--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 and mediator need to be registerd in the UI after launching it.
./haveno-desktop \
@ -73,6 +62,18 @@ arbitrator-desktop2:
--apiPassword=apitest \
--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:
./haveno-desktop \
--baseCurrencyNetwork=XMR_STAGENET \
@ -93,7 +94,8 @@ alice-daemon:
--appName=haveno-XMR_STAGENET_Alice \
--apiPassword=apitest \
--apiPort=9999 \
--walletRpcBindPort=38091
--walletRpcBindPort=38091 \
--passwordRequired=false
bob-desktop:
./haveno-desktop \
@ -115,7 +117,8 @@ bob-daemon:
--appName=haveno-XMR_STAGENET_Bob \
--apiPassword=apitest \
--apiPort=10000 \
--walletRpcBindPort=38092
--walletRpcBindPort=38092 \
--passwordRequired=false
monero-shared:
./.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_FEES_TS = "bitcoinFeesTs";
public static final String BYPASS_MEMPOOL_VALIDATION = "bypassMempoolValidation";
public static final String PASSWORD_REQUIRED = "passwordRequired";
// Default values for certain options
public static final int UNSPECIFIED_PORT = -1;
@ -190,6 +191,7 @@ public class Config {
public final boolean preventPeriodicShutdownAtSeedNode;
public final boolean republishMailboxEntries;
public final boolean bypassMempoolValidation;
public final boolean passwordRequired;
// Properties derived from options but not exposed as options themselves
public final File torDir;
@ -579,6 +581,13 @@ public class Config {
.ofType(boolean.class)
.defaultsTo(false);
ArgumentAcceptingOptionSpec<Boolean> passwordRequiredOpt =
parser.accepts(PASSWORD_REQUIRED,
"Requires a password for creating a Haveno account")
.withRequiredArg()
.ofType(boolean.class)
.defaultsTo(false);
try {
CompositeOptionSet options = new CompositeOptionSet();
@ -686,6 +695,7 @@ public class Config {
this.preventPeriodicShutdownAtSeedNode = options.valueOf(preventPeriodicShutdownAtSeedNodeOpt);
this.republishMailboxEntries = options.valueOf(republishMailboxEntriesOpt);
this.bypassMempoolValidation = options.valueOf(bypassMempoolValidationOpt);
this.passwordRequired = options.valueOf(passwordRequiredOpt);
} catch (OptionException ex) {
throw new ConfigException("problem parsing option '%s': %s",
ex.options().get(0),

View File

@ -54,11 +54,13 @@ public class Encryption {
public static final String ASYM_KEY_ALGO = "RSA";
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 HMAC = "HmacSHA256";
public static final String HMAC_ERROR_MSG = "Hmac does not match.";
public static KeyPair generateKeyPair() {
long ts = System.currentTimeMillis();
try {
@ -101,11 +103,6 @@ public class Encryption {
return new SecretKeySpec(secretKeyBytes, 0, secretKeyBytes.length, SYM_KEY_ALGO);
}
public static byte[] getSecretKeyBytes(SecretKey secretKey) {
return secretKey.getEncoded();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Hmac
///////////////////////////////////////////////////////////////////////////////////////////
@ -179,7 +176,7 @@ public class Encryption {
if (verifyHmac(Hex.decode(payloadAsHex), Hex.decode(hmacAsHex), secretKey)) {
return Hex.decode(payloadAsHex);
} 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.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Getter
@EqualsAndHashCode
@Slf4j
@Singleton
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
public KeyRing(KeyStorage keyStorage) {
if (keyStorage.allKeyFilesExist()) {
signatureKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_SIGNATURE);
encryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_ENCRYPTION);
} else {
// First time we create key pairs
signatureKeyPair = Sig.generateKeyPair();
encryptionKeyPair = Encryption.generateKeyPair();
keyStorage.saveKeyRing(this);
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()) {
signatureKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_SIGNATURE, password);
encryptionKeyPair = keyStorage.loadKeyPair(KeyStorage.KeyEntry.MSG_ENCRYPTION, password);
if (signatureKeyPair != null && encryptionKeyPair != null) pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic());
} 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();
encryptionKeyPair = Encryption.generateKeyPair();
pubKeyRing = new PubKeyRing(signatureKeyPair.getPublic(), encryptionKeyPair.getPublic());
keyStorage.saveKeyRing(this, password);
}
// 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.file.FileUtil;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import com.google.inject.Inject;
import javax.inject.Named;
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.KeyPair;
import java.security.NoSuchAlgorithmException;
@ -39,6 +47,9 @@ import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -46,6 +57,7 @@ import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;
import org.slf4j.Logger;
@ -58,6 +70,11 @@ import static bisq.common.util.Preconditions.checkDir;
@Singleton
public class KeyStorage {
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 {
MSG_SIGNATURE("sig", Sig.KEY_ALGO),
@ -104,7 +121,7 @@ public class KeyStorage {
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);
// long now = System.currentTimeMillis();
try {
@ -118,9 +135,53 @@ public class KeyStorage {
//noinspection ResultOfMethodCallIgnored
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);
privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (InvalidKeySpecException | IOException e) {
} catch (InvalidKeySpecException | IOException | CryptoException e) {
log.error("Could not load key " + keyEntry.toString(), e.getMessage());
throw new RuntimeException("Could not load key " + keyEntry.toString(), e);
}
@ -150,20 +211,44 @@ public class KeyStorage {
}
}
public void saveKeyRing(KeyRing keyRing) {
savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName());
savePrivateKey(keyRing.getEncryptionKeyPair().getPrivate(), KeyEntry.MSG_ENCRYPTION.getFileName());
public void saveKeyRing(KeyRing keyRing, String password) {
savePrivateKey(keyRing.getSignatureKeyPair().getPrivate(), KeyEntry.MSG_SIGNATURE.getFileName(), password);
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())
//noinspection ResultOfMethodCallIgnored
storageDir.mkdir();
storageDir.mkdirs();
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
try (FileOutputStream fos = new FileOutputStream(storageDir + "/" + name + ".key")) {
fos.write(pkcs8EncodedKeySpec.getEncoded());
} catch (IOException e) {
byte[] keyBytes = pkcs8EncodedKeySpec.getEncoded();
// 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);
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.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> {
private final PubKeyRing pubKeyRing;
private final KeyRing keyRing;
@Inject
public PubKeyRingProvider(KeyRing keyRing) {
pubKeyRing = keyRing.getPubKeyRing();
this.keyRing = keyRing;
}
@Override
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/>.
*/
package bisq.core.crypto;
package bisq.common.crypto;
import bisq.common.UserThread;
import bisq.common.util.Utilities;
@ -51,15 +51,26 @@ public class ScryptUtil {
.build();
return new KeyCrypterScrypt(scryptParameters);
}
public static KeyParameter deriveKeyWithScrypt(KeyCrypterScrypt keyCrypterScrypt, String password) {
try {
log.debug("Doing key derivation");
long start = System.currentTimeMillis();
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
long duration = System.currentTimeMillis() - start;
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 {
log.debug("Doing key derivation");
long start = System.currentTimeMillis();
KeyParameter aesKey = keyCrypterScrypt.deriveKey(password);
long duration = System.currentTimeMillis() - start;
log.debug("Key derivation took {} msec", duration);
KeyParameter aesKey = deriveKeyWithScrypt(keyCrypterScrypt, password);
UserThread.execute(() -> {
try {
resultHandler.handleResult(aesKey);

View File

@ -114,7 +114,6 @@ public class PersistenceManager<T extends PersistableEnvelope> {
return;
}
// We don't know from which thread we are called so we map to user thread
UserThread.execute(() -> {
if (doShutdown) {
@ -382,6 +381,11 @@ public class PersistenceManager<T extends PersistableEnvelope> {
return;
}
if (!initCalled.get()) {
log.warn("requestPersistence() called before init. Ignoring request");
return;
}
persistenceRequested = true;
// 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;
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.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
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
@Deprecated
@Slf4j
public class CoreAccountService {
private static final String DEFAULT_PASSWORD = "abctesting123";
private String password = DEFAULT_PASSWORD;
private final List<PasswordChangeListener> listeners = new CopyOnWriteArrayList<>();
public String getPassword() {
return password;
private final Config config;
private final KeyStorage keyStorage;
private final KeyRing keyRing;
@Getter
private String password;
private List<AccountServiceListener> listeners = new ArrayList<AccountServiceListener>();
@Inject
public CoreAccountService(Config config,
KeyStorage keyStorage,
KeyRing keyRing) {
this.config = config;
this.keyStorage = keyStorage;
this.keyRing = keyRing;
}
public void setPassword(String newPassword) {
String oldPassword = password;
password = newPassword;
notifyListenerAboutPasswordChange(oldPassword, newPassword);
}
public void addPasswordChangeListener(PasswordChangeListener listener) {
Objects.requireNonNull(listener, "listener");
public void addListener(AccountServiceListener listener) {
listeners.add(listener);
}
private void notifyListenerAboutPasswordChange(String oldPassword, String newPassword) {
for (PasswordChangeListener listener : listeners) {
listener.onPasswordChange(oldPassword, newPassword);
public boolean removeListener(AccountServiceListener listener) {
return listeners.remove(listener);
}
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 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);
}
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");
public interface PasswordChangeListener {
void onPasswordChange(String oldPassword, String newPassword);
// 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.MarketPriceInfo;
import bisq.core.api.model.TxFeeRateInfo;
import bisq.core.app.AppStartupState;
import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
@ -33,6 +34,7 @@ import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.app.Version;
import bisq.common.config.Config;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
@ -46,6 +48,8 @@ import javax.inject.Singleton;
import com.google.common.util.concurrent.FutureCallback;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@ -58,7 +62,6 @@ import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroRpcConnection;
import monero.wallet.model.MoneroDestination;
import monero.wallet.model.MoneroTxWallet;
@ -73,6 +76,8 @@ public class CoreApi {
@Getter
private final Config config;
private final AppStartupState appStartupState;
private final CoreAccountService coreAccountService;
private final CoreDisputeAgentsService coreDisputeAgentsService;
private final CoreHelpService coreHelpService;
private final CoreOffersService coreOffersService;
@ -86,6 +91,8 @@ public class CoreApi {
@Inject
public CoreApi(Config config,
AppStartupState appStartupState,
CoreAccountService coreAccountService,
CoreDisputeAgentsService coreDisputeAgentsService,
CoreHelpService coreHelpService,
CoreOffersService coreOffersService,
@ -97,6 +104,8 @@ public class CoreApi {
CoreNotificationService notificationService,
CoreMoneroConnectionsService coreMoneroConnectionsService) {
this.config = config;
this.appStartupState = appStartupState;
this.coreAccountService = coreAccountService;
this.coreDisputeAgentsService = coreDisputeAgentsService;
this.coreHelpService = coreHelpService;
this.coreOffersService = coreOffersService;
@ -115,11 +124,197 @@ public class CoreApi {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Dispute Agents
// Help
///////////////////////////////////////////////////////////////////////////////////////////
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
public String getMethodHelp(String methodName) {
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) {
return coreHelpService.getMethodHelp(methodName);
public void registerDisputeAgent(String disputeAgentType, String registrationKey) {
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -312,146 +507,4 @@ public class CoreApi {
public String getTradeRole(String 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;
import bisq.common.UserThread;
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.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.Singleton;
import lombok.extern.slf4j.Slf4j;
import monero.common.MoneroConnectionManager;
import monero.common.MoneroConnectionManagerListener;
import monero.common.MoneroRpcConnection;
import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc;
import monero.daemon.model.MoneroPeer;
@Slf4j
@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 long DEFAULT_REFRESH_PERIOD = 15_000L; // check the connection every 15 seconds per default
private static final int MIN_BROADCAST_CONNECTIONS = 0; // TODO: 0 for stagenet, 5+ for mainnet
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
private static final List<MoneroRpcConnection> DEFAULT_CONNECTIONS = Arrays.asList(
@ -24,21 +41,222 @@ public class CoreMoneroConnectionsService {
);
private final Object lock = new Object();
private final CoreAccountService accountService;
private final MoneroConnectionManager connectionManager;
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
public CoreMoneroConnectionsService(MoneroConnectionManager connectionManager,
public CoreMoneroConnectionsService(WalletsSetup walletsSetup,
CoreAccountService accountService,
MoneroConnectionManager connectionManager,
EncryptedConnectionList connectionList) {
this.accountService = accountService;
this.connectionManager = connectionManager;
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);
}
}
@Override
public void onPasswordChanged(String oldPassword, String newPassword) {
log.info(getClass() + ".onPasswordChanged({}, {}) called", oldPassword, newPassword);
connectionList.changePassword(oldPassword, newPassword);
}
});
});
}
public void initialize() {
// ------------------------ CONNECTION MANAGEMENT -------------------------
public MoneroDaemon getDaemon() {
accountService.checkAccountOpen();
return this.daemon;
}
public void addListener(MoneroConnectionManagerListener listener) {
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
connectionList.getConnections().forEach(connectionManager::addConnection);
log.info("Read " + connectionList.getConnections().size() + " connections from disk");
// add default connections
for (MoneroRpcConnection connection : DEFAULT_CONNECTIONS) {
@ -50,24 +268,38 @@ public class CoreMoneroConnectionsService {
connectionList.getCurrentConnectionUri().ifPresentOrElse(connectionManager::setConnection, () -> {
connectionManager.setConnection(DEFAULT_CONNECTIONS.get(0).getUri()); // default to localhost
});
// initialize daemon
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
updateDaemonInfo();
// restore configuration
connectionManager.setAutoSwitch(connectionList.getAutoSwitch());
long refreshPeriod = connectionList.getRefreshPeriod();
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();
// register connection change listener
connectionManager.addListener(this::onConnectionChanged);
// run once
if (!isInitialized) {
// register connection change listener
connectionManager.addListener(this::onConnectionChanged);
// poll daemon periodically
startPollingDaemon();
isInitialized = true;
}
}
}
private void onConnectionChanged(MoneroRpcConnection currentConnection) {
synchronized (lock) {
if (currentConnection == null) {
daemon = null;
connectionList.setCurrentConnectionUri(null);
} else {
daemon = new MoneroDaemonRpc(connectionManager.getConnection());
connectionList.removeConnection(currentConnection.getUri());
connectionList.addConnection(currentConnection);
connectionList.setCurrentConnectionUri(currentConnection.getUri());
@ -75,88 +307,26 @@ public class CoreMoneroConnectionsService {
}
}
public void addConnectionListener(MoneroConnectionManagerListener listener) {
synchronized (lock) {
connectionManager.addListener(listener);
private void startPollingDaemon() {
UserThread.runPeriodically(() -> {
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) {
synchronized (lock) {
connectionList.addConnection(connection);
connectionManager.addConnection(connection);
}
}
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);
}
private List<MoneroPeer> getOnlinePeers() {
return daemon.getPeers().stream()
.filter(peer -> peer.isOnline())
.collect(Collectors.toList());
}
}

View File

@ -40,7 +40,14 @@ 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) {
sendNotification(NotificationMessage.newBuilder()
.setType(NotificationType.TRADE_UPDATE)

View File

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

View File

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

View File

@ -17,22 +17,18 @@
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.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.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javax.inject.Inject;
import javax.inject.Singleton;
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.
@ -53,7 +49,9 @@ public class AppStartupState {
private final BooleanProperty hasSufficientPeersForBroadcast = new SimpleBooleanProperty();
@Inject
public AppStartupState(WalletsSetup walletsSetup, P2PService p2PService) {
public AppStartupState(CoreNotificationService notificationService,
CoreMoneroConnectionsService connectionsService,
P2PService p2PService) {
p2PService.addP2PServiceListener(new BootstrapListener() {
@Override
@ -62,13 +60,13 @@ public class AppStartupState {
}
});
walletsSetup.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
if (walletsSetup.isDownloadComplete())
connectionsService.downloadPercentageProperty().addListener((observable, oldValue, newValue) -> {
if (connectionsService.isDownloadComplete())
isBlockDownloadComplete.set(true);
});
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
if (walletsSetup.hasSufficientPeersForBroadcast())
connectionsService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
if (connectionsService.hasSufficientPeersForBroadcast())
hasSufficientPeersForBroadcast.set(true);
});
@ -77,6 +75,7 @@ public class AppStartupState {
hasSufficientPeersForBroadcast,
allDomainServicesInitialized,
(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) {
walletAndNetworkReady.set(true);
}
@ -85,6 +84,7 @@ public class AppStartupState {
p2pNetworkAndWalletInitialized.subscribe((observable, oldValue, newValue) -> {
if (newValue) {
applicationFullyInitialized.set(true);
notificationService.sendAppInitializedNotification();
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.config.Config;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.proto.network.NetworkProtoResolver;
import bisq.common.proto.persistable.PersistenceProtoResolver;
@ -93,6 +91,5 @@ public class CoreModule extends AppModule {
install(new FilterModule(config));
install(new CorePresentationModule(config));
install(new MoneroConnectionModule(config));
bind(PubKeyRing.class).toProvider(PubKeyRingProvider.class);
}
}

View File

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

View File

@ -17,6 +17,8 @@
package bisq.core.app;
import bisq.core.api.AccountServiceListener;
import bisq.core.api.CoreAccountService;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
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.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
@ -35,6 +36,7 @@ import bisq.common.app.AppModule;
import bisq.common.config.HavenoHelpFormatter;
import bisq.common.config.Config;
import bisq.common.config.ConfigException;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.handlers.ResultHandler;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistedDataHost;
@ -45,7 +47,6 @@ import bisq.common.util.Utilities;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@ -64,11 +65,12 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
private final String appName;
private final String version;
protected CoreAccountService accountService;
protected Injector injector;
protected AppModule module;
protected Config config;
private boolean isShutdownInProgress;
private boolean hasDowngraded;
private boolean isReadOnly;
public HavenoExecutable(String fullName, String scriptName, String appName, String version) {
this.fullName = fullName;
@ -136,17 +138,61 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
setupGuice();
setupAvoidStandbyMode();
hasDowngraded = HavenoSetup.hasDowngraded();
if (hasDowngraded) {
// 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
// in the process and require a shut down.
startApplication();
} else {
// 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
// in the process and require a shut down.
isReadOnly = HavenoSetup.hasDowngraded();
// 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);
} 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
///////////////////////////////////////////////////////////////////////////////////////////
@ -198,9 +244,9 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
}
protected void runHavenoSetup() {
HavenoSetup bisqSetup = injector.getInstance(HavenoSetup.class);
bisqSetup.addHavenoSetupListener(this);
bisqSetup.start();
HavenoSetup havenoSetup = injector.getInstance(HavenoSetup.class);
havenoSetup.addHavenoSetupListener(this);
havenoSetup.start();
}
@Override
@ -233,7 +279,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
injector.getInstance(TradeStatisticsManager.class).shutDown();
injector.getInstance(XmrTxProofService.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");
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
log.info("OpenOfferManager shutdown completed");
@ -248,16 +294,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
injector.getInstance(P2PService.class).shutDown(() -> {
log.info("P2PService shutdown completed");
module.close(injector);
if (!hasDowngraded) {
// 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);
}
completeShutdown(resultHandler, EXIT_SUCCESS);
});
});
walletsSetup.shutDown();
@ -267,31 +304,26 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
// Wait max 20 sec.
UserThread.runAfter(() -> {
log.warn("Graceful shut down not completed in 20 sec. We trigger our timeout handler.");
if (!hasDowngraded) {
// 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);
}
completeShutdown(resultHandler, EXIT_SUCCESS);
}, 20);
} catch (Throwable t) {
log.error("App shutdown failed with exception {}", t.toString());
t.printStackTrace();
if (!hasDowngraded) {
// If user tried to downgrade we do not write the persistable data to avoid data corruption
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
log.info("Graceful shutdown resulted in an error. Exiting now.");
resultHandler.handleResult();
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
});
} else {
UserThread.runAfter(() -> System.exit(EXIT_FAILURE), 1);
}
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
PersistenceManager.flushAllDataToDiskAtShutdown(() -> {
log.info("Graceful shutdown flushed persistence. Exiting now.");
resultHandler.handleResult();
UserThread.runAfter(() -> System.exit(exitCode), 1);
});
} else {
resultHandler.handleResult();
UserThread.runAfter(() -> System.exit(exitCode), 1);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import bisq.core.trade.TradeModule;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.xmr.connection.MoneroConnectionModule;
import bisq.network.crypto.EncryptionServiceModule;
import bisq.network.p2p.P2PModule;
import bisq.network.p2p.network.BridgeAddressProvider;
@ -41,8 +41,6 @@ import bisq.common.app.AppModule;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
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.persistable.PersistenceProtoResolver;
@ -93,6 +91,6 @@ public class ModuleForAppWithP2p extends AppModule {
install(new BitcoinModule(config));
install(new AlertModule(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.Encryption;
import bisq.common.crypto.ScryptUtil;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.proto.persistable.PersistedDataHost;
import bisq.core.api.CoreAccountService;
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.security.SecureRandom;
import java.util.HashMap;
@ -22,8 +24,6 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import javax.crypto.SecretKey;
import javax.inject.Inject;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import lombok.NonNull;
import monero.common.MoneroRpcConnection;
import org.bitcoinj.crypto.KeyCrypterScrypt;
@ -60,7 +60,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
transient private PersistenceManager<EncryptedConnectionList> persistenceManager;
private final Map<String, EncryptedConnection> items = new HashMap<>();
private @NonNull String currentConnectionUri = "";
private @NonNull String currentConnectionUrl = "";
private long refreshPeriod;
private boolean autoSwitch;
@ -70,17 +70,16 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
this.accountService = accountService;
this.persistenceManager = persistenceManager;
this.persistenceManager.initialize(this, "EncryptedConnectionList", PersistenceManager.Source.PRIVATE);
this.accountService.addPasswordChangeListener(this::onPasswordChange);
}
private EncryptedConnectionList(byte[] salt,
List<EncryptedConnection> items,
@NonNull String currentConnectionUri,
@NonNull String currentConnectionUrl,
long refreshPeriod,
boolean autoSwitch) {
this.keyCrypterScrypt = ScryptUtil.getKeyCrypterScrypt(salt);
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUri, Function.identity())));
this.currentConnectionUri = currentConnectionUri;
this.items.putAll(items.stream().collect(Collectors.toMap(EncryptedConnection::getUrl, Function.identity())));
this.currentConnectionUrl = currentConnectionUrl;
this.refreshPeriod = refreshPeriod;
this.autoSwitch = autoSwitch;
}
@ -93,9 +92,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
initializeEncryption(persistedEncryptedConnectionList.keyCrypterScrypt);
items.clear();
items.putAll(persistedEncryptedConnectionList.items);
currentConnectionUri = persistedEncryptedConnectionList.currentConnectionUri;
currentConnectionUrl = persistedEncryptedConnectionList.currentConnectionUrl;
refreshPeriod = persistedEncryptedConnectionList.refreshPeriod;
autoSwitch = persistedEncryptedConnectionList.autoSwitch;
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
@ -104,6 +105,8 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
writeLock.lock();
try {
initializeEncryption(ScryptUtil.getKeyCrypterScrypt());
} catch (Exception e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
@ -203,11 +206,11 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
}
}
public void setCurrentConnectionUri(String currentConnectionUri) {
public void setCurrentConnectionUri(String currentConnectionUrl) {
boolean changed;
writeLock.lock();
try {
changed = !this.currentConnectionUri.equals(this.currentConnectionUri = currentConnectionUri == null ? "" : currentConnectionUri);
changed = !this.currentConnectionUrl.equals(this.currentConnectionUrl = currentConnectionUrl == null ? "" : currentConnectionUrl);
} finally {
writeLock.unlock();
}
@ -219,17 +222,54 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
public Optional<String> getCurrentConnectionUri() {
readLock.lock();
try {
return Optional.of(currentConnectionUri).filter(s -> !s.isEmpty());
return Optional.of(currentConnectionUrl).filter(s -> !s.isEmpty());
} finally {
readLock.unlock();
}
}
private void requestPersistence() {
public void requestPersistence() {
persistenceManager.requestPersistence();
}
@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();
}
private void onPasswordChange(String oldPassword, String newPassword) {
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();
try {
SecretKey oldSecret = encryptionKey;
@ -243,9 +283,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
}
private SecretKey toSecretKey(String password) {
if (password == null) {
return null;
}
if (password == null) return null;
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) {
if (secret == null) return encrypted; // no encryption
try {
return Encryption.decrypt(encrypted, secret);
} catch (CryptoException e) {
@ -273,6 +312,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
}
private static byte[] encrypt(byte[] unencrypted, SecretKey secretKey) {
if (secretKey == null) return unencrypted; // no encryption
try {
return Encryption.encrypt(unencrypted, secretKey);
} catch (CryptoException e) {
@ -286,7 +326,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
byte[] passwordSalt = generateSalt(passwordBytes);
byte[] encryptedPassword = encryptPassword(passwordBytes, passwordSalt);
return EncryptedConnection.builder()
.uri(connection.getUri())
.url(connection.getUri())
.username(connection.getUsername() == null ? "" : connection.getUsername())
.encryptedPassword(encryptedPassword)
.encryptionSalt(passwordSalt)
@ -298,7 +338,7 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
byte[] decryptedPasswordBytes = decryptPassword(connection.getEncryptedPassword(), connection.getEncryptionSalt());
String password = decryptedPasswordBytes == null ? null : new String(decryptedPasswordBytes, StandardCharsets.UTF_8);
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());
return moneroRpcConnection;
}
@ -357,39 +397,4 @@ public class EncryptedConnectionList implements PersistableEnvelope, PersistedDa
}
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 java.math.BigInteger;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
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
* associated protobuf message.
@ -48,7 +42,6 @@ import monero.wallet.model.MoneroWalletListener;
@Slf4j
public final class XmrAddressEntryList implements PersistableEnvelope, PersistedDataHost {
transient private PersistenceManager<XmrAddressEntryList> persistenceManager;
transient private MoneroWallet wallet;
private final Set<XmrAddressEntry> entrySet = new CopyOnWriteArraySet<>();
@Inject
@ -100,61 +93,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
// 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() {
return ImmutableList.copyOf(entrySet);
}
@ -202,25 +140,6 @@ public final class XmrAddressEntryList implements PersistableEnvelope, Persisted
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
public String toString() {
return "XmrAddressEntryList{" +

View File

@ -8,14 +8,14 @@ import javafx.beans.property.SimpleDoubleProperty;
import java.util.Date;
class DownloadListener {
public class DownloadListener {
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));
}
protected void doneDownload() {
public void doneDownload() {
UserThread.execute(() -> this.percentage.set(1d));
}

View File

@ -17,7 +17,6 @@
package bisq.core.btc.setup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.nodes.ProxySocketFactory;
import bisq.core.btc.wallet.HavenoRiskAnalysis;
@ -72,9 +71,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
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.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
* 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);
// 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 String filePrefix;
protected final CoreMoneroConnectionsService moneroConnectionsManager;
protected volatile BlockChain vChain;
protected volatile SPVBlockStore vStore;
protected volatile MoneroDaemonRpc vXmrDaemon;
protected volatile MoneroWalletRpc vXmrWallet;
protected volatile Wallet vBtcWallet;
protected volatile PeerGroup vPeerGroup;
protected final int rpcBindPort;
protected final File directory;
protected volatile File vXmrWalletFile;
protected volatile File vBtcWalletFile;
@ -176,21 +149,17 @@ public class WalletConfig extends AbstractIdleService {
*/
public WalletConfig(NetworkParameters params,
File directory,
int rpcBindPort,
CoreMoneroConnectionsService connectionsManager,
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.
*/
private WalletConfig(Context context, File directory, int rpcBindPort, CoreMoneroConnectionsService connectionsManager, String filePrefix) {
private WalletConfig(Context context, File directory, String filePrefix) {
this.context = context;
this.params = checkNotNull(context.getParams());
this.directory = checkDir(directory);
this.rpcBindPort = rpcBindPort;
this.moneroConnectionsManager = connectionsManager;
this.filePrefix = checkNotNull(filePrefix);
}
@ -293,85 +262,6 @@ public class WalletConfig extends AbstractIdleService {
// 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
protected void startUp() throws Exception {
// Runs in a separate thread.
@ -380,31 +270,6 @@ public class WalletConfig extends AbstractIdleService {
File chainFile = new File(directory, filePrefix + ".spvchain");
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";
vBtcWalletFile = new File(directory, filePrefix + btcPrefix + ".wallet");
boolean shouldReplayWallet = (vBtcWalletFile.exists() && !chainFileExists) || restoreFromSeed != null;
@ -647,16 +512,6 @@ public class WalletConfig extends AbstractIdleService {
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() {
checkState(state() == State.STARTING || state() == State.RUNNING, "Cannot call until startup is complete");
return vPeerGroup;

View File

@ -17,12 +17,10 @@
package bisq.core.btc.setup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.exceptions.InvalidHostException;
import bisq.core.btc.exceptions.RejectedTxException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.AddressEntryList;
import bisq.core.btc.model.XmrAddressEntryList;
import bisq.core.btc.nodes.BtcNetworkConfig;
import bisq.core.btc.nodes.BtcNodes;
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.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.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import java.net.InetAddress;
import java.net.UnknownHostException;
@ -100,9 +95,6 @@ import javax.annotation.Nullable;
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.
// 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 static final String PRE_SEGWIT_WALLET_BACKUP = "pre_segwit_haveno_BTC.wallet.backup";
private static final int MIN_BROADCAST_CONNECTIONS = 2;
private static final long DAEMON_POLL_INTERVAL_SECONDS = 20;
private static final int MIN_BROADCAST_CONNECTIONS = 0;
@Getter
public final BooleanProperty walletsSetupFailed = new SimpleBooleanProperty();
@ -122,25 +113,20 @@ public class WalletsSetup {
private final RegTestHost regTestHost;
private final AddressEntryList addressEntryList;
private final XmrAddressEntryList xmrAddressEntryList;
private final Preferences preferences;
private final Socks5ProxyProvider socks5ProxyProvider;
private final Config config;
private final LocalBitcoinNode localBitcoinNode;
private final BtcNodes btcNodes;
@Getter
private final CoreMoneroConnectionsService moneroConnectionsManager;
private final String xmrWalletFileName;
private final int numConnectionsForBtc;
private final String userAgent;
private final NetworkParameters params;
private final File walletDir;
private final int walletRpcBindPort;
private final int socks5DiscoverMode;
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final LongProperty chainHeight = new SimpleLongProperty(0);
private final ObjectProperty<List<MoneroPeer>> peers = new SimpleObjectProperty<>();
private final DownloadListener downloadListener = new DownloadListener();
private final List<Runnable> setupTaskHandlers = new ArrayList<>();
private final List<Runnable> setupCompletedHandlers = new ArrayList<>();
public final BooleanProperty shutDownComplete = new SimpleBooleanProperty();
private final boolean useAllProvidedNodes;
@ -153,36 +139,29 @@ public class WalletsSetup {
@Inject
public WalletsSetup(RegTestHost regTestHost,
AddressEntryList addressEntryList,
XmrAddressEntryList xmrAddressEntryList,
Preferences preferences,
Socks5ProxyProvider socks5ProxyProvider,
Config config,
LocalBitcoinNode localBitcoinNode,
BtcNodes btcNodes,
CoreMoneroConnectionsService moneroConnectionsManager,
@Named(Config.USER_AGENT) String userAgent,
@Named(Config.WALLET_DIR) File walletDir,
@Named(Config.WALLET_RPC_BIND_PORT) int walletRpcBindPort,
@Named(Config.USE_ALL_PROVIDED_NODES) boolean useAllProvidedNodes,
@Named(Config.NUM_CONNECTIONS_FOR_BTC) int numConnectionsForBtc,
@Named(Config.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
this.regTestHost = regTestHost;
this.addressEntryList = addressEntryList;
this.xmrAddressEntryList = xmrAddressEntryList;
this.preferences = preferences;
this.socks5ProxyProvider = socks5ProxyProvider;
this.config = config;
this.localBitcoinNode = localBitcoinNode;
this.btcNodes = btcNodes;
this.moneroConnectionsManager = moneroConnectionsManager;
this.numConnectionsForBtc = numConnectionsForBtc;
this.useAllProvidedNodes = useAllProvidedNodes;
this.userAgent = userAgent;
this.socks5DiscoverMode = evaluateMode(socks5DiscoverModeString);
this.walletDir = walletDir;
this.walletRpcBindPort = walletRpcBindPort;
xmrWalletFileName = "haveno_" + config.baseCurrencyNetwork.getCurrencyCode();
params = Config.baseCurrencyNetworkParameters();
PeerGroup.setIgnoreHttpSeeds(true);
}
@ -206,31 +185,23 @@ public class WalletsSetup {
exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
STARTUP_TIMEOUT + " seconds.")), STARTUP_TIMEOUT);
// initialize Monero connection manager
moneroConnectionsManager.initialize();
backupWallets();
final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
log.info("Socks5Proxy for bitcoinj: socks5Proxy=" + socks5Proxy);
walletConfig = new WalletConfig(params, walletDir, walletRpcBindPort, moneroConnectionsManager, "haveno") {
walletConfig = new WalletConfig(params, walletDir, "haveno") {
@Override
protected void onSetupCompleted() {
//We are here in the btcj thread Thread[ STARTING,5,main]
super.onSetupCompleted();
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.
if (preferences.getBitcoinNodes() != null && !preferences.getBitcoinNodes().isEmpty())
peerGroup.setAddPeersFromAddressMessage(false);
UserThread.runPeriodically(() -> {
updateDaemonInfo();
}, DAEMON_POLL_INTERVAL_SECONDS);
// Need to be Threading.SAME_THREAD executor otherwise BitcoinJ will skip that listener
peerGroup.addPreMessageReceivedEventListener(Threading.SAME_THREAD, (peer, message) -> {
if (message instanceof RejectMessage) {
@ -244,11 +215,12 @@ public class WalletsSetup {
return message;
});
// run external startup handlers
setupTaskHandlers.forEach(Runnable::run);
// Map to user thread
UserThread.execute(() -> {
updateDaemonInfo();
addressEntryList.onWalletReady(walletConfig.btcWallet());
xmrAddressEntryList.onWalletReady(walletConfig.getXmrWallet());
timeoutTimer.stop();
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
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.setConfig(config);
@ -427,9 +382,7 @@ public class WalletsSetup {
///////////////////////////////////////////////////////////////////////////////////////////
public void backupWallets() {
FileUtil.rollingBackup(walletDir, xmrWalletFileName, 20);
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".keys", 20);
FileUtil.rollingBackup(walletDir, xmrWalletFileName + ".address.txt", 20);
// TODO: remove?
}
public void clearBackups() {
@ -479,6 +432,10 @@ public class WalletsSetup {
// Handlers
///////////////////////////////////////////////////////////////////////////////////////////
public void addSetupTaskHandler(Runnable handler) {
setupTaskHandlers.add(handler);
}
public void addSetupCompletedHandler(Runnable handler) {
setupCompletedHandlers.add(handler);
}
@ -492,14 +449,6 @@ public class WalletsSetup {
return walletConfig.btcWallet();
}
public MoneroDaemon getXmrDaemon() {
return walletConfig.getXmrDaemon();
}
public MoneroWallet getXmrWallet() {
return walletConfig.getXmrWallet();
}
public NetworkParameters getParams() {
return params;
}
@ -521,10 +470,6 @@ public class WalletsSetup {
return numPeers;
}
public ReadOnlyObjectProperty<List<MoneroPeer>> peerConnectionsProperty() {
return peers;
}
public LongProperty chainHeightProperty() {
return chainHeight;
}
@ -538,14 +483,7 @@ public class WalletsSetup {
}
public boolean isChainHeightSyncedWithinTolerance() {
Long peersChainHeight = walletConfig.vXmrDaemon.getSyncInfo().getTargetHeight();
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;
throw new RuntimeException("WalletsSetup.isChainHeightSyncedWithinTolerance() not implemented for BTC");
}
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.TransactionVerificationException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.model.InputsAndChangeOutput;
import bisq.core.btc.model.PreparedDepositTxAndMakerInputs;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.setup.WalletConfig;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Res;
import bisq.core.user.Preferences;
import bisq.core.util.ParsingUtils;
import bisq.common.config.Config;
import bisq.common.util.Tuple2;
@ -37,7 +34,6 @@ import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.SegwitAddress;
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 {
private static final Logger log = LoggerFactory.getLogger(TradeWalletService.class);
private static final Coin MIN_DELAYED_PAYOUT_TX_FEE = Coin.valueOf(1000);
@ -94,8 +85,6 @@ public class TradeWalletService {
@Nullable
private Wallet wallet;
@Nullable
private MoneroWallet xmrWallet;
@Nullable
private WalletConfig walletConfig;
@Nullable
private KeyParameter aesKey;
@ -113,7 +102,6 @@ public class TradeWalletService {
walletsSetup.addSetupCompletedHandler(() -> {
walletConfig = walletsSetup.getWalletConfig();
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
///////////////////////////////////////////////////////////////////////////////////////////
@ -1061,16 +1030,6 @@ public class TradeWalletService {
// 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.
*

View File

@ -18,9 +18,8 @@
package bisq.core.btc.wallet;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.crypto.ScryptUtil;
import bisq.core.locale.Res;
import bisq.common.crypto.ScryptUtil;
import bisq.common.handlers.ExceptionHandler;
import bisq.common.handlers.ResultHandler;

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,7 @@ import bisq.core.notifications.MobileNotificationService;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -41,15 +40,15 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class TradeEvents {
private final PubKeyRing pubKeyRing;
private final PubKeyRingProvider pubKeyRingProvider;
private final TradeManager tradeManager;
private final MobileNotificationService mobileNotificationService;
@Inject
public TradeEvents(TradeManager tradeManager, KeyRing keyRing, MobileNotificationService mobileNotificationService) {
public TradeEvents(TradeManager tradeManager, PubKeyRingProvider pubKeyRingProvider, MobileNotificationService mobileNotificationService) {
this.tradeManager = tradeManager;
this.mobileNotificationService = mobileNotificationService;
this.pubKeyRing = keyRing.getPubKeyRing();
this.pubKeyRingProvider = pubKeyRingProvider;
}
public void onAllServicesInitialized() {
@ -74,19 +73,19 @@ public class TradeEvents {
case DEPOSIT_PUBLISHED:
break;
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);
break;
case FIAT_SENT:
// 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);
break;
case FIAT_RECEIVED:
break;
case PAYOUT_PUBLISHED:
// 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);
break;
case WITHDRAWN:

View File

@ -39,7 +39,7 @@ import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import bisq.common.util.Tuple2;
import bisq.common.util.Utilities;
@ -65,7 +65,7 @@ public class CreateOfferService {
private final TxFeeEstimationService txFeeEstimationService;
private final PriceFeedService priceFeedService;
private final P2PService p2PService;
private final PubKeyRing pubKeyRing;
private final PubKeyRingProvider pubKeyRingProvider;
private final User user;
private final BtcWalletService btcWalletService;
private final TradeStatisticsManager tradeStatisticsManager;
@ -81,7 +81,7 @@ public class CreateOfferService {
TxFeeEstimationService txFeeEstimationService,
PriceFeedService priceFeedService,
P2PService p2PService,
PubKeyRing pubKeyRing,
PubKeyRingProvider pubKeyRingProvider,
User user,
BtcWalletService btcWalletService,
TradeStatisticsManager tradeStatisticsManager,
@ -90,7 +90,7 @@ public class CreateOfferService {
this.txFeeEstimationService = txFeeEstimationService;
this.priceFeedService = priceFeedService;
this.p2PService = p2PService;
this.pubKeyRing = pubKeyRing;
this.pubKeyRingProvider = pubKeyRingProvider;
this.user = user;
this.btcWalletService = btcWalletService;
this.tradeStatisticsManager = tradeStatisticsManager;
@ -190,14 +190,14 @@ public class CreateOfferService {
paymentAccount,
currencyCode,
makerFeeAsCoin);
// select signing arbitrator
Mediator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager); // TODO (woodser): using mediator manager for arbitrators
OfferPayload offerPayload = new OfferPayload(offerId,
creationTime,
makerAddress,
pubKeyRing,
pubKeyRingProvider.get(),
OfferPayload.Direction.valueOf(direction.name()),
priceAsLong,
marketPriceMarginParam,

View File

@ -63,7 +63,7 @@ public class OfferFilter {
this.filterManager = filterManager;
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
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
myInsufficientTradeLimitCache.clear());
@ -212,13 +212,13 @@ public class OfferFilter {
myInsufficientTradeLimitCache.put(offerId, result);
return result;
}
public boolean hasValidSignature(Offer offer) {
// get arbitrator
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner());
if (arbitrator == null) return false; // invalid arbitrator
// validate arbitrator signature
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
}

View File

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

View File

@ -17,7 +17,7 @@
package bisq.core.support;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.locale.Res;
import bisq.core.support.messages.ChatMessage;
import bisq.core.support.messages.SupportMessage;
@ -47,7 +47,7 @@ import javax.annotation.Nullable;
@Slf4j
public abstract class SupportManager {
protected final P2PService p2PService;
protected final WalletsSetup walletsSetup;
protected final CoreMoneroConnectionsService connectionService;
protected final Map<String, Timer> delayMsgMap = new HashMap<>();
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedMailboxMessageWithPubKeys = new CopyOnWriteArraySet<>();
private final CopyOnWriteArraySet<DecryptedMessageWithPubKey> decryptedDirectMessageWithPubKeys = new CopyOnWriteArraySet<>();
@ -59,12 +59,11 @@ public abstract class SupportManager {
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
public SupportManager(P2PService p2PService, WalletsSetup walletsSetup) {
public SupportManager(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
this.p2PService = p2PService;
this.connectionService = connectionService;
mailboxMessageService = p2PService.getMailboxMessageService();
this.walletsSetup = walletsSetup;
// We get first the message handler called then the onBootstrapped
p2PService.addDecryptedDirectMessageListener((decryptedMessageWithPubKey, senderAddress) -> {
// 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() {
return allServicesInitialized &&
p2PService.isBootstrapped() &&
walletsSetup.isDownloadComplete() &&
walletsSetup.hasSufficientPeersForBroadcast();
connectionService.isDownloadComplete() &&
connectionService.hasSufficientPeersForBroadcast();
}

View File

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

View File

@ -17,7 +17,7 @@
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.XmrWalletService;
import bisq.core.locale.Res;
@ -95,7 +95,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
public ArbitrationManager(P2PService p2PService,
TradeWalletService tradeWalletService,
XmrWalletService walletService,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
OpenOfferManager openOfferManager,
@ -103,7 +103,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
ArbitrationDisputeListService arbitrationDisputeListService,
Config config,
PriceFeedService priceFeedService) {
super(p2PService, tradeWalletService, walletService, walletsSetup, tradeManager, closedTradableManager,
super(p2PService, tradeWalletService, walletService, connectionService, tradeManager, closedTradableManager,
openOfferManager, keyRing, arbitrationDisputeListService, config, priceFeedService);
}
@ -365,19 +365,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
cleanupRetryMap(uid);
// 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());
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
// parse payout tx
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
if (multisigWallet != null) {
multisigWallet.importMultisigHex(Arrays.asList(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex()));
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());
// 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());
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
sendAckMessage(peerPublishedDisputePayoutTxMessage, peersPubKeyRing, true, null);
requestPersistence();
@ -436,7 +436,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
UUID.randomUUID().toString(),
SupportType.ARBITRATION,
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(),
senderPubKeyRing,
response,
@ -644,8 +644,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
numAttempts++;
payoutTx = multisigWallet.createTx(txConfig);
} catch (MoneroError e) {
System.out.println(e.toString());
System.out.println(e.getStackTrace());
// 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
// 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

View File

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

View File

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

View File

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

View File

@ -1108,7 +1108,8 @@ public abstract class Trade implements Tradable, Model {
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.
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.checkNotNull;
import monero.wallet.model.MoneroTxWallet;
public class TradeManager implements PersistedDataHost, DecryptedDirectMessageListener {
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());
onTradesChanged();
xmrWalletService.setTradeManager(this);
xmrWalletService.getAddressEntriesForAvailableBalanceStream()
.filter(addressEntry -> addressEntry.getOfferId() != null)
.forEach(addressEntry -> {
@ -1014,7 +1014,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
}
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
xmrWalletService.deleteMultisigWallet(trade.getId());
xmrWalletService.deleteMultisigWallet(trade.getId()); // TODO (woodser): don't delete multisig wallet until payout tx unlocked?
requestPersistence();
}
}

View File

@ -17,6 +17,7 @@
package bisq.core.trade.protocol.tasks;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade;
@ -68,6 +69,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
checkNotNull(request);
checkTradeId(processModel.getOfferId(), request);
XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService();
System.out.println("PROCESS MULTISIG MESSAGE");
System.out.println(request);
@ -98,18 +100,18 @@ public class ProcessInitMultisigRequest extends TradeTask {
boolean updateParticipants = false;
if (processModel.getPreparedMultisigHex() == null) {
System.out.println("Preparing multisig wallet!");
multisigWallet = processModel.getProvider().getXmrWalletService().createMultisigWallet(trade.getId());
multisigWallet = xmrWalletService.createMultisigWallet(trade.getId());
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
updateParticipants = true;
} else {
multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
}
// make multisig if applicable
TradingPeer[] peers = getMultisigPeers();
if (processModel.getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) {
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());
updateParticipants = true;
}
@ -117,7 +119,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
// exchange multisig keys if applicable
if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
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);
}

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,6 +60,7 @@ public class OpenOfferManagerTest {
null,
null,
null,
null,
offerBookService,
null,
null,
@ -106,6 +107,7 @@ public class OpenOfferManagerTest {
null,
null,
null,
null,
offerBookService,
null,
null,
@ -146,6 +148,7 @@ public class OpenOfferManagerTest {
null,
null,
null,
null,
offerBookService,
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.HavenoSetup;
import bisq.core.api.AccountServiceListener;
import bisq.core.app.CoreModule;
import bisq.common.UserThread;
import bisq.common.app.AppModule;
import bisq.common.crypto.IncorrectPasswordException;
import bisq.common.handlers.ResultHandler;
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.ThreadFactory;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import bisq.daemon.grpc.GrpcServer;
@Slf4j
@ -61,7 +64,6 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
@Override
protected void launchApplication() {
headlessApp = new HavenoDaemon();
UserThread.execute(this::onApplicationLaunched);
}
@ -101,15 +103,116 @@ public class HavenoDaemonMain extends HavenoHeadlessAppMain implements HavenoSet
@Override
protected void onApplicationStarted() {
super.onApplicationStarted();
grpcServer = injector.getInstance(GrpcServer.class);
grpcServer.start();
}
@Override
public void gracefulShutDown(ResultHandler 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.StopCheckingConnectionsReply;
import bisq.proto.grpc.StopCheckingConnectionsRequest;
import bisq.proto.grpc.UriConnection;
import java.net.URI;
import java.net.URISyntaxException;
import bisq.proto.grpc.UrlConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
@ -84,7 +84,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
public void removeConnection(RemoveConnectionRequest request,
StreamObserver<RemoveConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> {
coreApi.removeMoneroConnection(validateUri(request.getUri()));
coreApi.removeMoneroConnection(validateUri(request.getUrl()));
return RemoveConnectionReply.newBuilder().build();
});
}
@ -93,7 +93,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
public void getConnection(GetConnectionRequest request,
StreamObserver<GetConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> {
UriConnection replyConnection = toUriConnection(coreApi.getMoneroConnection());
UrlConnection replyConnection = toUrlConnection(coreApi.getMoneroConnection());
GetConnectionReply.Builder builder = GetConnectionReply.newBuilder();
if (replyConnection != null) {
builder.setConnection(replyConnection);
@ -107,8 +107,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<GetConnectionsReply> responseObserver) {
handleRequest(responseObserver, () -> {
List<MoneroRpcConnection> connections = coreApi.getMoneroConnections();
List<UriConnection> replyConnections = connections.stream()
.map(GrpcMoneroConnectionsService::toUriConnection).collect(Collectors.toList());
List<UrlConnection> replyConnections = connections.stream()
.map(GrpcMoneroConnectionsService::toUrlConnection).collect(Collectors.toList());
return GetConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
});
}
@ -117,8 +117,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
public void setConnection(SetConnectionRequest request,
StreamObserver<SetConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> {
if (request.getUri() != null && !request.getUri().isEmpty())
coreApi.setMoneroConnection(validateUri(request.getUri()));
if (request.getUrl() != null && !request.getUrl().isEmpty())
coreApi.setMoneroConnection(validateUri(request.getUrl()));
else if (request.hasConnection())
coreApi.setMoneroConnection(toMoneroRpcConnection(request.getConnection()));
else coreApi.setMoneroConnection((MoneroRpcConnection) null); // disconnect from client
@ -131,7 +131,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<CheckConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> {
MoneroRpcConnection connection = coreApi.checkMoneroConnection();
UriConnection replyConnection = toUriConnection(connection);
UrlConnection replyConnection = toUrlConnection(connection);
CheckConnectionReply.Builder builder = CheckConnectionReply.newBuilder();
if (replyConnection != null) {
builder.setConnection(replyConnection);
@ -145,8 +145,8 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<CheckConnectionsReply> responseObserver) {
handleRequest(responseObserver, () -> {
List<MoneroRpcConnection> connections = coreApi.checkMoneroConnections();
List<UriConnection> replyConnections = connections.stream()
.map(GrpcMoneroConnectionsService::toUriConnection).collect(Collectors.toList());
List<UrlConnection> replyConnections = connections.stream()
.map(GrpcMoneroConnectionsService::toUrlConnection).collect(Collectors.toList());
return CheckConnectionsReply.newBuilder().addAllConnections(replyConnections).build();
});
}
@ -176,7 +176,7 @@ class GrpcMoneroConnectionsService extends MoneroConnectionsImplBase {
StreamObserver<GetBestAvailableConnectionReply> responseObserver) {
handleRequest(responseObserver, () -> {
MoneroRpcConnection connection = coreApi.getBestAvailableMoneroConnection();
UriConnection replyConnection = toUriConnection(connection);
UrlConnection replyConnection = toUrlConnection(connection);
GetBestAvailableConnectionReply.Builder builder = GetBestAvailableConnectionReply.newBuilder();
if (replyConnection != null) {
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;
return UriConnection.newBuilder()
.setUri(rpcConnection.getUri())
return UrlConnection.newBuilder()
.setUrl(rpcConnection.getUri())
.setPriority(rpcConnection.getPriority())
.setOnlineStatus(toOnlineStatus(rpcConnection.isOnline()))
.setAuthenticationStatus(toAuthenticationStatus(rpcConnection.isAuthenticated()))
.build();
}
private static UriConnection.AuthenticationStatus toAuthenticationStatus(Boolean authenticated) {
if (authenticated == null) return UriConnection.AuthenticationStatus.NO_AUTHENTICATION;
else if (authenticated) return UriConnection.AuthenticationStatus.AUTHENTICATED;
else return UriConnection.AuthenticationStatus.NOT_AUTHENTICATED;
private static UrlConnection.AuthenticationStatus toAuthenticationStatus(Boolean authenticated) {
if (authenticated == null) return UrlConnection.AuthenticationStatus.NO_AUTHENTICATION;
else if (authenticated) return UrlConnection.AuthenticationStatus.AUTHENTICATED;
else return UrlConnection.AuthenticationStatus.NOT_AUTHENTICATED;
}
private static UriConnection.OnlineStatus toOnlineStatus(Boolean online) {
if (online == null) return UriConnection.OnlineStatus.UNKNOWN;
else if (online) return UriConnection.OnlineStatus.ONLINE;
else return UriConnection.OnlineStatus.OFFLINE;
private static UrlConnection.OnlineStatus toOnlineStatus(Boolean online) {
if (online == null) return UrlConnection.OnlineStatus.UNKNOWN;
else if (online) return UrlConnection.OnlineStatus.ONLINE;
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;
return new MoneroRpcConnection(
validateUri(uriConnection.getUri()),
validateUri(uriConnection.getUrl()),
nullIfEmpty(uriConnection.getUsername()),
nullIfEmpty(uriConnection.getPassword()))
.setPriority(uriConnection.getPriority());
}
private static String validateUri(String uri) throws URISyntaxException {
if (uri.isEmpty()) {
throw new IllegalArgumentException("URI is required");
}
// Create new URI for validation, internally String is used again
return new URI(uri).toString();
private static String validateUri(String url) throws MalformedURLException {
if (url.isEmpty()) throw new IllegalArgumentException("URL is required");
return new URL(url).toString(); // validate and return
}
private static String nullIfEmpty(String value) {

View File

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

View File

@ -48,9 +48,14 @@ class GrpcShutdownService extends ShutdownServerGrpc.ShutdownServerImplBase {
StreamObserver<StopReply> responseObserver) {
try {
log.info("Shutdown request received.");
var reply = StopReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
HavenoHeadlessApp.setOnGracefulShutDownHandler(new Runnable() {
@Override
public void run() {
var reply = StopReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
});
UserThread.runAfter(HavenoHeadlessApp.getShutDownHandler(), 500, MILLISECONDS);
} catch (Throwable cause) {
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.witness.AccountAgeWitnessService;
import bisq.core.alert.PrivateNotificationManager;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.CryptoCurrency;
import bisq.core.locale.CurrencyUtil;
@ -53,7 +53,6 @@ import bisq.core.payment.AliPayAccount;
import bisq.core.payment.AmazonGiftCardAccount;
import bisq.core.payment.CryptoCurrencyAccount;
import bisq.core.payment.RevolutAccount;
import bisq.core.payment.payload.AssetsAccountPayload;
import bisq.core.presentation.BalancePresentation;
import bisq.core.presentation.SupportTicketsPresentation;
import bisq.core.presentation.TradePresentation;
@ -109,7 +108,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener {
private final HavenoSetup bisqSetup;
private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final User user;
private final BalancePresentation balancePresentation;
private final TradePresentation tradePresentation;
@ -140,7 +139,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
private final DoubleProperty combinedSyncProgress = new SimpleDoubleProperty(-1);
private final BooleanProperty isSplashScreenRemoved = new SimpleBooleanProperty();
private final StringProperty footerVersionInfo = new SimpleStringProperty();
private Timer checkNumberOfBtcPeersTimer;
private Timer checkNumberOfXmrPeersTimer;
private Timer checkNumberOfP2pNetworkPeersTimer;
@SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> tradesAndUIReady;
@ -153,7 +152,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
@Inject
public MainViewModel(HavenoSetup bisqSetup,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
BtcWalletService btcWalletService,
User user,
BalancePresentation balancePresentation,
@ -178,7 +177,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
TorNetworkSettingsWindow torNetworkSettingsWindow,
CorruptedStorageFileHandler corruptedStorageFileHandler) {
this.bisqSetup = bisqSetup;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.user = user;
this.balancePresentation = balancePresentation;
this.tradePresentation = tradePresentation;
@ -258,7 +257,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
});
setupP2PNumPeersWatcher();
setupBtcNumPeersWatcher();
setupXmrNumPeersWatcher();
marketPricePresentation.setup();
accountPresentation.setup();
@ -509,19 +508,19 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
});
}
private void setupBtcNumPeersWatcher() {
walletsSetup.numPeersProperty().addListener((observable, oldValue, newValue) -> {
private void setupXmrNumPeersWatcher() {
connectionService.numPeersProperty().addListener((observable, oldValue, newValue) -> {
int numPeers = (int) newValue;
if ((int) oldValue > 0 && numPeers == 0) {
if (checkNumberOfBtcPeersTimer != null)
checkNumberOfBtcPeersTimer.stop();
if (checkNumberOfXmrPeersTimer != null)
checkNumberOfXmrPeersTimer.stop();
checkNumberOfBtcPeersTimer = UserThread.runAfter(() -> {
checkNumberOfXmrPeersTimer = UserThread.runAfter(() -> {
// check again numPeers
if (walletsSetup.numPeersProperty().get() == 0) {
if (connectionService.numPeersProperty().get() == 0) {
if (localBitcoinNode.shouldBeUsed())
getWalletServiceErrorMsg().set(
Res.get("mainView.networkWarning.localhostBitcoinLost",
Res.get("mainView.networkWarning.localhostBitcoinLost", // TODO: update error message for XMR
Res.getBaseCurrencyName().toLowerCase()));
else
getWalletServiceErrorMsg().set(
@ -532,8 +531,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
}
}, 5);
} else if ((int) oldValue == 0 && numPeers > 0) {
if (checkNumberOfBtcPeersTimer != null)
checkNumberOfBtcPeersTimer.stop();
if (checkNumberOfXmrPeersTimer != null)
checkNumberOfXmrPeersTimer.stop();
getWalletServiceErrorMsg().set(null);
}
});

View File

@ -33,9 +33,8 @@ import bisq.desktop.util.Layout;
import bisq.desktop.util.validation.PasswordValidator;
import bisq.core.btc.wallet.WalletsManager;
import bisq.core.crypto.ScryptUtil;
import bisq.core.locale.Res;
import bisq.common.crypto.ScryptUtil;
import bisq.common.util.Tuple4;
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_taker.SellerAsTakerCreatesDepositTxInputs;
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.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
@ -119,7 +118,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
TakerCreateFeeTx.class, // TODO (woodser): rename to TakerCreateFeeTx
SellerAsTakerCreatesDepositTxInputs.class,
TakerProcessesInputsForDepositTxResponse.class,
@ -182,7 +180,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList(
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
TakerCreateFeeTx.class,
BuyerAsTakerCreatesDepositTxInputs.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.Trade;
import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.PubKeyRingProvider;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -35,17 +35,17 @@ public class TransactionAwareTradableFactory {
private final ArbitrationManager arbitrationManager;
private final RefundManager refundManager;
private final XmrWalletService xmrWalletService;
private final PubKeyRing pubKeyRing;
private final PubKeyRingProvider pubKeyRingProvider;
@Inject
TransactionAwareTradableFactory(ArbitrationManager arbitrationManager,
RefundManager refundManager,
XmrWalletService xmrWalletService,
PubKeyRing pubKeyRing) {
PubKeyRingProvider pubKeyRingProvider) {
this.arbitrationManager = arbitrationManager;
this.refundManager = refundManager;
this.xmrWalletService = xmrWalletService;
this.pubKeyRing = pubKeyRing;
this.pubKeyRingProvider = pubKeyRingProvider;
}
TransactionAwareTradable create(Tradable delegate) {
@ -56,7 +56,7 @@ public class TransactionAwareTradableFactory {
arbitrationManager,
refundManager,
xmrWalletService,
pubKeyRing);
pubKeyRingProvider.get());
} else {
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.TradeDetailsWindow;
import bisq.desktop.util.GUIUtil;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.locale.Res;
import bisq.core.offer.OpenOffer;
@ -103,7 +102,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
private final BtcWalletService btcWalletService;
private final P2PService p2PService;
private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final Preferences preferences;
private final TradeDetailsWindow tradeDetailsWindow;
private final OfferDetailsWindow offerDetailsWindow;
@ -120,14 +119,14 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
@Inject
private TransactionsView(BtcWalletService btcWalletService,
P2PService p2PService,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
Preferences preferences,
TradeDetailsWindow tradeDetailsWindow,
OfferDetailsWindow offerDetailsWindow,
DisplayedTransactionsFactory displayedTransactionsFactory) {
this.btcWalletService = btcWalletService;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.preferences = preferences;
this.tradeDetailsWindow = tradeDetailsWindow;
this.offerDetailsWindow = offerDetailsWindow;
@ -537,7 +536,7 @@ public class TransactionsView extends ActivatableView<VBox, Void> {
}
private void revertTransaction(String txId, @Nullable Tradable tradable) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
try {
btcWalletService.doubleSpendTransaction(txId, () -> {
if (tradable != null)

View File

@ -27,7 +27,7 @@ import bisq.desktop.util.DisplayUtils;
import bisq.desktop.util.GUIUtil;
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.CountryUtil;
import bisq.core.locale.CryptoCurrency;
@ -97,8 +97,8 @@ class OfferBookViewModel extends ActivatableViewModel {
private final User user;
private final OfferBook offerBook;
final Preferences preferences;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final CoreMoneroConnectionsService connectionService;
final PriceFeedService priceFeedService;
private final ClosedTradableManager closedTradableManager;
final AccountAgeWitnessService accountAgeWitnessService;
@ -142,7 +142,7 @@ class OfferBookViewModel extends ActivatableViewModel {
OpenOfferManager openOfferManager,
OfferBook offerBook,
Preferences preferences,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
P2PService p2PService,
PriceFeedService priceFeedService,
ClosedTradableManager closedTradableManager,
@ -157,7 +157,7 @@ class OfferBookViewModel extends ActivatableViewModel {
this.user = user;
this.offerBook = offerBook;
this.preferences = preferences;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.p2PService = p2PService;
this.priceFeedService = priceFeedService;
this.closedTradableManager = closedTradableManager;
@ -561,7 +561,7 @@ class OfferBookViewModel extends ActivatableViewModel {
boolean canCreateOrTakeOffer() {
return GUIUtil.canCreateOrTakeOfferOrShowPopup(user, navigation) &&
GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(walletsSetup) &&
GUIUtil.isChainHeightSyncedWithinToleranceOrShowPopup(connectionService) &&
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.util.GUIUtil;
import bisq.desktop.util.Transitions;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.Restrictions;
import bisq.core.locale.Res;
@ -53,7 +52,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
private final WalletPasswordWindow walletPasswordWindow;
private final OpenOfferManager openOfferManager;
private final P2PService p2PService;
private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final BtcWalletService btcWalletService;
private final CoinFormatter btcFormatter;
@ -65,7 +64,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
public BtcEmptyWalletWindow(WalletPasswordWindow walletPasswordWindow,
OpenOfferManager openOfferManager,
P2PService p2PService,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
BtcWalletService btcWalletService,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter btcFormatter) {
headLine(Res.get("emptyWalletWindow.headline", "BTC"));
@ -73,13 +72,14 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
type = Type.Instruction;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.btcWalletService = btcWalletService;
this.btcFormatter = btcFormatter;
this.walletPasswordWindow = walletPasswordWindow;
this.openOfferManager = openOfferManager;
}
@Override
public void show() {
createGridPane();
addHeadLine();
@ -143,7 +143,7 @@ public final class BtcEmptyWalletWindow extends Overlay<BtcEmptyWalletWindow> {
}
private void doEmptyWallet(KeyParameter aesKey) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, walletsSetup)) {
if (GUIUtil.isReadyForTxBroadcastOrShowPopup(p2PService, connectionService)) {
if (!openOfferManager.getObservableList().isEmpty()) {
UserThread.runAfter(() ->
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.validation.LengthValidator;
import bisq.desktop.util.validation.PercentageNumberValidator;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.exceptions.TransactionVerificationException;
import bisq.core.btc.exceptions.TxBroadcastException;
import bisq.core.btc.exceptions.WalletException;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletsManager;
@ -92,7 +91,6 @@ import java.time.Instant;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@ -111,7 +109,7 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
private final P2PService p2PService;
private final MediationManager mediationManager;
private final Preferences preferences;
private final WalletsSetup walletsSetup;
private final CoreMoneroConnectionsService connectionService;
private final WalletsManager walletsManager;
GridPane inputsGridPane;
GridPane importTxGridPane;
@ -150,17 +148,18 @@ public class ManualPayoutTxWindow extends Overlay<ManualPayoutTxWindow> {
P2PService p2PService,
MediationManager mediationManager,
Preferences preferences,
WalletsSetup walletsSetup,
CoreMoneroConnectionsService connectionService,
WalletsManager walletsManager) {
this.tradeWalletService = tradeWalletService;
this.p2PService = p2PService;
this.mediationManager = mediationManager;
this.preferences = preferences;
this.walletsSetup = walletsSetup;
this.connectionService = connectionService;
this.walletsManager = walletsManager;
type = Type.Attention;
}
@Override
public void show() {
if (headLine == null)
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 {
tradeWalletService.emergencyPublishPayoutTxFrom2of2MultiSig(
txIdAndHex.second,

View File

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

View File

@ -398,7 +398,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
return Res.get("portfolio.pending.failedTrade.missingContract");
}
PubKeyRing myPubKeyRing = model.dataModel.getPubKeyRing();
PubKeyRing myPubKeyRing = model.dataModel.getPubKeyRingProvider().get();
boolean isMyRoleBuyer = contract.isMyRoleBuyer(myPubKeyRing);
boolean isMyRoleMaker = contract.isMyRoleMaker(myPubKeyRing);
@ -411,7 +411,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
if (trade.getMakerDepositTx() == null) {
return Res.get("portfolio.pending.failedTrade.missingDepositTx");
}
if (trade.getTakerDepositTx() == null) {
return Res.get("portfolio.pending.failedTrade.missingDepositTx"); // TODO (woodser): use .missingTakerDepositTx, .missingMakerDepositTx
}

View File

@ -409,11 +409,11 @@ public abstract class TradeStepView extends AnchorPane {
private void updateTimeLeft() {
if (timeLeftTextField != null) {
// TODO (woodser): extra TradeStepView created but not deactivated on trade.setState(), so deactivate when model's trade is null
if (model.dataModel.getTrade() == null) {
log.warn("deactivating TradeStepView because model's trade is null");
// schedule deactivation to avoid concurrent modification of clock listeners
Platform.runLater(new Runnable() {
@Override
@ -423,7 +423,7 @@ public abstract class TradeStepView extends AnchorPane {
});
return;
}
String remainingTime = model.getRemainingTradeDurationAsWords();
timeLeftProgressBar.setProgress(model.getRemainingTradeDurationAsPercentage());
if (!remainingTime.isEmpty()) {
@ -675,7 +675,7 @@ public abstract class TradeStepView extends AnchorPane {
DisputeResult disputeResult = optionalDispute.get().getDisputeResultProperty().get();
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 sellerPayoutAmount = model.btcFormatter.formatCoinWithCode(disputeResult.getSellerPayoutAmount());
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.util.GUIUtil;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.btc.nodes.BtcNodes;
import bisq.core.btc.nodes.LocalBitcoinNode;
import bisq.core.btc.setup.WalletsSetup;
@ -38,7 +39,6 @@ import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils;
import bisq.core.util.validation.RegexValidator;
import bisq.core.util.validation.RegexValidatorFactory;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.network.Statistic;
@ -119,6 +119,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
private final ClockWatcher clockWatcher;
private final WalletsSetup walletsSetup;
private final P2PService p2PService;
private final CoreMoneroConnectionsService connectionManager;
private final ObservableList<P2pNetworkListItem> p2pNetworkListItems = FXCollections.observableArrayList();
private final SortedList<P2pNetworkListItem> p2pSortedList = new SortedList<>(p2pNetworkListItems);
@ -139,6 +140,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
@Inject
public NetworkSettingsView(WalletsSetup walletsSetup,
P2PService p2PService,
CoreMoneroConnectionsService connectionManager,
Preferences preferences,
BtcNodes btcNodes,
FilterManager filterManager,
@ -148,6 +150,7 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
super();
this.walletsSetup = walletsSetup;
this.p2PService = p2PService;
this.connectionManager = connectionManager;
this.preferences = preferences;
this.btcNodes = btcNodes;
this.filterManager = filterManager;
@ -291,10 +294,10 @@ public class NetworkSettingsView extends ActivatableView<GridPane, Void> {
reSyncSPVChainButton.setOnAction(event -> GUIUtil.reSyncSPVChain(preferences));
moneroPeersSubscription = EasyBind.subscribe(walletsSetup.peerConnectionsProperty(),
moneroPeersSubscription = EasyBind.subscribe(connectionManager.peerConnectionsProperty(),
this::updateMoneroPeersTable);
moneroBlockHeightSubscription = EasyBind.subscribe(walletsSetup.chainHeightProperty(),
moneroBlockHeightSubscription = EasyBind.subscribe(connectionManager.chainHeightProperty(),
this::updateChainHeightTextField);
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.AccountAgeWitnessService;
import bisq.core.api.CoreMoneroConnectionsService;
import bisq.core.app.HavenoSetup;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.locale.Country;
import bisq.core.locale.CountryUtil;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.locale.TradeCurrency;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferRestrictions;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountList;
import bisq.core.payment.payload.PaymentMethod;
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.user.DontShowAgainLookup;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter;
import bisq.core.util.coin.CoinUtil;
import bisq.network.p2p.P2PService;
@ -63,7 +58,6 @@ import bisq.common.file.CorruptedStorageFileHandler;
import bisq.common.persistence.PersistenceManager;
import bisq.common.proto.persistable.PersistableEnvelope;
import bisq.common.proto.persistable.PersistenceProtoResolver;
import bisq.common.util.MathUtils;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple3;
import bisq.common.util.Utilities;
@ -72,7 +66,6 @@ import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.uri.BitcoinURI;
import org.bitcoinj.utils.Fiat;
import com.googlecode.jcsv.CSVStrategy;
import com.googlecode.jcsv.writer.CSVEntryConverter;
@ -757,17 +750,17 @@ public class GUIUtil {
return true;
}
public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, WalletsSetup walletsSetup) {
public static boolean isReadyForTxBroadcastOrShowPopup(P2PService p2PService, CoreMoneroConnectionsService connectionService) {
if (!GUIUtil.isBootstrappedOrShowPopup(p2PService)) {
return false;
}
if (!walletsSetup.hasSufficientPeersForBroadcast()) {
new Popup().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", walletsSetup.getMinBroadcastConnections())).show();
if (!connectionService.hasSufficientPeersForBroadcast()) {
new Popup().information(Res.get("popup.warning.notSufficientConnectionsToBtcNetwork", connectionService.getMinBroadcastConnections())).show();
return false;
}
if (!walletsSetup.isDownloadComplete()) {
if (!connectionService.isDownloadComplete()) {
new Popup().information(Res.get("popup.warning.downloadNotComplete")).show();
return false;
}
@ -775,8 +768,8 @@ public class GUIUtil {
return true;
}
public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(WalletsSetup walletsSetup) {
if (!walletsSetup.isChainHeightSyncedWithinTolerance()) {
public static boolean isChainHeightSyncedWithinToleranceOrShowPopup(CoreMoneroConnectionsService connectionService) {
if (!connectionService.isChainHeightSyncedWithinTolerance()) {
new Popup().information(Res.get("popup.warning.chainNotSynced")).show();
return false;
}

View File

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

View File

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

View File

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

View File

@ -24,20 +24,134 @@ option java_package = "bisq.proto.grpc";
option java_multiple_files = true;
///////////////////////////////////////////////////////////////////////////////////////////
// DisputeAgents
// Help
///////////////////////////////////////////////////////////////////////////////////////////
service DisputeAgents {
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) {
service Help {
rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) {
}
}
message RegisterDisputeAgentRequest {
string dispute_agent_type = 1;
string registration_key = 2;
message GetMethodHelpRequest {
string method_name = 1;
}
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 {
enum NotificationType {
TRADE_UPDATE = 0;
CHAT_MESSAGE = 1;
KEEP_ALIVE = 2;
APP_INITIALIZED = 0;
KEEP_ALIVE = 1;
TRADE_UPDATE = 2;
CHAT_MESSAGE = 3;
}
string id = 1;
@ -78,20 +193,134 @@ message SendNotificationReply {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Help
// MoneroConnections
///////////////////////////////////////////////////////////////////////////////////////////
service Help {
rpc GetMethodHelp (GetMethodHelpRequest) returns (GetMethodHelpReply) {
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 GetMethodHelpRequest {
string method_name = 1;
message UrlConnection {
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 {
string method_help = 1;
message AddConnectionRequest {
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 {
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -703,134 +932,4 @@ message AddressBalanceInfo {
int64 balance = 2;
int64 num_confirmations = 3;
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 {
string uri = 1;
string url = 1;
string username = 2;
bytes encrypted_password = 3;
bytes encryption_salt = 4;
@ -1706,7 +1706,7 @@ message EncryptedConnection {
message EncryptedConnectionList {
bytes salt = 1;
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)
bool auto_switch = 5;
}